除了标注对象类型(如注释,网格和零件对象)之外,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, FreeCADGui
from pivy import coin

class Box:
    def __init__(self, obj):
        '''添加Box自定义属性特征'''
        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):
        '''定义属性改变时的操作'''
        FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n")
 
    def execute(self, fp):
        '''在进行重新计算时执行某些操作,此方法是必需的'''
        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):
        '''返回默认显示模式的名称。它必须在getDisplayModes中定义。Return the name of the default display mode. It must be defined in getDisplayModes.'''
        return "Shaded"
 
    def setDisplayMode(self,mode):
        '''将attach中定义的显示模式映射到getDisplayModes中定义的那些。\ 
                因为它们具有相同的名称,所以不需要做任何事情。这个方法是可选的
                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):
        '''返回XPM格式的图标,该图标将显示在树形视图中。此方法是\ 
                optional,如果未定义,则显示默认图标。
                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):
        '''保存文档时,使用Python的json模块存储此对象。
                返回所有可序列化对象的元组或无。
                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 None


def makeBox():
    FreeCAD.newDocument()
    a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box")
    Box(a)
    ViewProviderBox(a.ViewObject)

makeBox()

可用的属性

属性是FeaturePython对象的真正构建元素。通过它们,用户将能够交互和修改您的对象。在文档中创建新的FeaturePython对象(obj = FreeCAD.ActiveDocument.addObject(“App :: FeaturePython”,“Box”))后,您可以通过发出以下命令获取可用属性的列表:

obj.supportedProperties()

您将获得可用属性的列表:

App :: PropertyBool 
App :: PropertyBoolList 
App :: PropertyFloat 
App :: PropertyFloatList 
App :: PropertyFloatConstraint 
App :: PropertyQuantity 
App :: PropertyQuantityConstraint 
App :: PropertyAngle 
App :: PropertyDistance 
App :: PropertyLength 
App :: PropertySpeed 
App :: PropertyAcceleration 
App: :PropertyForce 
App :: PropertyPressure 
App :: PropertyInteger 
App :: PropertyIntegerConstraint 
App :: PropertyPercent 
App :: PropertyEnumeration 
App :: PropertyIntegerList 
App :: PropertyIntegerSet 
App :: PropertyMap 
App :: PropertyString 
App :: PropertyUUID 
App :: PropertyFont
App :: PropertyStringList 
App :: PropertyLink 
App :: PropertyLinkSub 
App :: PropertyLinkList 
App :: PropertyLinkSubList 
App :: PropertyMatrix 
App :: PropertyVector 
App :: PropertyVectorList 
App :: PropertyPlacement 
App :: PropertyPlacementLink 
App :: PropertyColor 
App :: PropertyColorList 
App: :PropertyMaterial 
App :: PropertyPath 
App :: PropertyFile 
App :: PropertyFileIncluded 
App :: PropertyPythonObject 
Part :: PropertyPartShape 
Part :: PropertyGeometryList 
Part :: PropertyShapeHistory 
Part :: PropertyFilletEdges 
Sketcher :: PropertyConstraintList

向自定义对象添加属性时,请注意以下事项:

  • 不要在属性描述中使用字符“<”或“>”(这会破坏.fcstd文件中的xml片段)
  • 属性按字母顺序存储在.fcstd文件中。如果属性中有形状,则按字母顺序在“Shape”之后的任何属性将在形状之后加载,这可能会导致奇怪的行为。

财产类型

默认情况下,可以更新属性。可以使属性为只读,例如在想要显示方法结果的情况下。也可以隐藏酒店。可以使用设置属性类型

obj.setEditorMode(“MyPropertyName”,mode)

其中mode是一个short int,可以设置为:

0  - 默认模式,读写
 1  - 只读
 2  - 隐藏

在FreeCAD文件重新加载时未设置EditorModes。这可以通过__setstate__函数完成。请参阅http://forum.freecadweb.org/v...。通过使用setEditorMode,属性仅在PropertyEditor中读取。它们仍然可以从python中更改。要真正使它们只读,必须直接在addProperty函数内传递设置。有关示例,请参见http://forum.freecadweb.org/v...


其他更复杂的例子

此示例使用“ 零件模块”创建八面体,然后使用“关键”创建其硬币表示。

首先是Document对象本身:

import FreeCAD, FreeCADGui, Part
import pivy
from pivy import coin

class Octahedron:
  def __init__(self, obj):
     "Add some custom properties to our box feature"
     obj.addProperty("App::PropertyLength","Length","Octahedron","Length of the octahedron").Length=1.0
     obj.addProperty("App::PropertyLength","Width","Octahedron","Width of the octahedron").Width=1.0
     obj.addProperty("App::PropertyLength","Height","Octahedron", "Height of the octahedron").Height=1.0
     obj.addProperty("Part::PropertyPartShape","Shape","Octahedron", "Shape of the octahedron")
     obj.Proxy = self

  def execute(self, fp):
     # Define six vetices for the shape
     v1 = FreeCAD.Vector(0,0,0)
     v2 = FreeCAD.Vector(fp.Length,0,0)
     v3 = FreeCAD.Vector(0,fp.Width,0)
     v4 = FreeCAD.Vector(fp.Length,fp.Width,0)
     v5 = FreeCAD.Vector(fp.Length/2,fp.Width/2,fp.Height/2)
     v6 = FreeCAD.Vector(fp.Length/2,fp.Width/2,-fp.Height/2)
     
     # Make the wires/faces
     f1 = self.make_face(v1,v2,v5)
     f2 = self.make_face(v2,v4,v5)
     f3 = self.make_face(v4,v3,v5)
     f4 = self.make_face(v3,v1,v5)
     f5 = self.make_face(v2,v1,v6)
     f6 = self.make_face(v4,v2,v6)
     f7 = self.make_face(v3,v4,v6)
     f8 = self.make_face(v1,v3,v6)
     shell=Part.makeShell([f1,f2,f3,f4,f5,f6,f7,f8])
     solid=Part.makeSolid(shell)
     fp.Shape = solid

  # helper mehod to create the faces
  def make_face(self,v1,v2,v3):
     wire = Part.makePolygon([v1,v2,v3,v1])
     face = Part.Face(wire)
     return face
Then, we have the view provider object, responsible for showing the object in the 3D scene:

class ViewProviderOctahedron:
  def __init__(self, obj):
     "Set this object to the proxy object of the actual view provider"
     obj.addProperty("App::PropertyColor","Color","Octahedron","Color of the octahedron").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()

     self.data=coin.SoCoordinate3()
     self.face=coin.SoIndexedLineSet()

     self.shaded.addChild(self.scale)
     self.shaded.addChild(self.color)
     self.shaded.addChild(self.data)
     self.shaded.addChild(self.face)
     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(self.data)
     self.wireframe.addChild(self.face)
     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
     if prop == "Shape":
        s = fp.getPropertyByName("Shape")
        self.data.point.setNum(6)
        cnt=0
        for i in s.Vertexes:
           self.data.point.set1Value(cnt,i.X,i.Y,i.Z)
           cnt=cnt+1
        
        self.face.coordIndex.set1Value(0,0)
        self.face.coordIndex.set1Value(1,1)
        self.face.coordIndex.set1Value(2,2)
        self.face.coordIndex.set1Value(3,-1)

        self.face.coordIndex.set1Value(4,1)
        self.face.coordIndex.set1Value(5,3)
        self.face.coordIndex.set1Value(6,2)
        self.face.coordIndex.set1Value(7,-1)

        self.face.coordIndex.set1Value(8,3)
        self.face.coordIndex.set1Value(9,4)
        self.face.coordIndex.set1Value(10,2)
        self.face.coordIndex.set1Value(11,-1)

        self.face.coordIndex.set1Value(12,4)
        self.face.coordIndex.set1Value(13,0)
        self.face.coordIndex.set1Value(14,2)
        self.face.coordIndex.set1Value(15,-1)

        self.face.coordIndex.set1Value(16,1)
        self.face.coordIndex.set1Value(17,0)
        self.face.coordIndex.set1Value(18,5)
        self.face.coordIndex.set1Value(19,-1)

        self.face.coordIndex.set1Value(20,3)
        self.face.coordIndex.set1Value(21,1)
        self.face.coordIndex.set1Value(22,5)
        self.face.coordIndex.set1Value(23,-1)

        self.face.coordIndex.set1Value(24,4)
        self.face.coordIndex.set1Value(25,3)
        self.face.coordIndex.set1Value(26,5)
        self.face.coordIndex.set1Value(27,-1)

        self.face.coordIndex.set1Value(28,0)
        self.face.coordIndex.set1Value(29,4)
        self.face.coordIndex.set1Value(30,5)
        self.face.coordIndex.set1Value(31,-1)

  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):
     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 """
        /* 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):
     return None

  def __setstate__(self,state):
     return None

小佩琪
9 声望6 粉丝