Working with ByRef arrays in CATIA

This is not actually a script, but a solution I’ve found to a huge roadblock I encountered while trying to use Python to automate CATIA V5 using win32com AND use all funcionalities exposed to automation.

We all know it’s very easy to get started by just using late-binding (dynamic dispatch) and the more advanced users will already know that it will be not possible to use Subs that need Byref arrays. Developers of win32com related stuff are aware of the problem but not to the fact that in some applications these kind of Sub are very intensively used i.e. all subs to get triplets such as GetCoordinates of any class derived from the Point class, or GetFirstAxis of any class derived from the Plane class.
Developers state that it’s sufficient to switch to early bindig using makepy to solve the problem but in my case for CATIA V5 R19 it didn’t solve the problem at all, it even added more problems.

Type libraries/COM implementation of CATIA appear to be poorly specified in many cases. When looking at the variant type required by GetCoordinates (and all similar byref functions) described in the py generated from makepy, it specified 8204 that corresponds to pythoncom.VT_ARRAY | pythoncom.VT_VARIANT
It actually missed the VT_BYREF value.
I added it to the bit or operation getting 24588 for pythoncom.VT_ARRAY | pythoncom.VT_VARIANT | pythoncom.VT_BYREF.
After just saving the module, my script instatly worked by just passing and assigning to a tuple:

ptcoord = [0]*3
ptcoord = point.GetCoordinates(ptcoord)

Another big, annoying roadblock that prevented me to even try to solve this issue, and this is still related to the poor TLB specification or COM implementation of CATIA V5. When switching to early-binding (forced by the need to use byref subs), specific classes are assigned to returned objects. But CATIA has some kind of “polymorphic” behaviour in some case. For instance, the type returned by CATIA.ActiveDocument is stated to be Document. But in reality, it will be a specific child of the Document class, i.e. PartDocument or ProductDocument. Early-binding will force the result to be Document because so is specified in the TLB. But the Document class has no property called Part, so there’s no way to get past this point because one can not get to the the Part object. Solution was to acquire the object using “temporary” dynamic dispatch, then use Dispatch() function on it to get the right specific type assigned.
This solved finally the last roadblock:

from win32com.client import Dispatch
from win32com.client.dynamic import DumbDispatch

CATIA = Dispatch('CATIA.Application')
oDoc = Dispatch(DumbDispatch(CATIA.ActiveDocument))
oPart = oDoc.Part

To slightly improve the readability, I created a simple dedicated funcion:

from win32com.client import Dispatch
from win32com.client.dynamic import DumbDispatch
def GetRightDispatch(o):
	return Dispatch(DumbDispatch(o))
CATIA = Dispatch('CATIA.Application')
oDoc = getRightDispatch(CATIA.ActiveDocument)
oPart = oDoc.Part

So i use getRightDispatch() instead of direct assignment each time an COM object property is likely to return a type that is not the right type specified in a tlb, but a child of it. For instance:
Part.HybridShapeFactory (used to return Factory instead of HybridShapeFactory)
HybridShapes.Item(I) (used to return HybridShape instead of the specific HybridShape type)
Part.AnnotationSets
Now the roads are open. Cheers

