Scripted objects
Förutom standard objekttyper som annoteringar, nät och delobjekt, så erbjuder FreeCAD också den fantastiska möjligheten att bygga 100% python-skriptade objekt, som kallas för Python Funktioner. dessa objekt kommer att bete sig exakt som alla andra FreeCAD objekt, de kan sparas i ett dokument och öppnas av en annan FreeCAD installation, eftersom den python kod som definierar objektet även sparas i dokumentet.
Python Funktioner följer samma regler som alla FreeCAD funktioner: de är separerade i App och GUI delar. App delen, Dokumentobjektet, definierar vårt objekts geometri , medan dess gränssnittsdel, Visaobjektet, definierar hur objektet kommer att ritas på skärmen. VisaObjektet, är som alla andra FreeCAD funktioner, endast tillgängligt när du kör FreeCAD i dess eget gränssnitt. Det finns flera egenskaper och metoder tillgängliga för att bygga ditt objekt. Egenskaperna måste vara en av de fördefinierade egenskapstyperna som FreeCAD erbjuder, och kommer att visas i egenskapsfönstret, så de kan redigeras av användaren. på detta sätt så är Python Funktionsobjekt totalt parametrisk. Du kan definiera egenskaper för objektet och dess Visaobjekt separat.
Enkelt exempel
Följande exempel kan hittas i src/Mod/TemplatePyMod/FeaturePython.py filen, tillsammans med flera andra exempel:
"Exempel på en funktionsklass och dess visare." import FreeCAD, FreeCADGui from pivy import coin class Box: def __init__(self, obj): "Lägg till några anpassade egenskaper till vår lådfunktion" obj.addProperty("App::PropertyLength","Length","Box","Lådans längd").Length=1.0 obj.addProperty("App::PropertyLength","Width","Box","Lådans bredd").Width=1.0 obj.addProperty("App::PropertyLength","Height","Box", "Lådans höjd").Height=1.0 obj.Proxy = self def onChanged(self, fp, prop): "Gör något när en egenskap har förändrats" FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n") def execute(self, fp): "Gör något när en omberäkning görs, denna metod är obligatorisk" FreeCAD.Console.PrintMessage("Beräkna om Python lådfunktion\n") class ViewProviderBox: def __init__(self, obj): "Sätt detta objekt till den aktuella visarens proxy objekt" obj.addProperty("App::PropertyColor","Color","Box","Color of the box").Color=(1.0,0.0,0.0) obj.Proxy = self def attach(self, obj): "Ställ in visarens scen subgraf, denna metod är obligatorisk" 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): "Om en egenskap på den hanterade funktionen har förändrats, så har vi chansen att hantera det här" # fp är den hanterade funktionen, prop är namnet på den egenskap som har förändrats l = fp.getPropertyByName("Length") w = fp.getPropertyByName("Width") h = fp.getPropertyByName("Height") self.scale.scaleFactor.setValue(l,w,h) pass def getDisplayModes(self,obj): "Returnera en lista på visningslägen." modes=[] modes.append("Shaded") modes.append("Wireframe") return modes def getDefaultDisplayMode(self): "Returnera namnet på standardvisningsläget. Det måste definieras i getDisplayModes." return "Shaded" def setDisplayMode(self,mode): "Kartlägg det definierade visningsläget i anknytning med de som definierats i getDisplayModes. Eftersom de har samma namn så behöver inget göras. Denna metod är valfri" return mode def onChanged(self, vp, prop): "Här kan vi göra något när en enstaka egenskap har ändrats" 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): "Returnera ikonen i XPM vilken kommer att synas i trädvyn. Denna metod är valfri och om den inte definieras så visas en standardikon." 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): "När dokumentet sparas så kommer detta objekt att sparas genom användandet av Python's cPickle modul. Eftersom vi har lite o-plockbart här -- Coin sakerna -- så måste vi definiera denna metod för att returnera en tupel på alla plockbara objekt eller inget." return None def __setstate__(self,state): "När det plockade objektet återkallas från dokumentet så har vi chansen ställa in några interna saker här. Eftersom ingen data plockades, så behöver inget göras här." return None def makeBox(): FreeCAD.newDocument() a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") Box(a) ViewProviderBox(a.ViewObject)
Tillgängliga egenskaper
Egenskaper är PythonFunktion objektens sanna byggstenar. Genom dem, så kan användaren interagera och ändra objektet. Efter att ett nytt PythonFunktion objekt har skapats i ditt dokument ( a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") ), så kan du få en lista på de tillgängliga egenskaperna genom att skriva:
a.supportedProperties()
Du kommer att få en lista på tillgängliga egenskaper:
App::PropertyBool App::PropertyFloat App::PropertyFloatList App::PropertyFloatConstraint App::PropertyAngle App::PropertyDistance App::PropertyInteger App::PropertyIntegerConstraint App::PropertyPercent App::PropertyEnumeration App::PropertyIntegerList App::PropertyString App::PropertyStringList App::PropertyLink App::PropertyLinkList App::PropertyMatrix App::PropertyVector App::PropertyVectorList App::PropertyPlacement App::PropertyPlacementLink App::PropertyColor App::PropertyColorList App::PropertyMaterial App::PropertyPath App::PropertyFile App::PropertyFileIncluded Part::PropertyPartShape Part::PropertyFilletContour Part::PropertyCircle
När du adderar egenskaper till dina anpassade objekt, var uppmärksam på detta:
- Använd inte tecknen "<" eller ">" i egenskapsbeskrivningen (det förstör xml delarna i .fcstd filen)
- Egenskaper lagras i alfabetisk ordning i en .fcstd fil. Om du har en form i dina egenskaper, så kommer alla egenskaper vars namn kommer efter "Form" i alfabetisk ordning att laddas EFTER formen, vilket kan orsaka ett märkligt beteende.
Andra mer komplexa exempel
Detta exempel använder sig av Del Modulen för att skapa en oktahedron, sedan skapas dess coin representation med pivy.
Det första är själva Dokument objektet:
import FreeCAD, FreeCADGui, Part class Octahedron: def __init__(self, obj): "Lägg till lite anpassade egenskaper till vår låda" obj.addProperty("App::PropertyLength","Length","Octahedron","Oktahedronens längd").Length=1.0 obj.addProperty("App::PropertyLength","Width","Octahedron","Oktahedronens bredd").Width=1.0 obj.addProperty("App::PropertyLength","Height","Octahedron", "Oktahedronens höjd").Height=1.0 obj.addProperty("Part::PropertyPartShape","Shape","Octahedron", "Oktahedronens form") obj.Proxy = self def execute(self, fp): # Definiera sex hörn för formen 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) # skapa trådarna/ytorna 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 # hjälpmetod för att skapa ytorna def make_face(self,v1,v2,v3): wire = Part.makePolygon([v1,v2,v3,v1]) face = Part.Face(wire) return face
Sedan så har vi visarobjektet, ansvarigt för att visa objektet i 3D scenen:
class ViewProviderOctahedron: def __init__(self, obj): "Sätt detta objekt till de aktuella visarens proxy objekt " obj.addProperty("App::PropertyColor","Color","Octahedron","Oktahedronens färg").Color=(1.0,0.0,0.0) obj.Proxy = self def attach(self, obj): "Ställ in visarens scen subgraf, denna metod är obligatorisk" 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): "Om en egenskap på den hanterade funktionen har förändrats, så har vi chansen att hantera det här" # fp är den hanterade funktionen, prop är namnet på den egenskap som har förändrats 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): "Returnera en lista på visningslägen." modes=[] modes.append("Shaded") modes.append("Wireframe") return modes def getDefaultDisplayMode(self): "Returnera namnet på standardvisningsläget. Det måste definieras i getDisplayModes." return "Shaded" def setDisplayMode(self,mode): return mode def onChanged(self, vp, prop): "Här kan vi göra något när en enstaka egenskap har ändrats" 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
Slutligen, när vårt objekt och dess visningsobjekt är definierade, så behöver vi bara anropa dem:
FreeCAD.newDocument() a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron") Octahedron(a) ViewProviderOctahedron(a.ViewObject)
Göra objekt valbara
Om du vill göra ditt objekt valbart, eller åtminstone en del av den, genom att klicka på den i vyn, så måste du inkludera dess geometri inuti en SoFCSelection nod. Om ditt objekt har en komplex representation, med widgetar, annoteringar, etc, så kanske du bara vill inkludera en del av den i en SoFCSelection. Allt som är en SoFCSelection skannas konstant av FreeCAD för att detektera val/förval, så det är vettigt att inte försöka överbelasta den med onödig skanning. Detta är vad du skulle göra för att inkludera en self.face från det ovan visade exemplet:
selectionNode = coin.SoType.fromName("SoFCSelection").createInstance() selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name) selectionNode.objectName.setValue(obj.Object.Name) # här är obj ViewObject, vi behöver dess associerade App Object selectionNode.subElementName.setValue("Face") selectNode.addChild(self.face) ... self.shaded.addChild(selectionNode) self.wireframe.addChild(selectionNode)
Du skapar en SoFCSelection nod, sedan lägger du till dina geometrinoder till den, sedan lägger du till den till din huvudnod, istället för att lägga till dina geometrinoder direkt.
Arbeta med enkla former
Om ditt parametriska objektbara resulterar i en form, så behöver du inte använda ett visarobjekt. Formen kommer att visas med hjälp av FreeCAD's standard form representation:
class Line: def __init__(self, obj): "App two point properties" obj.addProperty("App::PropertyVector","p1","Line","Start point") obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(1,0,0) obj.Proxy = self def execute(self, fp): "Skriv ut ett kort meddelande när en omberäkning görs, denna metod är obligatorisk" fp.Shape = Part.makeLine(fp.p1,fp.p2) a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line") Line(a) a.ViewObject.Proxy=0 # Ställ bara in den till något annan än None (denna assignering behövs för att köra en intern notifiering)