0

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'

  • Is there something like a "refresh" method for win32com?
  • Is it due to the instance type being Library.ApplicationAddIn that I only see default methods?

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)
2
  • 1
    The low-level check is to see what win32com wrapper files are being generated. See this: stackoverflow.com/questions/52889704/…. The first step is to find your gen_py folder and and delete all the folders under your Python version (eg 3.9). This will force a regeneration of the gencache wrapper .py files (in a CLSID folder) and try your code again. If you still get the error, find the wrapper file and check (a) your function is there, and (b) you have spelled it correctly (case-sensitive). Commented Jan 19, 2024 at 9:33
  • I did this (3.10 btw). 1st run afterwards failed, 2nd+ runs: Same behaviour as before. Let me add some info per EDIT Commented Jan 19, 2024 at 9:47

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks, that's a valuable hint! I got a step further, but the next issue seems to be quite 'pythonic'. I'll add more by EDIT
No, that was it! The rest of the errors were by me :)
This is probably why your VBA worked: I am guessing you have Dim obj as Object rather than including References to the type library. Your original code might also work if you didn't use Ensure Module and deleted your gen_py folders. Then win32com would default to late-binding: where there is no "pre-checking" of function calls. In late-binding the re-typing referred to in the answer isn't needed.
Yes, original code had the right Dim statement, didn't realize that.. I tried the late-binding by explicitly running with win32com.client.dynamic but thit failed at different places before I even got to my issue.
But I just realized what the EnsureModule is used for: The sole purpose is typecasting and I can do the same for my case: oSTLTranslator = self.mod.TranslatorAddIn(oSTLTranslator) thx :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.