除了标注对象类型(如注释,网格和零件对象)之外,FreeCAD还提供了构建100%python脚本对象(称为Python功能)的惊人可能性。这些对象的行为与任何其他FreeCAD对象完全相同,并在文件保存/加载时自动保存和恢复。

必须了解一个特性,这些对象使用python的json模块保存在FreeCAD FcStd文件中。该模块将python对象转换为字符串,允许将其添加到保存的文件中。在加载时,json模块使用该字符串重新创建原始对象,前提是它可以访问创建该对象的源代码。这意味着如果保存这样的自定义对象并在不存在生成该对象的python代码的机器上打开它,则不会重新创建该对象。如果将这些对象分发给其他人,则需要分发创建它的python脚本。

Python功能遵循与所有FreeCAD功能相同的规则:它们分为App和GUI部分。应用程序部分Document对象定义了对象的几何形状,而GUI部分View Provider Object定义了如何在屏幕上绘制对象。与任何其他FreeCAD功能一样,View Provider Object仅在您自己的GUI中运行FreeCAD时可用。有几个属性和方法可用于构建对象。属性必须是FreeCAD提供的任何预定义属性类型,并且将显示在属性视图窗口中,以便用户可以编辑它们。这样,FeaturePython对象就是真正完全参数化的。您可以单独定义Object及其ViewObject的属性。

提示:在以前的版本中,我们使用了Python的cPickle模块。但是,该模块执行任意代码,从而导致安全问题。因此,我们转向Python的json模块。

基本的例子

可以在src / Mod / TemplatePyMod / FeaturePython.py文件中找到以下示例,以及其他几个示例:

'''Examples for a feature class and its view provider.'''import FreeCAD, FreeCADGuifrom pivy import coinclass Box:    def __init__(self, obj):        '''Add some custom properties to our box feature'''        obj.addProperty("App::PropertyLength","Length","Box","Length of the box").Length=1.0        obj.addProperty("App::PropertyLength","Width","Box","Width of the box").Width=1.0        obj.addProperty("App::PropertyLength","Height","Box", "Height of the box").Height=1.0        obj.Proxy = self       def onChanged(self, fp, prop):        '''Do something when a property has changed'''        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")     def execute(self, fp):        '''Do something when doing a recomputation, this method is mandatory'''        FreeCAD.Console.PrintMessage("Recompute Python Box feature\n")class ViewProviderBox:    def __init__(self, obj):        '''Set this object to the proxy object of the actual view provider'''        obj.addProperty("App::PropertyColor","Color","Box","Color of the box").Color=(1.0,0.0,0.0)        obj.Proxy = self     def attach(self, obj):        '''Setup the scene sub-graph of the view provider, this method is mandatory'''        self.shaded = coin.SoGroup()        self.wireframe = coin.SoGroup()        self.scale = coin.SoScale()        self.color = coin.SoBaseColor()               data=coin.SoCube()        self.shaded.addChild(self.scale)        self.shaded.addChild(self.color)        self.shaded.addChild(data)        obj.addDisplayMode(self.shaded,"Shaded");        style=coin.SoDrawStyle()        style.style = coin.SoDrawStyle.LINES        self.wireframe.addChild(style)        self.wireframe.addChild(self.scale)        self.wireframe.addChild(self.color)        self.wireframe.addChild(data)        obj.addDisplayMode(self.wireframe,"Wireframe");        self.onChanged(obj,"Color")     def updateData(self, fp, prop):        '''If a property of the handled feature has changed we have the chance to handle this here'''        # fp is the handled feature, prop is the name of the property that has changed        l = fp.getPropertyByName("Length")        w = fp.getPropertyByName("Width")        h = fp.getPropertyByName("Height")        self.scale.scaleFactor.setValue(float(l),float(w),float(h))        pass     def getDisplayModes(self,obj):        '''Return a list of display modes.'''        modes=[]        modes.append("Shaded")        modes.append("Wireframe")        return modes     def getDefaultDisplayMode(self):        '''Return the name of the default display mode. It must be defined in getDisplayModes.'''        return "Shaded"     def setDisplayMode(self,mode):        '''Map the display mode defined in attach with those defined in getDisplayModes.\                Since they have the same names nothing needs to be done. This method is optional'''        return mode     def onChanged(self, vp, prop):        '''Here we can do something when a single property got changed'''        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")        if prop == "Color":            c = vp.getPropertyByName("Color")            self.color.rgb.setValue(c[0],c[1],c[2])     def getIcon(self):        '''Return the icon in XPM format which will appear in the tree view. This method is\                optional and if not defined a default icon is shown.'''        return """            /* XPM */            static const char * ViewProviderBox_xpm[] = {            "16 16 6 1",            "   c None",            ".  c #141010",            "+  c #615BD2",            "@  c #C39D55",            "#  c #000000",            "$  c #57C355",            "        ........",            "   ......++..+..",            "   .@@@@.++..++.",            "   .@@@@.++..++.",            "   .@@  .++++++.",            "  ..@@  .++..++.",            "###@@@@ .++..++.",            "##$.@@$#.++++++.",            "#$#$.$$$........",            "#$$#######      ",            "#$$#$$$$$#      ",            "#$$#$$$$$#      ",            "#$$#$$$$$#      ",            " #$#$$$$$#      ",            "  ##$$$$$#      ",            "   #######      "};            """     def __getstate__(self):        '''When saving the document this object gets stored using Python's json module.\                Since we have some un-serializable parts here -- the Coin stuff -- we must define this method\                to return a tuple of all serializable objects or None.'''        return None     def __setstate__(self,state):        '''When restoring the serialized object from document we have the chance to set some internals here.\                Since no data were serialized nothing needs to be done here.'''        return Nonedef makeBox():    FreeCAD.newDocument()    a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box")    Box(a)    ViewProviderBox(a.ViewObject)makeBox()