Here's the initial setup:
- Download and install Python. I used Active Python 2.7.8
- Get Solidworks. Python macros have worked pretty seamlessly across 2012, 2014 and 2015.
- Get familiar with the Solidworks online API help. E.g., http://help.solidworks.com/2014/english/api/sldworksapiprogguide/Welcome.htm is for the 2014 API. Note you can change the year in the URL to access the docs for other versions of Solidworks.
Basic startup
This code snippet connects to running instance of Solidworks of the year specified. For example to connect to Solidworks 2015, setswYearLastDigit = 5
:
import win32com.client import pythoncom swYearLastDigit = 5 sw = win32com.client.Dispatch("SldWorks.Application.%d" % (20+(swYearLastDigit-2))) # e.g. 20 is SW2012, 23 is SW2015You can also invoke Dispatch without the year specification, as in
....Dispatch("SldWorks.Application")
. If there's only one version on your machine, this connects to that version.
At this point, the python code looks similar to the VBA code in the API docs. Sometimes you have to play with whether a function wants args or not. Here's the next piece of the boilerplate I have at the beginning of my scripts:
model = sw.ActiveDoc modelExt = model.Extension selMgr = model.SelectionManager featureMgr = model.FeatureManager sketchMgr = model.SketchManager eqMgr = model.GetEquationMgrAs an example of difference in arguments, consider the
Equation
method on the IEquationMGR
object (eqMgr
in my code above). The 2014 API docs for the Equation member says that you read an equation by reading Equation(idx), and set by putting an equal sign after the expression. In python the binding is a bit different:
print("Equation 1 is: " + eqMgr.Equation(1)) eqMgr.Equation(1, "\"myVar\" = 42") print("Equation 1 is now: " + eqMgr.Equation(1))The most common difference I see between the Visual Basic docs and python are whether to put parenthesis after the member name or not. I just try both and see which works.
By the way, I see little rhyme or reason to the return values of method invocations, both at the API level as well as the values returned in practice. I usually go with the API docs, and assert
return values, then delete the assertion if/when the method doesn't follow the API docs.
Creating arguments of the correct type (aka, getting SelectById2
to work)
Sometimes the method requires some fancy arguments, like reference arguments, or you otherwise just can't figure out what the thing is expecting. The Visual Basic interface is better at automatically converting types into the appropriate COM objects. The python bindings for the API don't work quite as well all the time. So here's what to do when you need to dig deeper and understand how to invoke a method:
- Generate the static python COM bindings for solidworks
- First, run
python c:\Python27\Lib\site-packages\win32com\client\makepy.py
- Select "SldWorks 2015 Type Library" and hit OK. You'll see output like this:
Generating to C:\Users\myhappyuser\AppData\Local\Temp\gen_py\2.7\83A33D31-27C5-11CE-BFD4-00400513BB57x0x23x0.py Building definitions from type library... Generating... Importing module
The exact file name may change depending on your version of Solidworks.
- First, run
- Open up that generated file in a viewer, like Komodo or Notepad
- Open up the web page VARIANT Type Constants in a browser
Let's work a few common examples.
First let's try the macro command to select an object by name. The recommended version of the method is modelExt.SelectByID2. If you try putting in some actual args, you'll see:
c:\Users\happyuser\Documents\MeasuringCup>python ActivePython 2.7.8.10 (ActiveState Software Inc.) based on Python 2.7.8 (default, Jul 2 2014, 19:48:49) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import win32com.client >>> sw = win32com.client.Dispatch("SldWorks.Application") >>> model = sw.ActiveDoc >>> modelExt = model.Extension >>> modelExt.SelectByID2("mysketch", "SKETCH", 0, 0, 0, False, 0, None, 0) Traceback (most recent call last): File "The last number in the", line 1, in File " >", line 2, in SelectByID2 pywintypes.com_error: (-2147352571, 'Type mismatch.', None, 8)
Type mismatch.
line indicates the argument that is causing the problem. In this case it is the None, which is the eighth argument. The API man page says it is expecting a "pointer to a callout". We just want to pass None, but the None isn't getting converted to the right COM object, so we have to do the conversion manually.
To do this, open the generated python file, for Solidworks 2015 it should be named 83A33D31-27C5-11CE-BFD4-00400513BB57x0x23x0.py, and search for 'SelectById2'. You should find a hit that looks like:
def SelectByID2(self, Name=defaultNamedNotOptArg, Type=defaultNamedNotOptArg, X=defaultNamedNotOptArg, Y=defaultNamedNotOptArg , Z=defaultNamedNotOptArg, Append=defaultNamedNotOptArg, Mark=defaultNamedNotOptArg, Callout=defaultNamedNotOptArg, SelectOption=defaultNamedNotOptArg): 'Select a specified entity' return self._oleobj_.InvokeTypes(68, LCID, 1, (11, 0), ((8, 1), (8, 1), (5, 1), (5, 1), (5, 1), (11, 1), (3, 1), (9, 1), (3, 1)),Name , Type, X, Y, Z, Append , Mark, Callout, SelectOption)The list of tuples ( ((8,1), (8, 1), (5, 1), ...) above ) contains info on the expected type of each argument. The eighth tuple corresponds to the problematic eighth argument. That tuple is (9, 1). Now look up '9' in that MSDN web page titled "VARIANT Type Constants" and you'll see it matches with VT_DISPATCH. Here's the magic on how to generate the correct object manually:
arg1 = win32com.client.VARIANT(pythoncom.VT_DISPATCH, None)The first arg to VARIANT is the type of the object to create, and the second arg is the initial contents. So now we can use that and:
>>> arg1 = win32com.client.VARIANT(pythoncom.VT_DISPATCH, None) >>> modelExt.SelectByID2("Sketch1", "SKETCH", 0, 0, 0, False, 0, arg1, 0) TrueHooray!!!
Now let's try a different example. Continuing on, let's get a sketch we selected:
>>> selMgr = model.SelectionManager >>> aSketch = selMgr.GetSelectedObject(1).GetSpecificFeature2 >>> aSketch.Name u'Sketch1'and let's get the plane it came from:
>>> aSketch.GetReferenceEntity(0) Traceback (most recent call last): File "Oops, that didn't work. Well looking into the generate python file and searching for GetReferenceEntity, we find:", line 1, in File " >", line 2, in GetReferenceEntity pywintypes.com_error: (-2147352571, 'Type mismatch.', None, 1)
def GetReferenceEntity(self, LEntityType=defaultNamedNotOptArg): 'Get entity that this sketch is created on' return self._ApplyTypes_(52, 1, (9, 0), ((16387, 3),), u'GetReferenceEntity', None,LEntityType )And then looking at the VARIANT web page, we find that 16387 = pythoncom.VT_BYREF | pythoncom.VT_I4 So GetReferenceEntity uses an output argument to return the entity type. We can construct an output argument similar to what we did for SelectByID2:
arg1 = win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, -1) refPlane = aSketch.GetReferenceEntity(arg1)and now we can see:
>>> arg1 = win32com.client.VARIANT(pythoncom.VT_BYREF | pythoncom.VT_I4, -1) >>> arg1.value -1 >>> refPlane = aSketch.GetReferenceEntity(arg1) >>> arg1.value 4To decode the '4', look at the doc for GetReferenceEntity, which points to the doc for an enumeration type swSelectType_e , which says that 4 maps to
swSelDATUMPLANES
Constants
If you don't want to put '4' and other random constants in your python code, there are two possibilities. The first is to generate the python Solidworks COM constants bindings:- Run
python c:\Python27\Lib\site-packages\win32com\client\makepy.py
- Select "SOLIDWORKS 2015 Constant type library"
- Add an
EnsureModule
command early in your python program using the number shown in the output of the makepy command. For example, with Solidworks 2015, that is:swconst = win32com.client.gencache.EnsureModule('{4687F359-55D0-4CD3-B6CF-2EB42C11F989}', 0, 23, 0).constants # sw2015
assert arg1.value == swconst.swSelDATUMPLANESThis isn't quite as nice as the Visual Basic interface, which structures the constants.
The downside of the EnsureModule
approach is that it requires that anyone using your beautiful python morsel to run makepy.
A more crude approach is to copy the constants you need from the man pages. For example, I have:
class swconst: swSelDATUMPLANES = 4 .... more constants here ...
A prayer for GetMathUtility
And here is the one bit of the API that I can't get to work. For the life of me, I can't seem to get GetMathUtility to work, and so can not figure out how to create a MathPoint. What happens is:>>> mathUtil = sw.GetMathUtility >>> mathUtil.CreatePoint Traceback (most recent call last): File "The output I expect is an error saying an argument is missing. I can not find any argument that placates this method, nor any other method in the mathUtil object returned. Kudos to anyone who can figure this out! It works fine in Visual Basic, so my guess is something is either screwed up in the python binding, or there's some kind of bug in the interface that the visual basic binding manages to avoid.", line 1, in File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 511, in __getattr__ ret = self._oleobj_.Invoke(retEntry.dispid,0,invoke_type,1) pywintypes.com_error: (-2147417851, 'The server threw an exception.', None, None)
mathUtil = sw.GetMathUtility()
ReplyDeleteAdding parenthesis only makes it fail earlier:
ReplyDelete-------------------
Traceback (most recent call last):
File "solidworksApp.py", line 91, in
mathUtil = sw.GetMathUtility()
File "C:\Python27\lib\site-packages\win32com\client\dynamic.py", line 192, in __call__
return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
pywintypes.com_error: (-2147352573, 'Member not found.', None, None)
In the 2012 version of Solidworks helpfile, CreatePoint appears to require a variant array of coordinates. I assume this is a SafeArray of 3 doubles, 8197.
ReplyDeleteI have yet to get this to work by directly editing the proxy stub file.
I'm working on a Solidworks API problem at present (swModeler.CreateBodyFromBox()) and have used similar techniques to transfer SafeArrays of Dispatch pointers and doubles, swModel.RayIntersections().
Woops, I mean 0x2000 + 0x5 = 0x2005 = 8197
ReplyDeleteI'm trying to follow along to your post, and after this line:
ReplyDeletemodelExt.SelectByID2("Sketch1", "SKETCH", 0, 0, 0, False, 0, arg1, 0),
I get the following error:
TypeError: Objects of type 'VARIANT' can not be converted to a COM VARIANT (but obtaining the buffer() of this object could)
Any idea?
Great post btw!
I'm not sure what would cause it. Are you using the same kind/version of python as listed in the post? Do you get the same error when trying the other variant arg examples?
ReplyDeletewoww... great job and nice idea.
ReplyDeleteHi Josh,
ReplyDeleteMy Name is Arvind, I have a small clarification. I wrote a code in vba for dissolving component pattern but it is not getting executed at all and I could not figure out why.
here is my code:
Sub main()
Dim swApp As SldWorks.SldWorks
Dim swModel As SldWorks.ModelDoc2
Dim swfeat As SldWorks.Feature
Dim swComp As SldWorks.Component2
Dim swRootComp As SldWorks.Component2
Dim swConfMgr As SldWorks.ConfigurationManager
Dim swConf As SldWorks.Configuration
Dim partname As SldWorks.Component2
Dim swAssy As SldWorks.AssemblyDoc
Dim swModelDoc As SldWorks.ModelDoc2
Set swApp = Application.SldWorks
Set swModel = swApp.ActiveDoc
Set swAssy = swModel
Set swfeat = swModel.FirstFeature
Set swConfMgr = swModel.ConfigurationManager
Set swConf = swConfMgr.ActiveConfiguration
Set swRootComp = swConf.GetRootComponent3(True)
While Not swfeat Is Nothing
If swfeat.GetTypeName2 = "Reference" Then
Set swComp = swfeat.GetSpecificFeature2
If UCase(Right(swComp.GetPathName, 3)) = "ASM" Then
MsgBox "Assembly:" & swComp.Name2
ElseIf UCase(Right(swComp.GetPathName, 3)) = "PRT" Then
MsgBox "Part :" & swComp.Name2
End If
End If
If swfeat.GetTypeName2 = "ReferencePattern" Then
swfeat.Select (False)
swModel.DissolveComponentPattern
End If
Set swfeat = swfeat.GetNextFeature
Wend
End Sub
Kindly help me out.
Hi Joshua,
ReplyDeleteI just stumbled across your nice article above. I have similar issues with MathUtils.
Did you get in any way closer to a solution ?
Cheers Frank
Nope, I just worked around it.
ReplyDeleteWith Solidworks 2019 API, I am trying to use the cusPropMgr.GetAll3 from the ICustomPropertyManager class. Here is the code I run:
ReplyDeletePropNames = VARIANT(16396,"")
PropTypes = VARIANT(16396,"")
PropValues = VARIANT(16396, "")
PropLink = VARIANT(16396, "")
Resolved = VARIANT(16396, "")
Prop = cusPropMgr.GetAll3(PropNames,PropTypes,PropValues,Resolved,PropLink)
Analyzing the Data the function send me, the "PropNames" are the "PropLink" and the "PropTypes" are the "Resolved" and vice versa. Is this a Solidworks bug? Have you seen that before? Any fix?
I have spent wayyy to much time solving this. Here is what I finaly got to work.
Deleteimport win32com.client
from win32com.client import VARIANT
from pythoncom import *
swApp = win32com.client.Dispatch("SLDWORKS.Application")
activedoc = swApp.ActiveDoc
configname = activedoc.GetActiveConfiguration.name # '' is default
PropNames = VARIANT(VT_BYREF | VT_VARIANT, '');
PropTypes = VARIANT(VT_BYREF | VT_VARIANT, '');
PropValues = VARIANT(VT_BYREF | VT_VARIANT, '');
# note this modifies the above variables, not return them like you would expect.
bool_out = = activedoc.Extension.CustomPropertyManager(configname).GetAll(PropNames,PropTypes,PropValues);
names = PropNames.value # python tuple
types = PropTypes.value
values = PropValues.value
# also, here is how you get mass properties
mp = swApp.GetMassProperties2(str(paths[0]), "Default", int(3))
volume = mp[3] # m^3
surface_area = mp[4] # m^2
mass = mp[5] # kg
Are there any video tutorials out there on this? Would help us noobs learn a bit faster lol
ReplyDeleteIs there a way to extract number of mates used in an assembly?
ReplyDeleteJust wanted to let you know that his has been a Godsend in my first try in writing python scripts to automate SolidWorks.
ReplyDeleteThanks!
Thank you for this awsome blog! I was wondering if you could help clarify one more thing: Let's say we write a Macro directly on Solidworks, (In my case, a Macro that rebuilds the shape after I change coordinates of control points), do you know of a way to run the macro from Python? I got an answer about 'sw.bindings.RunMacro' though I do not find any documentations regarding that. Thank you already!
ReplyDeleteGood question. I don't know offhand. One suggestion is to run python from the command line and try invoking the RunMacro varoius ways manually and see if you can get it to work.
DeleteHi Joshua
ReplyDeleteCan you share few python programs which you have worked on Solidworks. I've started learning and your programs would help me learn faster.
I'll be thankful to you.
Wow thanks for this! I've been struggling to use solidworks with Python, and I managed to sort it out thanks to your guide!
ReplyDelete