Scripted objects/de: Difference between revisions

From FreeCAD Documentation
(Created page with "Dann haben wir das Darstellungs-Objekt,")
(Updating to match new version of source page)
(43 intermediate revisions by 6 users not shown)
Line 1: Line 1:
<languages/>
{{docnav/de|[[PySide/de|PySide]]|[[Embedding FreeCAD/de|Einbetten von FreeCAD]]}}

Außer den Standardobjektarten wie Anmerkungen, Mesh-und Teile-Objekten, bietet FreeCAD auch die erstaunliche Möglichkeit an, 100%-ige python-Skript-Objekte zu erstellen, genannt Python Features. Diese Objekte verhalten sich genau so wie jedes andere Objekt in FreeCAD und werden automatisch in Dateien gespeichert und wiederhergestellt, wenn laden / speichern ausgeführt wird.
Außer den Standardobjektarten wie Anmerkungen, Mesh-und Teile-Objekten, bietet FreeCAD auch die erstaunliche Möglichkeit an, 100%-ige python-Skript-Objekte zu erstellen, genannt Python Features. Diese Objekte verhalten sich genau so wie jedes andere Objekt in FreeCAD und werden automatisch in Dateien gespeichert und wiederhergestellt, wenn laden / speichern ausgeführt wird.


Line 8: Line 11:


