除了標註對象類型(如註釋,網格和零件對象)以外,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
默認狀況下,能夠更新屬性。可使屬性爲只讀,例如在想要顯示方法結果的狀況下。也能夠隱藏酒店。可使用設置屬性類型函數
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