pythonwin32comautodesk-inventor

python win32com with Inventor seems to be missing methods


I am trying to translate sample VBA code from Inventor over to python.
Most things, like modifying parameters work really well. But unfortunately, export to STL is failing.

It seems, that methods of the "STL Exporter Plugin" that should be there are just missing.
In my case: HasSaveCopyAsOptions and SaveCopyAs. (This even seems to happen for other plugins..)

Error message:
AttributeError: '<win32com.gen_py.Autodesk Inventor Object Library.ApplicationAddIn instance at 0x1940552744240>' object has no attribute 'HasSaveCopyAsOptions'

I already set a breakpoint before calling SaveCopyAs and tried to dir(oSTLTranslator). The resulting list looks quite empty for my expectations.

# -*- coding: utf-8 -*-

import win32com.client
from win32com.client import gencache, Dispatch, constants, DispatchEx

import os

# !!MINIFIED VERSION OF CLASS!!
class InventorAPI:
    
    def loadInventorApp(self):
        self.oApp = win32com.client.Dispatch('Inventor.Application')
        self.oApp.Visible = True
        self.mod = gencache.EnsureModule('{D98A091D-3A0F-4C3E-B36E-61F62068D488}', 0, 1, 0)
        self.oApp = self.mod.Application(self.oApp)
        # self.oApp.SilentOperation = True
        
    def loadInventorDoc(self):
        oDoc = self.oApp.ActiveDocument
        self.oDoc = self.mod.PartDocument(oDoc)

    def listAllPlugins(self):
        no = self.oApp.ApplicationAddIns.Count
        print(f"Listing {no} plugins:")
        i=0
        while (i<no):
            i+=1 #Increments first, as index is 1-based
            name = self.oApp.ApplicationAddIns.Item(i).ShortDisplayName
            id = self.oApp.ApplicationAddIns.Item(i).ClientId
            print(f"{i:02}: {id} - {name}")
            

    def saveStl(self, Name):
      oSTLTranslator = self.oApp.ApplicationAddIns.ItemById("{533E9A98-FC3B-11D4-8E7E-0010B541CD80}")
      if oSTLTranslator is None:
        raise( "Could not access STL translator." )
      oContext = self.oApp.TransientObjects.CreateTranslationContext()
      oOptions = self.oApp.TransientObjects.CreateNameValueMap()
      
      if oSTLTranslator.HasSaveCopyAsOptions(self.oApp.ActiveDocument, oContext, oOptions):
        #Set accuracy.        
        # 0 = High        
        # 1 = Medium        
        # 2 = Low        

        oOptions.SetValue("Resolution", 0)
        oContext.Type = kFileBrowseIOMechanism 
        oData = self.oApp.TransientObjects.CreateDataMedium()
        oData.FileName = Name
        
        oSTLTranslator.SaveCopyAs(self.oApp.ActiveDocument, oContext, oOptions, oData)
    

inv = InventorAPI()
inv.loadInventorApp()

#inv.listAllPlugins()
#exit()

inv.loadInventorDoc()
inv.saveStl("Mount_75mm.stl")
exit()

UPDATE:
[No longer relevant - Check post history, if interested]

UPDATE 2:
As pointed ot by Michael, the issue is indeed with the types!
I created a custom ItemById method to cast to TranslatorAddIn:

# Result is of type CLSID (default: ApplicationAddIn)
# The method ItemById is actually a property, but must be used as a method to correctly pass the arguments
import pythoncom
defaultNamedNotOptArg=pythoncom.Empty
LCID = 0x0
def ItemByIdEx(oApplicationAddIns, ClientId=defaultNamedNotOptArg, CLSID='{A0481EEB-2031-11D3-B78D-0060B0F159EF}'):
    'Retrieves an ApplicationAddIn object based on the Client Id'
    ret = oApplicationAddIns._oleobj_.InvokeTypes(50335746, LCID, 2, (9, 0), ((8, 1),),ClientId
        )
    if ret is not None:
        ret = win32com.client.Dispatch(ret, 'ItemById', CLSID) #<== CHANGED THIS, original code has default ID of ApplicationAddIn 
    return ret

With this, my object oSTLTranslator has the missing methods available. I can now call

#oSTLTranslator = self.oApp.ApplicationAddIns.ItemById("{533E9A98-FC3B-11D4-8E7E-0010B541CD80}")
oSTLTranslator = ItemByIdEx(self.oApp.ApplicationAddIns, "{533E9A98-FC3B-11D4-8E7E-0010B541CD80}", "{6ECCBC87-A50D-11D4-8DE4-0010B541CAA8}")

UPDATE 3:
Custom method is not even needed!
The purpose of self.mod is to serve as type-caster.
Just do: oSTLTranslator = self.mod.TranslatorAddIn(oSTLTranslator)

Final function:

def saveStl(self, Name):
  oSTLTranslator = self.oApp.ApplicationAddIns.ItemById("{533E9A98-FC3B-11D4-8E7E-0010B541CD80}")
  if oSTLTranslator is None:
    raise ValueError("Could not access STL translator." )
  oSTLTranslator = self.mod.TranslatorAddIn(oSTLTranslator)

  oContext = self.oApp.TransientObjects.CreateTranslationContext()
  oOptions = self.oApp.TransientObjects.CreateNameValueMap()
        
  if oSTLTranslator.HasSaveCopyAsOptions(self.oApp.ActiveDocument, oContext, oOptions):
    #Set accuracy.        
    # 2 = High        
    # 1 = Medium        
    # 0 = Low        

    #For completeness - usually no need to set all of these
    oOptions.SetValue("Resolution", 0)
    oOptions.SetValue("SurfaceDeviation", 5.0)
    oOptions.SetValue("NormalDeviation", 1000.0)
    oOptions.SetValue("MaxEdgeLength", 100000.0)
    oOptions.SetValue("AspectRatio", 2150.0)
    oOptions.SetValue("OutputFileType", 0)
    oContext.Type = win32com.client.constants.kFileBrowseIOMechanism 
    oData = self.oApp.TransientObjects.CreateDataMedium()
    oData.FileName = Name
    
    oSTLTranslator.SaveCopyAs(self.oApp.ActiveDocument, oContext, oOptions, oData)

Solution

  • I'm sorry I'm not familiar with python. But the issue is in used types. Method ApplicationAddIns.ItemById returns object of type ApplicationAddIn. But translators are of type TranslatorAddIn which is derived from ApplicationAddIn.

    Try to re-type variable oSTLTranslator to TranslatorAddIn and I expect it will work.