FreeCad數據擴展

除了標註對象類型(如註釋,網格和零件對象)以外,FreeCAD還提供了構建100%python腳本對象(稱爲Python功能)的可能性。這些對象的行爲與任何其餘FreeCAD對象徹底相同,並在文件保存/加載時自動保存和恢復。php

這些對象使用python的json模塊保存在FreeCAD FcStd文件中。該模塊將python對象轉換爲字符串,容許將其添加到保存的文件中。在加載時,json模塊使用該字符串從新建立原始對象,前提是它能夠訪問建立該對象的源代碼。這意味着若是保存這樣的自定義對象並在不存在生成該對象的python代碼的機器上打開它,則不會從新建立該對象。若是將這些對象分發給其餘人,則須要分發建立它的python腳本。python

Python功能遵循與全部FreeCAD功能相同的規則:它們分爲App和GUI部分。應用程序部分Document對象定義了對象的幾何形狀,而GUI部分View Provider Object定義瞭如何在屏幕上繪製對象。與任何其餘FreeCAD功能同樣,View Provider Object僅在您本身的GUI中運行FreeCAD時可用。有幾個屬性和方法可用於構建對象。屬性必須是FreeCAD提供的任何預約義屬性類型,而且將顯示在屬性視圖窗口中,以便用戶能夠編輯它們。這樣,FeaturePython對象就是真正徹底參數化的。您能夠單獨定義Object及其ViewObject的屬性。web

提示:在之前的版本中,咱們使用了Python的cPickle模塊。可是,該模塊執行任意代碼,從而致使安全問題。所以,咱們轉向Python的json模塊。shell

基本的例子

能夠在src / Mod / TemplatePyMod / FeaturePython.py文件中找到如下示例,以及其餘幾個示例:json

'''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

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

向自定義對象添加屬性時,請注意如下事項:ide

  • 不要在屬性描述中使用字符「<」或「>」(這會破壞.fcstd文件中的xml片斷)
  • 屬性按字母順序存儲在.fcstd文件中。若是屬性中有形狀,則按字母順序在「Shape」以後的任何屬性將在形狀以後加載,這可能會致使奇怪的行爲。

財產類型

默認狀況下,能夠更新屬性。可使屬性爲只讀,例如在想要顯示方法結果的狀況下。也能夠隱藏酒店。可使用設置屬性類型函數

obj.setEditorMode(「MyPropertyName」,mode)

其中mode是一個short int,能夠設置爲:ui

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
相關文章
相關標籤/搜索