distinguish between bodies in Catia - vba

How to distinguish between bodies in Catia (i.e name of the 2 bodies can be the same) and there are no other properties in the properties tab. Is it possible to distinguish using catvba?

Therefor you can read (with undocumented methods) the internal name of a feature/object (which can not be changed):
Dim oModelElement As Object
Dim sInternalName As String
Set oModelElement = oBody.GetItem("ModelElement")
sInternalName = oModelElement.InternalName
MsgBox sInternalName

Related

How to handle object declaration in VBA (Error 91)

I'm stuck in VBA and I couldn't find a good answer in the other questions related to error 91. I want to create an object and store variables and arrays inside that object. I tried an approach like I would do in js:
Dim block As Object
...
Set block = Nothing
block.Name = "Unbekannter Prüfblock"
block.Awf = "Unbekannter Anwendungsfall"
block.Conditions = Array()
block.Checks = Array()
I use the "Set block = Nothing" because I will use it multiple times in a loop.
But all I get is error 91 - Object variable not set
How can I set the object?
Do I really have to declare everything in vba?
Isn't there a "stop annoying me with declaration notices" toggle? ;-)
Update
Thank you all so much for the detailed answers!
As suggested I created a class for "block" and also a class for "condition" and "check". Block for example:
Option Explicit
Public name As String
Public awf As String
Public conditions As Collection
Public checks As Collection
Then I use it inside my code like this:
Dim bl As Block
Dim co As Condition
Dim ce As Check
Set bl = New Block
bl.name = ws.Range("B" & i).value
bl.awf = ws.Range("B" & i).value
Set co = New Condition
co.attr = ws.Range("B" & i).value
co.value = ws.Range("C" & i).value
bl.conditions.Add co
VBA isn't Javascript; objects and their members cannot be created inline, they need a class definition.
When you make a member call against an object, you refer to it by name, and whenever that name refers to a null reference (Nothing) you'll get error 91.
To fix it, you need to ensure every member call is made against a valid object reference. Using the Set keyword you can assign such a reference, and to create a new instance of an object you can use the New keyword followed by the name of the class that defines the type you want a new instance of:
Dim Block As Object
Block.Something = 42 ' Error 91
Set Block = New SomeClass ' set reference
Block.Something = 42 ' OK
Note that because the object is declared As Object, every member call is late-bound (resolved at run-time); if the member doesn't exist (or if there's a typo), you'll get error 438 at run-time.
You can move this error to compile-time with early binding by using a more specific data type for the declaration:
Dim Block As SomeClass
Because the members of SomeClass are known at compile-time, the IDE will now provide you with a member completion list when you type up a member call, and typos will no longer be valid at compile-time: strive to remain in the early-bound realm whenever possible! Note: As Variant (explicit or not) is also similarly late-bound.
So we add a new class module and call it SomeClass and we add a couple of public fields:
Option Explicit
Public Name As String
Public Case As String
Public Condition As Variant
Public Check As Variant
And now you can create and consume a new instance of that class, and add instances of it to a collection to process later (note: you can't do that with a UDT/Type).
The VBIDE settings have an annoying option ("automatic syntax check", IIRC) that immediately pops a message box whenever there's a compilation error on the current line; uncheck it (invalid lines will appear in red, without a message box), but do have the "require variable declaration" setting checked: it will add Option Explicit to every module, and that will spare you from a number of easily avoidable run-time errors, moving them to compile-time.
In JS, you can add properties (together with values) on the fly to an object. That's not possible in VBA (and most other languages).
Your declaration Dim block As Object is defining a variable that is supposed to point to an Object. But it isn't pointing to anything yet, per default it is initialized with Nothing, which is, literally, nothing, and has neither properties nor methods, it's just a placeholder to signal "I don't point to anything yet". Furthermore, Object cannot be instantiated.
in VBA, you assign something to an object variable with Set (this is different to most other languages). If you want to create a new object, you use the keyword New.
However, before you do that, you need to know what kind of object (which class) you need. This can be an existing class, eg a Worksheet or a Range in Excel, or it can be an own defined class (by creating a new class module in your code). In any case, you need to define the properties and the methods of that class. Considering the most simple class module Class1 (of course you should think about a more usefull name):
Option Explicit
Public name as String
Public case as String
(I will not start to talk about private members and getter and setter).
You then write
Dim block As Class1
Set block = New Class1
block.name = "Unbekannter Prüfblock"
(But block.data = "Hello world" will not be possible as data is not a member of your class.)
Big advantage of this attempt is that the compiler can show you when you for example mistyped a property name before you even start your code (Debug->Compile). In JS, you will get either a runtime error or a new property on the fly - hard to find nasty bugs.
If you later need a new (empty) object, just create a new object using New. If the old object is not referenced anywhere else, the VBA runtime will take care that the memory is freed (so no need to write Set block = Nothing).
In case you really don't know the properties in advance (eg when you read XML or JSON files or a key-value list from an Excel sheet...), you can consider to use a Collection or a Dictionary. Plenty of examples on SO and elsewhere.
One remark to block.Condition = Array(). Arrays in VBA are not really dynamic, you cannot add or remove entries easily during runtime. In VBA, you have static and dynamic arrays:
Dim a(1 to 10) as String ' Static array with 10 members (at compile time)
Dim b() as String ' Dynamic array.
However, for dynamic members you need to define how many members you need before you write something into it, you use the ReDim statement for that. Useful if you can calculate the number of members in advance:
Redim b(1 to maxNames) ' Dynamic array, now with maxNames members (whatever maxNames is)
You can change the array size of a dynamic array with Redim Preserve, but that should be an exception (bad programming style, inperformant). Without Preserve, you will get a new array, but the former data is lost.
Redim Preserve b(1 to maxNames+10) ' Now with 10 more members.
If you really don't know the number of members and it can change often during runtime, again a Collection or a Dictionary can be the better alternative. Note that for example a Dictionary can itself a Dictionary as value, which allows to define Tree structures.
Regarding your issue adding to the collection:
You need to add this code to your class module "Block" - only then you can add objects to the collections
Private Sub Class_Initialize()
Set conditions = New Collection
set checks = new Collection
End Sub

Why must Set be used to hold objects?

While working on my PowerPoint macro, I noticed the following:
To obtain the current, active slide:
Dim currSlide As Slide
Set currSlide = Application.ActiveWindow.View.Slide
To obtain a newly created Textbox:
Dim textbox As Shape
Set textbox = currSlide.Shapes.AddTextbox(...)
I'm new to VBA, having worked with Java, C++ & C#. Why must Set be used above? Why does using Slide & Shape instead generate errors? In what way is VBA different in how variable declaration work in this respect?
This is taken from Byte Comb - Values and References in VBA
In VBA, the difference between value types and reference types is made explicit by requiring the keyword Set when assigning a reference. In addition, you will often see assigning a reference referred to as “binding”.
Byte Comb goes into great detail about the inter working of the VBA. I highly recommend that any serious about programming using the VBA read through it.
In layman's terms: Set is used so that the compiler knows that the programming wants to have a variable reference an Object type. In this way, the compiler will know to throw an error when you are trying to assign a value to an object.
Another reason is that objects can have default values.
In this example I have a variable named value that is of a Variant type. Variants can be of any type in the VBA. Notice how the compiler knows that to assign the value of the Range when value = Range("A1") and to set a reference to the Range when the keyword Set is used in Set value = Range("A1").

VBA macros for CATIA works on one computer, and doesn't work on another

I have a CATIA macro in VBA, that draws points by coordinates (from arrays).
It works on my computer (Catia V5-R2014 and on my neigbours - two versions V5-R2014 and R21).
But it doesn't work for colleges in a different city (they have version R21).
Basically, my macro reads input data from file, calculates coordinates, writes them in out-file, and then draws these points.
All steps except the last one work on either computer/version.
But at the last step "their" Catia just doesn't plot anything, w/o any errors.
So the Subruotine for the last step is:
Sub PlotGeometry()
' Nmlp - number of points
Dim i As Integer
Dim oPartDocument As Document
Dim ohSPointCoord() As HybridShapePointCoord
Dim ohSPoints As HybridShapePointCoord
Dim bodies1 As Bodies
Dim body1 As Body
ReDim ohSPointCoord(0 To Nmlp)
Set oPartDocument = CATIA.Documents.Add("Part")
Set oPart = oPartDocument.Part
Set oPartBody = oPart.MainBody
Set oPlaneYZ = oPart.CreateReferenceFromGeometry(oPart.OriginElements.PlaneYZ)
' -- Draw Points
Dim ohSFactory As HybridShapeFactory
Set ohSFactory = oPart.HybridShapeFactory
For i = 0 To Nmlp
Set ohSPointCoord(i) = ohSFactory.AddNewPointCoord(XM(i), YM(i), ZM(i))
oPartBody.InsertHybridShape ohSPointCoord(i)
Next i
oPart.Update
End Sub
What can it be?
Perhaps at your site you have Hybrid Design enabled, and at the other site they do not.
With Hybrid Design enabled, you would be able to add points to a Body. Not so if it is not enabled and you would get no error from your code.
The setting is under Tools->Options->Infrastructure->Part Infrastructure->Part Document Tab->Enable hybrid design inside part bodies and bodies.
For unexplained reasons, hybrid design being enabled is the default. However I do not recommend using it.
If you just want to make your code work in both places then use a Geometrical Set to aggregate your points instead of the main body.
Dim pointsBody as HybridBody
Set pointsBody = oPart.HybridBodies.Add
pointsBody.Name = "Points_Body"
...
For i = 0 To Nmlp
Set ohSPointCoord(i) = ohSFactory.AddNewPointCoord(XM(i), YM(i), ZM(i))
pointsBody.AppendHybridShape ohSPointCoord(i)
Next i
Just a random guess:
Go to VBE>Tools>References
and compare the values from both computers. They should be identical.
Compare these checkboxes:
If they are different, make sure to make them identical to the PC that works.

How do I get the Aggregated Bodies from a CATPart in CATIA API's?

I am able to write recursive subroutines that cycle thru all the Geometrical Sets and Ordered Geometrical Sets without issue, because there is a collection under each GS and OGS for HybridBodies and OrderedGeometricalSets, respectively. However, when I find the Part-Level (Root-Level) Bodies, there is not a Bodies collection inside it. So, when I have a model with multiple aggregated Boolean operation Bodies inside of a body, I can't find them in the standard collections operation in VBA, C#.net, or VB.net.
How can I find these Bodies inside of a Body?
This took a while to figure out, and I'm definitely posting it on the web because it's barely documented.
The problem with Bodies automation is the fact that all bodies are stored in the part level collection. I didn't see that at first, because I'm used to the Geometrical Set and OGS recursion when working with CATIA spec tree navigation.
But the fact that all bodies are stored in the root level collection is actually more of a hindrance than a benefit, because it does not allow for recursive cycling.
I tried to use the selection object searching to find aggregated bodies, but it was too buggy and cumbersome to figure it out.
The best solution for determining if a Body is aggregated via Boolean solids operation in another Body is to use the "InBooleanOperation" method. This is not very well documented and that's why I'm posting it here.
It returns a simple true or false. Like this:
Body CurB = MyBodies.Item(x);
Boolean InBoolOpp = CurB.InBooleanOperation;
if (InBoolOpp == false)
{
// Code here
}
As for finding the parent of a nested Body, I haven't figured it out yet, but I'll post it once I do.
You are correct, all bodies are viewed as being at the root of the specification tree. In VBA you can select a body then search for bodies inside. So use the .InBooleanOperation property first to see if the body is at the root of the tree...if it is, Select it and search for other bodies inside with the code below.
Dim oPartDoc as PartDocument
Set oPartDoc = CATIA.ActiveDocument
Dim oPart as Part
set oPart = oPartDoc.part
Dim oSelection as Selection
Set oSelection = oPartDoc.Selection
Dim cBodies as New Collection
Dim oBody As Body
Set oBody = oPart.Bodies.Item(1)
oSelection.Clear
oSelection.Add oBody 'Add the body to the selection object
oSelection.Search "Type=Body,sel" 'Search in the selected object
'All bodies in the selected body are added to the selection object
'Loop through selected bodies and add to collection
For i = 1 to oSelection.Count
cBodies.add oSelection.Item(i).Value
Next

Outlook Redemption : GetNamesFromIDs

I'm trying to get all property names / values from an Outlook item. I have custom properties in addition to the default outlook item properties. I'm using redemption to get around the Outlook warnings but I'm having some problems with the GetNamesFromIDs method on a Redemption.RDOMail Item....
I'm using my redemption session to get the message and trying to use the message to get the names of all the properties.
Dim rMessage as Redemption.RDOMail = _RDOSession.GetMessageFromID(EntryID, getPublicStoreID())
Dim propertyList As Redemption.PropList = someMessage.GetPropList(Nothing)
For i As Integer = 1 To propertyList.Count + 1
Console.WriteLine(propertyList(i).ToString())
Console.WriteLine(someMessage.GetNamesFromIDs(________, propertyList(i)))
Next
I'm not totally sure what to pass in as the first parameter to getNamesFromIDs. The definition of GetNamesFromIDs is as follows:
GetNamesFromIDs(MAPIProp as Object, PropTag as Integer) As Redemption.NamedProperty
I'm not totally sure what should be passed in as the MAPIProp object. I don't see this property referenced in the documentation. http://www.dimastr.com/redemption/rdo/MAPIProp.htm#properties
Any help or insight would be greatly appreciated.
I think I figured it out. I have used VBA only, so you need to "think around" it's limitations, it will follow the same scheme in VB.NET.
The function signature is this:
Function GetNamesFromIDs(MAPIProp As Unknown, PropTag As Long) As NamedProperty
As the first parameter it requires an object which supports the IUnknown interface. Looking at the Redemption docs it became clear that there is an interface named _MAPIProp, from which many other RDO objects are derived (IRDOMail is among them). So this must be the very RDOMail you are trying to get data out of.
Knowing that, it needed only one other subtle hint from the docs to get it working:
Given a prop tag (>= 0x80000000),
returns the GUID and id of the named
property.
So property tag must be >= 0x80000000, this means it wont work for all properties, but just for the custom ones (I guess that is the distinction in this case, correct me if I'm wrong.) Passing in property tags not fulfilling this condition raises an error message (0x8000ffff "unexpected results").
Here is my code. It is VBA, so forgive me the Hex() blunder, as VBAs long integer overflows for numbers that big. I am sure you will get the picture.
Sub GetNamesFromIds()
Dim rSession As New Redemption.RDOSession
Dim rMessage As Redemption.RDOMail
Dim PropertyList As Redemption.PropList
Dim PropTag As Long
Dim EntryId As String
Dim i As Integer
rSession.MAPIOBJECT = Application.Session.MAPIOBJECT
' retrieve first random mail for this example '
EntryId = ActiveExplorer.CurrentFolder.Items(1).EntryId
Set rMessage = rSession.GetMessageFromID(EntryId)
Set PropertyList = rMessage.GetPropList(0)
For i = 1 To PropertyList.Count
PropTag = PropertyList(i)
If "0x" & Right("00000000" & Hex(PropTag), 8) > "0x80000000" Then
Debug.Print
If IsArray(rMessage.Fields(PropTag)) Then
Debug.Print Hex(PropTag), "(Array:", UBound(rMessage.Fields(PropTag)), "items)"
Else
Debug.Print Hex(PropTag), "(", rMessage.Fields(PropTag), ")"
End If
Debug.Print " GUID:", rMessage.GetNamesFromIds(rMessage, PropTag).GUID
Debug.Print " ID:", rMessage.GetNamesFromIds(rMessage, PropTag).ID
End If
Next
End Sub
First snippet from the output:
8041001E ( urn:content-classes:message )
GUID: {00020386-0000-0000-C000-000000000046}
ID: content-class
Well, for background info, the author suggests using something like OutlookSpy to see how Outlook stores the properties.
Looking at this exchange (make sure to read through all the follow-up responses), there isn't much more (in fact, I think at one point the Outlook MVP types GetNamesFromIDs when he means GetIDsFromNames).
What you might try is using GetIDsFromNames to see what that returns, and then use that to pass to GetNamesFromIDs.
I've used Redemption before, but not in this particular manner, so that's all I've got for you ...