== Grundlegendes Beispiel ==
== Grundlegendes Beispiel ==
Das folgende Beispiel kann in der [http://free-cad.svn.sourceforge.net/viewvc/free-cad/trunk/src/Mod/TemplatePyMod/FeaturePython.py?view=markup src/Mod/TemplatePyMod/FeaturePython.py]-Datei gefunden werden, zusammen mit einigen anderen Beispielen:
Das folgende Beispiel kann in der [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py src/Mod/TemplatePyMod/FeaturePython.py]-Datei gefunden werden, zusammen mit einigen anderen Beispielen:
{{Code|code=
<syntaxhighlight>
"Examples for a feature class and its view provider."
'''Examples for a feature class and its view provider.'''

import FreeCAD, FreeCADGui
from pivy import coin

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

" ####### "};
}}
"""

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 None
def makeBox():
FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box")
Box(a)
ViewProviderBox(a.ViewObject)
</syntaxhighlight>
== Verfügbare Eigenschaften ==
== Verfügbare Eigenschaften ==


Eigenschaften sind die wahren Bausteine von FeaturePython-Gegenständen. Durch ist der Benutzer im Stande, mit einem Objekt zu interagieren und es zu ändern. Nach dem Erstellen eines neuen FeaturePython-Objekts in Ihrem Dokument( a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") ), können eine Liste der verfügbaren Eigenschaften bekommen, indem Sie folgendes eingeben:
Eigenschaften sind die wahren Bausteine von FeaturePython-Gegenständen. Durch ist der Benutzer im Stande, mit einem Objekt zu interagieren und es zu ändern. Nach dem Erstellen eines neuen FeaturePython-Objekts in Ihrem Dokument( a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") ), können eine Liste der verfügbaren Eigenschaften bekommen, indem Sie folgendes eingeben:

<syntaxhighlight>
{{Code|code=
obj.supportedProperties()
obj.supportedProperties()
</syntaxhighlight>
}}
Sie werden eine Liste von verfügbaren Eigenschaften bekommen mit:
Sie werden eine Liste von verfügbaren Eigenschaften bekommen mit:
{{Code|code=
<syntaxhighlight>
App::PropertyBool
App::PropertyBool
App::PropertyFloat
App::PropertyBoolList
App::PropertyFloatList
App::PropertyFloat
App::PropertyFloatList
App::PropertyFloatConstraint
App::PropertyFloatConstraint
App::PropertyAngle
App::PropertyDistance
App::PropertyQuantity
App::PropertyQuantityConstraint
App::PropertyInteger
App::PropertyAngle
App::PropertyIntegerConstraint
App::PropertyPercent
App::PropertyDistance
App::PropertyLength
App::PropertyEnumeration
App::PropertySpeed
App::PropertyIntegerList
App::PropertyAcceleration
App::PropertyString
App::PropertyStringList
App::PropertyForce
App::PropertyLink
App::PropertyPressure
App::PropertyLinkList
App::PropertyInteger
App::PropertyIntegerConstraint
App::PropertyMatrix
App::PropertyVector
App::PropertyPercent
App::PropertyEnumeration
App::PropertyVectorList
App::PropertyIntegerList
App::PropertyPlacement
App::PropertyIntegerSet
App::PropertyPlacementLink
App::PropertyColor
App::PropertyMap
App::PropertyColorList
App::PropertyString
App::PropertyMaterial
App::PropertyUUID
App::PropertyPath
App::PropertyFont
App::PropertyFile
App::PropertyStringList
App::PropertyLink
App::PropertyFileIncluded
App::PropertyLinkSub
Part::PropertyPartShape
App::PropertyLinkList
Part::PropertyFilletContour
App::PropertyLinkSubList
Part::PropertyCircle
App::PropertyMatrix
</syntaxhighlight>
App::PropertyVector
App::PropertyVectorList
App::PropertyPlacement
App::PropertyPlacementLink
App::PropertyPlacementList
App::PropertyColor
App::PropertyColorList
App::PropertyMaterial
App::PropertyPath
App::PropertyFile
App::PropertyFileIncluded
App::PropertyPythonObject
Part::PropertyPartShape
Part::PropertyGeometryList
Part::PropertyShapeHistory
Part::PropertyFilletEdges
Sketcher::PropertyConstraintList
}}

Beim Hinzufügen von Eigenschaften zu benutzerdefinierten Objekte, achten Sie bitte auf folgendes:
Beim Hinzufügen von Eigenschaften zu benutzerdefinierten Objekte, achten Sie bitte auf folgendes:


Line 182: Line 209:
* Eigenschaften werden alphabetisch in einer .fcstd Datei gespeichert. Wenn Sie eine Form("Shape") in Ihren Eigenschaften haben, wird jede Eigenschaft, deren Name in alphabetischen Reihenfolge nach "Shape" kommt, auch nach der Form geladen, was zu seltsamen Verhaltensweisen führen kann.
* Eigenschaften werden alphabetisch in einer .fcstd Datei gespeichert. Wenn Sie eine Form("Shape") in Ihren Eigenschaften haben, wird jede Eigenschaft, deren Name in alphabetischen Reihenfolge nach "Shape" kommt, auch nach der Form geladen, was zu seltsamen Verhaltensweisen führen kann.


A complete list of property attributes can be seen in the [https://github.com/FreeCAD/FreeCAD/blob/master/src/App/PropertyStandard.h PropertyStandard C++ header file].
For instance, if you want to allow the user to enter only a limited range of values (e.g. using PropertyIntegerConstraint), in Python you will assign a tuple containing not only the property value, but also the lower and upper limit as well as the stepsize, as below:

{{Code|code=
prop = (value, lower, upper, stepsize)
}}


== Andere komplexere Beispiele ==
== Andere komplexere Beispiele ==
Line 187: Line 220:
Dieses Beispiel macht von [[Part Module]] Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy.
Dieses Beispiel macht von [[Part Module]] Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy.
Zuerst erstellen wir das Document-Objekt selbst:
Zuerst erstellen wir das Document-Objekt selbst:
<syntaxhighlight>
obj.setEditorMode("MyPropertyName", mode)
</syntaxhighlight>
where mode is a short int that can be set to:
0 -- default mode, read and write
1 -- read-only
2 -- hidden


{{Code|code=
obj.setEditorMode("MyPropertyName", mode)
}}

wobei der mode als short int gesetzt werden kann als:
0 -- Standardmodus, Lesen und Schreiben
1 -- Nur-Lesen
2 -- Versteckt

The EditorModes are not set at FreeCAD file reload. This could to be done by the __setstate__ function. See http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=10#p108072. By using the setEditorMode the properties are only read only in PropertyEditor. They could still be changed from python. To really make them read only the setting has to be passed directly inside the addProperty function. See http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 for an example.

Using the direct setting in the addProperty function, you also have more possibilities. In particular, an interesting one is mark a property as an output property. This way FreeCAD won't mark the feature as touched when changing it (so no need to recompute).

Example of output property (see also https://forum.freecadweb.org/viewtopic.php?t=24928):

{{Code|code=
obj.addProperty("App::PropertyString","MyCustomProperty","","",8)
}}

The property types that can be set at last parameter of the addProperty function are:
0 -- Prop_None, No special property type
1 -- Prop_ReadOnly, Property is read-only in the editor
2 -- Prop_Transient, Property won't be saved to file
4 -- Prop_Hidden, Property won't appear in the editor
8 -- Prop_Output, Modified property doesn't touch its parent container
16 -- Prop_NoRecompute, Modified property doesn't touch its container for recompute


You can find these different property types defined in the [https://github.com/FreeCAD/FreeCAD/blob/master/src/App/PropertyContainer.h source code C++ header for PropertyContainer]

<div class="mw-translate-fuzzy">
== Andere komplexere Beispiele ==
== Andere komplexere Beispiele ==


Dieses Beispiel macht von [[Part Module]] Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy.
Dieses Beispiel macht von [[Part Module]] Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy.
Zuerst erstellen wir das Document-Objekt selbst:
Zuerst erstellen wir das Document-Objekt selbst:
</div>


Dann haben wir das Darstellungs-Objekt,
Dann haben wir das Darstellungs-Objekt,
<syntaxhighlight>
import FreeCAD, FreeCADGui, Part
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
</syntaxhighlight>
Then, we have the view provider object, responsible for showing the object in the 3D scene:
<syntaxhighlight>
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
</syntaxhighlight>
Finally, once our object and its viewobject are defined, we just need to call them:
<syntaxhighlight>
FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron")
Octahedron(a)
ViewProviderOctahedron(a.ViewObject)
</syntaxhighlight>
== Making objects selectable ==
If you want to make your object selectable, or at least part of it, by clicking on it in the viewport, you must include its coin geometry inside a SoFCSelection node. If your object has complex representation, with widgets, annotations, etc, you might want to include only a part of it in a SoFCSelection. Everything that is a SoFCSelection is constantly scanned by FreeCAD to detect selection/preselection, so it makes sense try not to overload it with unneeded scanning. This is what you would do to include a self.face from the example above:
<syntaxhighlight>
selectionNode = coin.SoType.fromName("SoFCSelection").createInstance()
selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name)
selectionNode.objectName.setValue(obj.Object.Name) # here obj is the ViewObject, we need its associated App Object
selectionNode.subElementName.setValue("Face")
selectNode.addChild(self.face)
...
self.shaded.addChild(selectionNode)
self.wireframe.addChild(selectionNode)
</syntaxhighlight>
Simply, you create a SoFCSelection node, then you add your geometry nodes to it, then you add it to your main node, instead of adding your geometry nodes directly.


{{Code|code=
== Working with simple shapes ==
import FreeCAD, FreeCADGui, Part
If your parametric object simply outputs a shape, you don't need to use a view provider object. The shape will be displayed using FreeCAD's standard shape representation:
import pivy
<syntaxhighlight>
from pivy import coin
class Line:

def __init__(self, obj):
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
}}

verantwortlich für die Ansicht des Objekts in der 3D-Szene:

{{Code|code=
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
}}

Schließlich, sobald unser Objekt und sein Darstellungs-Objekt definiert sind, müssen wir sie nur noch aufrufen:

{{Code|code=
FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron")
Octahedron(a)
ViewProviderOctahedron(a.ViewObject)
}}

== Objekte wählbar machen ==

Wollen Sie Ihr Objekt wählbar machen, oder zumindest ein Teil davon, indem Sie im Editor darauf klicken, müssen Sie seine coin-Geometrie in einen SoFCSelection-Knoten enibinden. Verfügt Ihr Objekt über komplexe Darstellung, mit Widgets, Anmerkungen, etc., möchten Sie vielleicht nur einen Teil davon in einem SoFCSelection einschliessen. Alles, was ein SoFCSelection ist, wird ständig durch FreeCAD gescannt, um eine Auswahl/Vorwahl zu entdecken, der Sinn dabei ist, zu versuchen, es nicht mit unnötigen Abtastungen zu überlasten. Folgendes würden Sie tun, um einen self.face vom Beispiel oben einzuschließen:

{{Code|code=
selectionNode = coin.SoType.fromName("SoFCSelection").createInstance()
selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name)
selectionNode.objectName.setValue(obj.Object.Name) # here obj is the ViewObject, we need its associated App Object
selectionNode.subElementName.setValue("Face")
selectNode.addChild(self.face)
...
self.shaded.addChild(selectionNode)
self.wireframe.addChild(selectionNode)
}}

Sie erzeugen einfach einen SoFCSelection Knoten. Dann fügen Sie Ihre Geometrieknoten diesem hinzu und fügen dann den SoFCSelection Knoten dem Hauptknoten hinzu anstatt die Gemoetrieknoten direkt hinzuzufügen.

== Arbeiten mit einfachen Formen ==
Erstellen Sie einfach einen SoFCSelection Knoten, dann fügen Sie Ihre Geometrie-Knoten dazu hinzu, dann fügen Sie alles zu Ihrem Hauptknoten hinzu, anstatt Ihre Geometrie-Knoten direkt einzufügen. Die Form wird mittels der FreeCAD Standard-Form-Darstellung angezeigt:

{{Code|code=
import FreeCAD as App
import FreeCADGui
import FreeCAD
import Part
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):
'''"Print a short message when doing a recomputation, this method is mandatory" '''
fp.Shape = Part.makeLine(fp.p1,fp.p2)

a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
FreeCAD.ActiveDocument.recompute()
}}

Gleicher Code unter Verwendung von '''ViewProviderLine'''

{{Code|code=
import FreeCAD as App
import FreeCADGui
import FreeCAD
import Part

class Line:
def __init__(self, obj):
'''"App two point properties" '''
'''"App two point properties" '''
obj.addProperty("App::PropertyVector","p1","Line","Start point")
obj.addProperty("App::PropertyVector","p1","Line","Start point")
obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(1,0,0)
obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(100,0,0)
obj.Proxy = self
obj.Proxy = self
def execute(self, fp):
def execute(self, fp):
'''"Print a short message when doing a recomputation, this method is mandatory" '''
'''"Print a short message when doing a recomputation, this method is mandatory" '''
fp.Shape = Part.makeLine(fp.p1,fp.p2)
fp.Shape = Part.makeLine(fp.p1,fp.p2)
a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
FreeCAD.ActiveDocument.recompute()
</syntaxhighlight>


class ViewProviderLine:
{{docnav|PyQt|Embedding FreeCAD}}
def __init__(self, obj):
''' Set this object to the proxy object of the actual view provider '''
obj.Proxy = self


def getDefaultDisplayMode(self):
[[Category:Poweruser Documentation]]
''' Return the name of the default display mode. It must be defined in getDisplayModes. '''
[[Category:Python Code]]
return "Flat Lines"
<languages/>

a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
ViewProviderLine(a.ViewObject)
App.ActiveDocument.recompute()
}}

== Weitere Informationen ==
Es gibt ein paar sehr interessante Forumeinträge zu geskripteten Objekten:

- [http://forum.freecadweb.org/viewtopic.php?f=22&t=13740 Python object attributes lost at load]

- [http://forum.freecadweb.org/viewtopic.php?t=12139 New FeaturePython is grey]

- [https://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 Eigenmode frequency always 0?]

- [https://forum.freecadweb.org/viewtopic.php?f=22&t=21330 how to implement python feature's setEdit properly?]

In addition to the examples presented here have a look at FreeCAD source code [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py src/Mod/TemplatePyMod/FeaturePython.py] for more examples.

{{docnav/de|[[PySide/de|PySide]]|[[Embedding FreeCAD/de|Einbetten von FreeCAD]]}}

{{Userdocnavi/de}}

[[Category:Poweruser Documentation/de]]

[[Category:Python Code/de]]


{{clear}}

Revision as of 21:22, 10 July 2019

Außer den Standardobjektarten wie Anmerkungen, Mesh-und Teile-Objekten, bietet FreeCAD auch die erstaunliche Möglichkeit an, 100%-ige python-Skript-Objekte zu erstellen, genannt Python Features. Diese Objekte verhalten sich genau so wie jedes andere Objekt in FreeCAD und werden automatisch in Dateien gespeichert und wiederhergestellt, wenn laden / speichern ausgeführt wird.

Eine Besonderheit muss verstanden werden, jene Gegenstände werden in FreeCAD FcStd Dateien gespeichert mit python's cPickle-modul. Das Modul bildet aus einem Python-Objekt einen String, der dann einer gespeicherten Datei hinzugefügt werden kann. Beim Dateiöffnen, verwendet das cPickle Modul diese Zeichenfolge , um das ursprüngliche Objekt neu zu erstellen, vorausgesetzt, es hat den Zugriff auf den Quellcode, welcher das Objekt erstellt hat. Dies bedeutet, dass wenn Sie ein solches benutzerdefiniertes Objekt speichern und öffnen es auf einer Maschine, wo der Python-Code, der das Objekt erzeugt hat, ist nicht vorhanden, wird das Objekt nicht neu erstellt werden.

Python-Features folgen der gleichen Regeln wie alle FreeCAD Features: sie werden in App und GUI Teile getrennt. Der App Teil, das Document Object, definiert die Geometrie unseres Objektes, während der GUI-Teil, das View-Objekt, definiert, wie das Objekt auf dem Bildschirm gezeichnet wird. Das View-Objekt, ist wie jedes andere FreeCAD Feature nur verfügbar, wenn Sie FreeCAD in seiner eigenen GUI ausführen. Es gibt mehrere Eigenschaften und Methoden, die verfügbar sind, um Ihr Objekt zu erstellen. Eigenschaften muss von einem der in FreeCAD vordefinierten Typen sein, um im Eigenschafts-View-Fenster zu erscheinen, wo sie vom Benutzer editiert werden können.

Auf diese Weise sind FeaturePython-Objekte wirklich und völlig parametrisch. Sie können die Eigenschaften für den Gegenstand und sein ViewObjekt getrennt definieren.

Grundlegendes Beispiel

Das folgende Beispiel kann in der src/Mod/TemplatePyMod/FeaturePython.py-Datei gefunden werden, zusammen mit einigen anderen Beispielen:

'''Examples for a feature class and its view provider.'''

import FreeCAD, FreeCADGui
from pivy import coin

class 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 None


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

makeBox()

Verfügbare Eigenschaften

Eigenschaften sind die wahren Bausteine von FeaturePython-Gegenständen. Durch ist der Benutzer im Stande, mit einem Objekt zu interagieren und es zu ändern. Nach dem Erstellen eines neuen FeaturePython-Objekts in Ihrem Dokument( a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") ), können eine Liste der verfügbaren Eigenschaften bekommen, indem Sie folgendes eingeben:

obj.supportedProperties()

Sie werden eine Liste von verfügbaren Eigenschaften bekommen mit:

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::PropertyPlacementList
App::PropertyColor
App::PropertyColorList
App::PropertyMaterial
App::PropertyPath
App::PropertyFile
App::PropertyFileIncluded
App::PropertyPythonObject
Part::PropertyPartShape
Part::PropertyGeometryList
Part::PropertyShapeHistory
Part::PropertyFilletEdges
Sketcher::PropertyConstraintList

Beim Hinzufügen von Eigenschaften zu benutzerdefinierten Objekte, achten Sie bitte auf folgendes:

  • Verwenden Sie keine Zeichen "<" oder ">" in den Eigenschaftes-Beschreibungen (das würde den XML-Teil in der .Fcstd-Datei zerbrechen)
  • Eigenschaften werden alphabetisch in einer .fcstd Datei gespeichert. Wenn Sie eine Form("Shape") in Ihren Eigenschaften haben, wird jede Eigenschaft, deren Name in alphabetischen Reihenfolge nach "Shape" kommt, auch nach der Form geladen, was zu seltsamen Verhaltensweisen führen kann.

A complete list of property attributes can be seen in the PropertyStandard C++ header file. For instance, if you want to allow the user to enter only a limited range of values (e.g. using PropertyIntegerConstraint), in Python you will assign a tuple containing not only the property value, but also the lower and upper limit as well as the stepsize, as below:

prop = (value, lower, upper, stepsize)

Andere komplexere Beispiele

Dieses Beispiel macht von Part Module Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy. Zuerst erstellen wir das Document-Objekt selbst:

obj.setEditorMode("MyPropertyName", mode)

wobei der mode als short int gesetzt werden kann als:

0 -- Standardmodus, Lesen und Schreiben
1 -- Nur-Lesen
2 -- Versteckt

The EditorModes are not set at FreeCAD file reload. This could to be done by the __setstate__ function. See http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=10#p108072. By using the setEditorMode the properties are only read only in PropertyEditor. They could still be changed from python. To really make them read only the setting has to be passed directly inside the addProperty function. See http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 for an example.

Using the direct setting in the addProperty function, you also have more possibilities. In particular, an interesting one is mark a property as an output property. This way FreeCAD won't mark the feature as touched when changing it (so no need to recompute).

Example of output property (see also https://forum.freecadweb.org/viewtopic.php?t=24928):

obj.addProperty("App::PropertyString","MyCustomProperty","","",8)

The property types that can be set at last parameter of the addProperty function are:

 0 -- Prop_None, No special property type
 1 -- Prop_ReadOnly, Property is read-only in the editor
 2 -- Prop_Transient, Property won't be saved to file
 4 -- Prop_Hidden, Property won't appear in the editor
 8 -- Prop_Output, Modified property doesn't touch its parent container
 16 -- Prop_NoRecompute, Modified property doesn't touch its container for recompute


You can find these different property types defined in the source code C++ header for PropertyContainer

Andere komplexere Beispiele

Dieses Beispiel macht von Part Module Gebrauch, um ein Oktaeder zu schaffen, erstellt dann seine coin-Darstellung mit pivy. Zuerst erstellen wir das Document-Objekt selbst:

Dann haben wir das Darstellungs-Objekt,

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

verantwortlich für die Ansicht des Objekts in der 3D-Szene:

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

Schließlich, sobald unser Objekt und sein Darstellungs-Objekt definiert sind, müssen wir sie nur noch aufrufen:

FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron")
Octahedron(a)
ViewProviderOctahedron(a.ViewObject)

Objekte wählbar machen

Wollen Sie Ihr Objekt wählbar machen, oder zumindest ein Teil davon, indem Sie im Editor darauf klicken, müssen Sie seine coin-Geometrie in einen SoFCSelection-Knoten enibinden. Verfügt Ihr Objekt über komplexe Darstellung, mit Widgets, Anmerkungen, etc., möchten Sie vielleicht nur einen Teil davon in einem SoFCSelection einschliessen. Alles, was ein SoFCSelection ist, wird ständig durch FreeCAD gescannt, um eine Auswahl/Vorwahl zu entdecken, der Sinn dabei ist, zu versuchen, es nicht mit unnötigen Abtastungen zu überlasten. Folgendes würden Sie tun, um einen self.face vom Beispiel oben einzuschließen:

selectionNode = coin.SoType.fromName("SoFCSelection").createInstance()
selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name)
selectionNode.objectName.setValue(obj.Object.Name) # here obj is the ViewObject, we need its associated App Object
selectionNode.subElementName.setValue("Face")
selectNode.addChild(self.face)
...
self.shaded.addChild(selectionNode)
self.wireframe.addChild(selectionNode)

Sie erzeugen einfach einen SoFCSelection Knoten. Dann fügen Sie Ihre Geometrieknoten diesem hinzu und fügen dann den SoFCSelection Knoten dem Hauptknoten hinzu anstatt die Gemoetrieknoten direkt hinzuzufügen.

Arbeiten mit einfachen Formen

Erstellen Sie einfach einen SoFCSelection Knoten, dann fügen Sie Ihre Geometrie-Knoten dazu hinzu, dann fügen Sie alles zu Ihrem Hauptknoten hinzu, anstatt Ihre Geometrie-Knoten direkt einzufügen. Die Form wird mittels der FreeCAD Standard-Form-Darstellung angezeigt:

import FreeCAD as App
import FreeCADGui
import FreeCAD
import Part
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):
        '''"Print a short message when doing a recomputation, this method is mandatory" '''
        fp.Shape = Part.makeLine(fp.p1,fp.p2)

a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
FreeCAD.ActiveDocument.recompute()

Gleicher Code unter Verwendung von ViewProviderLine

import FreeCAD as App
import FreeCADGui
import FreeCAD
import Part

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(100,0,0)
         obj.Proxy = self
   
    def execute(self, fp):
        '''"Print a short message when doing a recomputation, this method is mandatory" '''
        fp.Shape = Part.makeLine(fp.p1,fp.p2)

class ViewProviderLine:
   def __init__(self, obj):
      ''' Set this object to the proxy object of the actual view provider '''
      obj.Proxy = self

   def getDefaultDisplayMode(self):
      ''' Return the name of the default display mode. It must be defined in getDisplayModes. '''
      return "Flat Lines"

a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
ViewProviderLine(a.ViewObject)
App.ActiveDocument.recompute()

Weitere Informationen

Es gibt ein paar sehr interessante Forumeinträge zu geskripteten Objekten:

- Python object attributes lost at load

- New FeaturePython is grey

- Eigenmode frequency always 0?

- how to implement python feature's setEdit properly?

In addition to the examples presented here have a look at FreeCAD source code src/Mod/TemplatePyMod/FeaturePython.py for more examples.