7 Replies to “Working with ByRef arrays in CATIA”

  1. Hello DEWYDD,

    I am stuck with the same problem too. The below code should return the Center Of Gravity of the body called “Example”

    I have also used the dumb dispatch as you suggested, just in case i dont do some wrong trying to do it straight away.

    here is the code.
    ——————–

    from win32com.client import Dispatch
    from win32com.client.dynamic import DumbDispatch
    from win32com.client import VARIANT
    import pythoncom

    def GetRightDispatch(o):
    return Dispatch(DumbDispatch(o))

    CATIA = Dispatch(‘CATIA.Application’)
    Doc = GetRightDispatch(CATIA.ActiveDocument)
    Catpart = Doc.Part

    CATIA.DisplayFileAlerts = False
    CATIA.Visible = True

    Body=Catpart.Bodies.Item(“Example”)
    SPAWorkbench=Doc.GetWorkbench(“SPAWorkbench”)

    Bodyreference = Doc.Part.CreateReferenceFromObject(Body)
    TheMeasurable = SPAWorkbench.GetMeasurable(Bodyreference)

    #This prints the Volume. I used it to check if everything is alright till here.
    print(TheMeasurable.Volume)

    #Now the COG
    ptcoord = [0.0]*3
    ptcoord = VARIANT(pythoncom.VT_BYREF | pythoncom.VT_ARRAY | pythoncom.VT_VARIANT, ptcoord)

    ptcoord=TheMeasurable.GetCOG(ptcoord)

    print(ptcoord)

    ————————–
    I get this error

    File “c:\Miniconda3\lib\site-packages\win32com\client\dynamic.py”, line 287, in _ApplyTypes_
    result = self._oleobj_.InvokeTypes(*(dispid, LCID, wFlags, retType, argTypes) + args)
    TypeError: Objects for SAFEARRAYS must be sequences (of sequences), or a buffer object.

    The class Measurable has method

    def GetCOG(self, oCoordinates=defaultNamedNotOptArg):
    return self._ApplyTypes_(1610940419, 1, (24, 0), ((8204, 3),), ‘GetCOG’, None,oCoordinates
    )

    Any help to get around this problem will be of great help.

    Regards,
    Nithin

  2. Great article! I am just getting into CATIA v5 automation, and I have a question about how you set up early binding. When I try to use makepy to set up a static type system, there are many options to choose from for CATIA v5. Which one did you select? Also, how do I select which type libraries to use in my program?

    Thanks for your help!

    1. So more information:

      I am trying to get the coordinates of a HybridShapePointOnCurve object. I actually found the right library to use which is the CATGSMIDLItf library. I went into the file generated by makepy, and I found where the GetCoordinates method was defined. Then, I applied the change that you specified in the first section in your article (changing 8204 to 24588). Now, that method in the file is:

      def GetCoordinates(self, oCoordinates=defaultNamedNotOptArg):
      return self._ApplyTypes_(1611005952, 1, (24, 0), ((24588, 3),), ‘GetCoordinates’, None,oCoordinates
      )

      After making this change, I am still getting an error where the GetCoordinates method is failing. Is there anything else that could be an issue with the method specification? The error trace that I am getting is included below.

      Traceback (most recent call last):
      File “catlib.py”, line 70, in
      print(stringer.get_coordinates(point))
      File “catlib.py”, line 56, in get_coordinates
      ptcoord = point.GetCoordinates(ptcoord)
      File “C:\Users\HJOHNS~1\AppData\Local\Temp\gen_py\3.6\87EE735C-DF70-11D1-8556-0060941979CEx0x0x0.py”, line 7397, in GetCoordinates
      return self._ApplyTypes_(1611005952, 1, (24, 0), ((24588, 3),), ‘GetCoordinates’, None,oCoordinates
      File “c:\users\hjohnston7\anaconda\lib\site-packages\win32com\client\__init__.py”, line 467, in _ApplyTypes_
      self._oleobj_.InvokeTypes(dispid, 0, wFlags, retType, argTypes, *args),
      pywintypes.com_error: (-2147352567, ‘Exception occurred.’, (0, ‘CATIAHybridShapePointOnCurve’, ‘The method GetCoordinates failed’, None, 0, -2147467259), None)

  3. Hello DEWYDD and Hunter Johnston,

    I finally managed to crack it as well, Thanks for all the tips and write up regarding the problem. I am pasting the code below, with step by step instructions, how I got it working, The trick was to create an Object normally from CATIA which can output normal properties. The problem with these Objects is when it comes to function with in/out variables. They simply return what is given “in” to it. The trick here is to create new Objects from the generated Library just for the sake of in/out functions. And then assign the previously created Catia Object to the newly created Library Object. I am noteven using dynamic or DumbDispatch here. Here is the Code:

    import win32com.client.dynamic
    # The Below Library is the Library created using makepy, originally
    # had this name – 9143C8CC-1474-11D4-9461-006094EB3826x0x0x0.py
    # renamed and copied to Same folder where the python script is.
    import CATIA_V5_SpaceAnalysisInterfaces_Object_Library as CatLib

    # Get the Catia Application
    CATIA=win32com.client.dynamic.Dispatch(“CATIA.Application”)
    # Select The Document that is Open
    Document = CATIA.ActiveDocument
    # Select the Part in the Open Document
    Part = Document.Part
    # Select The body named “Example”
    Body=Part.Bodies.Item(“Schlecht”)
    # Create a reference from the Body, the reference can then
    # be used to measure Properties
    Bodyreference = Part.CreateReferenceFromObject(Body)

    # Create a SPA Workbench Object and get a Measureable of Body
    SPAWorkbench=Document.GetWorkbench(“SPAWorkbench”)
    Measurable = SPAWorkbench.GetMeasurable(Bodyreference)

    # The above Measurable object can return many values like Volume, Area etc.
    # The Problem is with in-out functions for which the CatLib generated using
    # makepy comes to the rescue. Just to show that The above Measureable object
    # outputs Volume and Area, we print it, just to see its possible.
    print(“Volume: “, Measurable.Volume)
    print(“Area: “, Measurable.Area)

    #The next two steps did the trick.
    #Create a new Measurbale instance from CatLib Measurable Class
    Measurable_object_from_CatLib=CatLib.Measurable

    #Assign the previous Measurebale object to this newly created instam
    Measurable_object_from_CatLib=Measurable

    # Now simply call the functions from the CatLib Measurable Object
    COGcoord=[0,0,0]
    COGcoord=Measurable_object_from_CatLib.GetCOG(COGcoord)

    #And here it is
    print(“Center of Gravity: “, COGcoord)

    Thanks once again.

  4. Sorry I meant to write. – We dont even have to use dynamic.Dispatch or DumDispatch. We can simply use:

    CATIA=win32com.client.Dispatch(“CATIA.Application”)

    and it still works.

  5. Also there a need to change a types numbers from 8204 to 24588 in the Library, like DEWYDD and mentioned in the post. Also sorry for the multiple posts. I didnt find a way to edit or delete my posts to add corrections.

  6. My mistake was that I was not adding the points that I was generating to a HybridBody. Once I did that, everything worked perfectly fine.

Leave a Reply

Your email address will not be published. Required fields are marked *