Scripted objects/fr: Difference between revisions
Renatorivo (talk | contribs) No edit summary |
(Updating to match new version of source page) |
||
(43 intermediate revisions by 5 users not shown) | |||
Line 1: | Line 1: | ||
<languages/> |
|||
Outre les types d'objets standards tels que les annotations, les mailles et les objets Parts, FreeCAD offre également la possibilité incroyable d'écrire des scripts d'objets 100% Python. Ces "objets" se comporteront exactement comme n'importe quels autres objets dans FreeCAD, et sont, sauvegardés et restaurés automatiquement dans le répertoire de chargement/sauvegarde.<br /> |
|||
{{docnav/fr|[[PySide/fr|PySide]]|[[Embedding FreeCAD/fr|Embedding FreeCAD]]}} |
|||
Une particularité doit être comprise, ces objets sont enregistrés dans des fichiers '''FreeCAD FcStd''' avec le module Python '''[http://docs.python.org/release/2.5/lib/module-cPickle.html cPickle]'''. Ce module transforme un objet (code) Python en une chaîne de caractères (texte), lui permettant d'être ajouté au fichier sauvegardé.<br /><br /> |
|||
Une fois chargé, le module '''cPickle''' utilise cette chaîne pour recréer l'objet d'origine, à condition qu'il ait accès au code source qui l'a créé. |
|||
Outre les types d'objets standards tels que les annotations, les mailles et les objets Parts, FreeCAD offre également la possibilité incroyable d'écrire des scripts d'objets 100% Python. Ces "objets" se comporteront exactement comme n'importe quels autres objets dans FreeCAD, et sont, sauvegardés et restaurés automatiquement dans le répertoire de chargement/sauvegarde. |
|||
Cela signifie que si vous enregistrez un tel objet personnalisé et l'ouvrez sur une machine où le code source Python qui a créé l'objet n'est pas présent, l'objet ne sera pas recréé.<br /> |
Une particularité doit être comprise, ces objets sont enregistrés dans des fichiers '''FreeCAD FcStd''' avec le module Python ''[http://docs.python.org/2/library/json.html json] '''. Ce module transforme un objet (code) Python en une chaîne de caractères (texte), lui permettant d'être ajouté au fichier sauvegardé. Une fois chargé, le module '''json''' utilise cette chaîne pour recréer l'objet d'origine, à condition qu'il ait accès au code source qui l'a créé. Cela signifie que si vous enregistrez un tel objet personnalisé et l'ouvrez sur une machine où le code source Python qui a créé l'objet n'est pas présent, l'objet ne sera pas recréé.<br /> |
||
'''Si vous distribuez ces scripts à d'autres, vous devrez aussi distribuer l'ensemble du script Python qui l'a créé.''' |
'''Si vous distribuez ces scripts à d'autres, vous devrez aussi distribuer l'ensemble du script Python qui l'a créé.''' |
||
Line 13: | Line 14: | ||
Vous pouvez paramétrer les '''propriétés''' et l'affichage '''ViewObject''' de l'objet séparément. |
Vous pouvez paramétrer les '''propriétés''' et l'affichage '''ViewObject''' de l'objet séparément. |
||
''' |
''' Astuce:''' dans les versions antérieures, nous avons utilisé le module Python [http://docs.python.org/release/2.5/lib/module-cPickle.html cPickle]. Cependant, ce module exécute du code arbitrairement et provoque ainsi des problèmes de sécurité. Alors, nous avons opté pour le module Python json. |
||
== Exemples de base == |
== Exemples de base == |
||
L'exemple suivant (portion) peut être trouvé sur la page, [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py src/Mod/TemplatePyMod/FeaturePython.py] qui inclus beaucoup d'autres exemples: |
|||
{{Code|code= |
|||
'''Examples for a feature class and its view provider.''' |
|||
import FreeCAD, FreeCADGui |
|||
L'exemple suivant (portion) peut être trouvé sur la page, [http://free-cad.svn.sourceforge.net/viewvc/free-cad/trunk/src/Mod/TemplatePyMod/FeaturePython.py?view=markup src/Mod/TemplatePyMod/FeaturePython.py] qui inclus beaucoup d'autres exemples: |
|||
from pivy import coin |
|||
<syntaxhighlight> |
|||
"Examples for a feature class and its view provider." |
|||
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) |
|||
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\''' |
|||
"#$$#$$$$$# ", |
|||
"#$$#$$$$$# ", |
|||
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 __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> |
|||
== Propriétés disponibles == |
|||
Les '''propriétés''' sont '''les bases''' des '''FeaturePython objets'''. Grâce à elles, l'utilisateur est en mesure d'interagir et de modifier son objet.<br /> |
|||
def makeBox(): |
|||
Après avoir créé un nouveau ObjetPython dans votre document '''( a = FreeCAD.ActiveDocument.addObject ("App :: FeaturePython", "Box") )''', ses propriétés sont directement accessibles, vous pouvez obtenir la liste,<br /> |
|||
FreeCAD.newDocument() |
|||
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") |
|||
Box(a) |
|||
ViewProviderBox(a.ViewObject) |
|||
makeBox() |
|||
}} |
|||
== Propriétés disponibles == |
|||
Les propriétés sont les bases des FeaturePython objets. Grâce à elles, l'utilisateur est en mesure d'interagir et de modifier son objet. Après avoir créé un nouveau ObjetPython dans votre document ( obj = FreeCAD.ActiveDocument.addObject ("App :: FeaturePython", "Box") ), ses propriétés sont directement accessibles, vous pouvez obtenir la liste,<br /> |
|||
en faisant: |
en faisant: |
||
<syntaxhighlight> |
|||
{{Code|code= |
|||
obj.supportedProperties() |
|||
obj.supportedProperties() |
|||
</syntaxhighlight> |
|||
}} |
|||
Et voici, la liste des propriétés disponibles: |
Et voici, la liste des propriétés disponibles: |
||
{{Code|code= |
|||
<syntaxhighlight> |
|||
App::PropertyBool |
|||
App::PropertyBoolList |
|||
App::PropertyFloat |
|||
App::PropertyFloatList |
|||
App::PropertyFloatConstraint |
|||
App::PropertyFloatConstraint |
|||
App::PropertyAngle |
|||
App::PropertyQuantity |
|||
App::PropertyQuantityConstraint |
|||
App::PropertyInteger |
|||
App::PropertyAngle |
|||
App::PropertyIntegerConstraint |
|||
App::PropertyDistance |
|||
App::PropertyLength |
|||
App::PropertyEnumeration |
|||
App::PropertySpeed |
|||
App::PropertyIntegerList |
|||
App::PropertyAcceleration |
|||
App::PropertyString |
|||
App::PropertyForce |
|||
App::PropertyPressure |
|||
App::PropertyInteger |
|||
App::PropertyIntegerConstraint |
|||
App::PropertyMatrix |
|||
App::PropertyPercent |
|||
App::PropertyEnumeration |
|||
App::PropertyVectorList |
|||
App::PropertyIntegerList |
|||
App::PropertyPlacement |
|||
App::PropertyIntegerSet |
|||
App::PropertyPlacementLink |
|||
App::PropertyMap |
|||
App::PropertyString |
|||
App::PropertyUUID |
|||
App::PropertyFont |
|||
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 |
|||
}} |
|||
Lors de l'ajout de propriétés à vos objets, prenez soin de ceci: |
Lors de l'ajout de propriétés à vos objets, prenez soin de ceci: |
||
* Ne pas utiliser de caractères '''"<"''' ou '''">"''' dans les descriptions des propriétés (qui coupent des portions de code dans le fichier xml.Fcstd) |
* Ne pas utiliser de caractères '''"<"''' ou '''">"''' dans les descriptions des propriétés (qui coupent des portions de code dans le fichier xml.Fcstd) |
||
* Les propriétés sont stockées dans un fichier texte |
* Les propriétés sont stockées dans un fichier texte .Fcstd. |
||
* Toutes les '''propriétés''' dont le nom vient après |
* Toutes les '''propriétés''' dont le nom vient après "Shape" sont triés dans l'ordre alphabétique, donc, si vous avez une forme dans vos propriétés, et comme les propriétés sont chargées après la forme, il peut y avoir des comportements inattendus! |
||
Une liste complète des attributs de propriété est disponible dans le [https://github.com/FreeCAD/FreeCAD/blob/master/src/App/PropertyStandard.h fichier d’en-tête PropertyStandard C++]. Par exemple, si vous souhaitez autoriser l'utilisateur à saisir uniquement une plage de valeurs limitée (par exemple, à l'aide de PropertyIntegerConstraint), vous affecterez à Python un tuple contenant non seulement la valeur de la propriété, mais également les limites inférieure et supérieure, ainsi que l'incrément, comme ci-dessous : |
|||
{{Code|code= |
|||
==Property Type== By default the properties can be updated. It is possible to make the properties read-only, for instance in the case one wants to show the result of a method. It is also possible to hide the property. The property type can be set using |
|||
prop = (value, lower, upper, stepsize) |
|||
<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 |
|||
==Property Type== |
|||
== Autres exemples plus complexes == |
|||
Par défaut, les propriétés peuvent être actualisées. Il est possible de rendre les propriétés en lecture seule, par exemple dans le cas ou l'on veut montrer le résultat d'une méthode. Il est également possible de cacher la propriété. |
|||
Le type de propriété peut être définie à l'aide |
|||
{{Code|code= |
|||
Cet exemple utilise le module [[Part Module/fr|Part Module]] pour créer un [http://fr.wikipedia.org/wiki/Octaèdre octaèdre], puis crée sa représentation '''[http://www.coin3d.org/ coin] avec pivy''' |
|||
obj.setEditorMode("MyPropertyName", mode) |
|||
}} |
|||
Mode est un '''int court''' qui peut avoir la valeur: |
|||
0 -- mode par défaut, lecture et écriture |
|||
1 -- lecture seule |
|||
2 -- caché |
|||
Les EditorModes ne sont pas fixés dans le fichier reload de FreeCAD. Cela pourrait être fait par la fonction __setstate__. Voir http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=10#p108072 En utilisant les propriétés de setEditorMode vous ne savez que lire dans PropertyEditor. Les propriétés pourraient encore être modifiées à partir d'une commande Python. Pour faire une lecture seul le réglage doit être transmis directement à la fonction d'ajout de propriété. Voir le topic http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 pour voir un exemple. |
|||
En utilisant le paramètre direct dans la fonction addProperty, vous avez également plus de possibilités. En particulier, un point intéressant est de marquer une propriété en tant que propriété en sortie. De cette façon, FreeCAD ne marquera pas la fonctionnalité comme étant touchée lors de la modification (inutile donc de recalculer). |
|||
Exemple de sortie de property (see also https://forum.freecadweb.org/viewtopic.php?t=24928): |
|||
{{Code|code= |
|||
obj.addProperty("App::PropertyString","MyCustomProperty","","",8) |
|||
}} |
|||
Les types de propriétés pouvant être définis au dernier paramètre de la fonction addProperty sont les suivants: |
|||
0 - Prop_None, pas de type de propriété spécial |
|||
1 - Prop_ReadOnly, la propriété est en lecture seule dans l'éditeur |
|||
2 - Prop_Transient, la propriété ne sera pas sauvegardée dans un fichier |
|||
4 - Prop_Hidden, la propriété n'apparaîtra pas dans l'éditeur |
|||
8 - Prop_Output, modifier la propriété ne touche pas son conteneur parent |
|||
16 - Prop_NoRecompute, modifier la propriété ne touche pas son conteneur pour le recalcul |
|||
Vous pouvez trouver ces différents types de propriétés définis dans [https://github.com/FreeCAD/FreeCAD/blob/master/src/App/PropertyContainer.h source code C++ header for PropertyContainer] |
|||
== Autres exemples plus complexes == |
|||
Cet exemple utilise le module [[Part Module/fr|Atelier Part]] pour créer un [http://fr.wikipedia.org/wiki/Octaèdre octaèdre], puis crée sa représentation '''[http://www.coin3d.org/ coin] avec pivy''' |
|||
En premier, c'est '''l'objet document''' lui-même: |
En premier, c'est '''l'objet document''' lui-même: |
||
<syntaxhighlight> |
|||
{{Code|code= |
|||
import FreeCAD, FreeCADGui, Part |
|||
import FreeCAD, FreeCADGui, Part |
|||
import pivy |
|||
class Octahedron: |
|||
from pivy import coin |
|||
def __init__(self, obj): |
|||
"Add some custom properties to our box feature" |
|||
class Octahedron: |
|||
obj.addProperty("App::PropertyLength","Length","Octahedron","Length of the octahedron").Length=1.0 |
|||
def __init__(self, obj): |
|||
obj.addProperty("App::PropertyLength","Width","Octahedron","Width of the octahedron").Width=1.0 |
|||
"Add some custom properties to our box feature" |
|||
obj.addProperty("App::PropertyLength","Height","Octahedron", "Height of the octahedron").Height=1.0 |
|||
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.Proxy = self |
|||
obj.addProperty("App::PropertyLength","Height","Octahedron", "Height of the octahedron").Height=1.0 |
|||
obj.addProperty("Part::PropertyPartShape","Shape","Octahedron", "Shape of the octahedron") |
|||
def execute(self, fp): |
|||
obj.Proxy = self |
|||
# Define six vetices for the shape |
|||
v1 = FreeCAD.Vector(0,0,0) |
|||
def execute(self, fp): |
|||
v2 = FreeCAD.Vector(fp.Length,0,0) |
|||
# Define six vetices for the shape |
|||
v3 = FreeCAD.Vector(0,fp.Width,0) |
|||
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) |
|||
# Make the wires/faces |
|||
v6 = FreeCAD.Vector(fp.Length/2,fp.Width/2,-fp.Height/2) |
|||
f1 = self.make_face(v1,v2,v5) |
|||
f2 = self.make_face(v2,v4,v5) |
|||
# Make the wires/faces |
|||
f3 = self.make_face(v4,v3,v5) |
|||
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) |
|||
shell=Part.makeShell([f1,f2,f3,f4,f5,f6,f7,f8]) |
|||
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) |
|||
# helper mehod to create the faces |
|||
fp.Shape = solid |
|||
def make_face(self,v1,v2,v3): |
|||
wire = Part.makePolygon([v1,v2,v3,v1]) |
|||
# helper mehod to create the faces |
|||
face = Part.Face(wire) |
|||
def make_face(self,v1,v2,v3): |
|||
return face |
|||
wire = Part.makePolygon([v1,v2,v3,v1]) |
|||
</syntaxhighlight> |
|||
face = Part.Face(wire) |
|||
return face |
|||
}} |
|||
Puis, nous avons '''view provider object''', qui est responsable d'afficher l'objet dans la scène 3D (votre projet à l'écran): |
Puis, nous avons '''view provider object''', qui est responsable d'afficher l'objet dans la scène 3D (votre projet à l'écran): |
||
<syntaxhighlight> |
|||
{{Code|code= |
|||
class ViewProviderOctahedron: |
|||
class ViewProviderOctahedron: |
|||
def __init__(self, obj): |
|||
def __init__(self, obj): |
|||
"Set this object to the proxy object of the actual view provider" |
|||
"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.addProperty("App::PropertyColor","Color","Octahedron","Color of the octahedron").Color=(1.0,0.0,0.0) |
|||
obj.Proxy = self |
|||
obj.Proxy = self |
|||
def attach(self, obj): |
|||
def attach(self, obj): |
|||
"Setup the scene sub-graph of the view provider, this method is mandatory" |
|||
"Setup the scene sub-graph of the view provider, this method is mandatory" |
|||
self.shaded = coin.SoGroup() |
|||
self.shaded = coin.SoGroup() |
|||
self.wireframe = coin.SoGroup() |
|||
self.scale = coin.SoScale() |
|||
self.color = coin.SoBaseColor() |
|||
self.data=coin.SoCoordinate3() |
|||
self.data=coin.SoCoordinate3() |
|||
self.face=coin.SoIndexedLineSet() |
|||
self.shaded.addChild(self.scale) |
|||
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=coin.SoDrawStyle() |
|||
style.style = coin.SoDrawStyle.LINES |
|||
self.wireframe.addChild(style) |
|||
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") |
|||
self.onChanged(obj,"Color") |
|||
def updateData(self, fp, prop): |
|||
def updateData(self, fp, prop): |
|||
"If a property of the handled feature has changed we have the chance to handle this here" |
|||
"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) |
|||
self.data.point.set1Value(cnt,i.X,i.Y,i.Z) |
|||
cnt=cnt+1 |
|||
self.face.coordIndex.set1Value(0,0) |
|||
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(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(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(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(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(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(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(28,0) |
|||
self.face.coordIndex.set1Value(29,4) |
|||
self.face.coordIndex.set1Value(30,5) |
|||
self.face.coordIndex.set1Value(31,-1) |
|||
def getDisplayModes(self,obj): |
|||
def getDisplayModes(self,obj): |
|||
"Return a list of display modes." |
|||
modes |
"Return a list of display modes." |
||
modes=[] |
|||
modes.append("Shaded") |
|||
modes.append("Wireframe") |
|||
return modes |
|||
def getDefaultDisplayMode(self): |
|||
def getDefaultDisplayMode(self): |
|||
"Return the name of the default display mode. It must be defined in getDisplayModes." |
|||
"Return the name of the default display mode. It must be defined in getDisplayModes." |
|||
return "Shaded" |
|||
return "Shaded" |
|||
def setDisplayMode(self,mode): |
|||
def setDisplayMode(self,mode): |
|||
return mode |
|||
def onChanged(self, vp, prop): |
|||
def onChanged(self, vp, prop): |
|||
"Here we can do something when a single property got changed" |
|||
"Here we can do something when a single property got changed" |
|||
FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n") |
|||
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): |
|||
def getIcon(self): |
|||
return """ |
|||
return """ |
|||
/* XPM */ |
|||
static const char * ViewProviderBox_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): |
|||
def __getstate__(self): |
|||
return None |
|||
return None |
|||
def __setstate__(self,state): |
|||
def __setstate__(self,state): |
|||
return None |
|||
return None |
|||
</syntaxhighlight> |
|||
}} |
|||
Enfin, une fois que notre objet et son '''viewobject''' sont définis, nous n'avons qu'a les appeler: |
Enfin, une fois que notre objet et son '''viewobject''' sont définis, nous n'avons qu'a les appeler: |
||
<syntaxhighlight> |
|||
{{Code|code= |
|||
FreeCAD.newDocument() |
|||
FreeCAD.newDocument() |
|||
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron") |
|||
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron") |
|||
Octahedron(a) |
|||
Octahedron(a) |
|||
ViewProviderOctahedron(a.ViewObject) |
|||
ViewProviderOctahedron(a.ViewObject) |
|||
</syntaxhighlight> |
|||
}} |
|||
== Création d'objets sélectionnables == |
== Création d'objets sélectionnables == |
||
Line 401: | Line 460: | ||
Tout ce qui est '''SoFCSelection''' est constamment "scanné" par FreeCAD pour voir s'il est sélectionné/présélectionné, il est donc logique de ne rien surcharger avec des '''scans''' inutiles.<br /><br /> |
Tout ce qui est '''SoFCSelection''' est constamment "scanné" par FreeCAD pour voir s'il est sélectionné/présélectionné, il est donc logique de ne rien surcharger avec des '''scans''' inutiles.<br /><br /> |
||
Voici un exemple de ce que vous devrez faire pour inclure un '''self.face''': |
Voici un exemple de ce que vous devrez faire pour inclure un '''self.face''': |
||
<syntaxhighlight> |
|||
{{Code|code= |
|||
selectionNode = coin.SoType.fromName("SoFCSelection").createInstance() |
|||
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) |
|||
selectNode.addChild(self.face) |
|||
... |
|||
... |
|||
self.shaded.addChild(selectionNode) |
|||
self.shaded.addChild(selectionNode) |
|||
self.wireframe.addChild(selectionNode) |
|||
</syntaxhighlight> |
|||
}} |
|||
Vous créez Simplement un '''SoFCSelection''' node (noeud), puis vous lui ajoutez vos noeuds géométriques, alors seulement vous l'ajoutez à votre noeud principal, au lieu d'ajouter vos noeuds géométriques directement. |
Vous créez Simplement un '''SoFCSelection''' node (noeud), puis vous lui ajoutez vos noeuds géométriques, alors seulement vous l'ajoutez à votre noeud principal, au lieu d'ajouter vos noeuds géométriques directement. |
||
Line 417: | Line 478: | ||
Si votre objet paramétrique renvoie simplement une forme, vous n'avez pas besoin d'utiliser un objet créateur de vue (''view provider object'').<br /> |
Si votre objet paramétrique renvoie simplement une forme, vous n'avez pas besoin d'utiliser un objet créateur de vue (''view provider object'').<br /> |
||
La forme sera affichée à l'aide du module standard de représentation des formes de FreeCAD: |
La forme sera affichée à l'aide du module standard de représentation des formes de FreeCAD: |
||
<syntaxhighlight> |
|||
{{Code|code= |
|||
class Line: |
|||
import FreeCAD as App |
|||
def __init__(self, obj): |
|||
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() |
|||
}} |
|||
Même code en utilisant '''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( |
obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(100,0,0) |
||
obj.Proxy = self |
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: |
|||
a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line") |
|||
def __init__(self, obj): |
|||
Line(a) |
|||
''' Set this object to the proxy object of the actual view provider ''' |
|||
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification) |
|||
obj.Proxy = self |
|||
FreeCAD.ActiveDocument.recompute() |
|||
</syntaxhighlight> |
|||
def getDefaultDisplayMode(self): |
|||
{{docnav/fr|PyQt/fr|Embedding FreeCAD/fr}} |
|||
''' 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() |
|||
}} |
|||
==Plus d'informations== |
|||
Quelques discussions intéressantes sur le forum à propos des objets scriptés: |
|||
- [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?] |
|||
En plus de ces exemples, vous pouvez voir dans le code source de FreeCAD [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py src/Mod/TemplatePyMod/FeaturePython.py] pour plus d'exemples. |
|||
{{docnav/fr|[[PySide/fr|PySide]]|[[Embedding FreeCAD/fr|Embedding FreeCAD]]}} |
|||
{{Userdocnavi/fr}} |
|||
[[Category:Poweruser Documentation/fr]] |
[[Category:Poweruser Documentation/fr]] |
||
[[Category:Python Code/fr]] |
[[Category:Python Code/fr]] |
||
{{clear}} |
{{clear}} |
||
<languages/> |
Revision as of 21:22, 10 July 2019
Outre les types d'objets standards tels que les annotations, les mailles et les objets Parts, FreeCAD offre également la possibilité incroyable d'écrire des scripts d'objets 100% Python. Ces "objets" se comporteront exactement comme n'importe quels autres objets dans FreeCAD, et sont, sauvegardés et restaurés automatiquement dans le répertoire de chargement/sauvegarde.
Une particularité doit être comprise, ces objets sont enregistrés dans des fichiers FreeCAD FcStd' avec le module Python json . Ce module transforme un objet (code) Python en une chaîne de caractères (texte), lui permettant d'être ajouté au fichier sauvegardé. Une fois chargé, le module json utilise cette chaîne pour recréer l'objet d'origine, à condition qu'il ait accès au code source qui l'a créé. Cela signifie que si vous enregistrez un tel objet personnalisé et l'ouvrez sur une machine où le code source Python qui a créé l'objet n'est pas présent, l'objet ne sera pas recréé.
Si vous distribuez ces scripts à d'autres, vous devrez aussi distribuer l'ensemble du script Python qui l'a créé.
Les fonctionnalités de Python suivent les mêmes règles que toutes les fonctionnalités de FreeCAD: ils sont séparés en plusieurs parties celle App (application) et GUI parts (interface graphique).
La partie Object App (application), définit la forme géométrique de notre objet, tandis que la partie graphique (GUI), définit la façon dont l'objet sera affiché à l'écran.
L'outil View Provider Object (créateur de vue), comme toutes les fonctions FreeCAD, n'est disponible que lorsque vous exécutez FreeCAD dans son interface (GUI).
Il ya plusieurs manières et méthodes disponibles pour créer votre projet. Les méthodes utilisées doivent êtres une des méthodes prédéfinies que vous fourni FreeCAD, et apparaîtra dans la fenêtre Propriété, afin qu'ils puissent être modifiés par l'utilisateur (onglet Données).
De cette manière, les objets sont FeaturePython (ont toutes les propriétés de Python) et sont totalements paramétriques.
Vous pouvez paramétrer les propriétés et l'affichage ViewObject de l'objet séparément.
Astuce: dans les versions antérieures, nous avons utilisé le module Python cPickle. Cependant, ce module exécute du code arbitrairement et provoque ainsi des problèmes de sécurité. Alors, nous avons opté pour le module Python json.
Exemples de base
L'exemple suivant (portion) peut être trouvé sur la page, src/Mod/TemplatePyMod/FeaturePython.py qui inclus beaucoup d'autres exemples:
'''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()
Propriétés disponibles
Les propriétés sont les bases des FeaturePython objets. Grâce à elles, l'utilisateur est en mesure d'interagir et de modifier son objet. Après avoir créé un nouveau ObjetPython dans votre document ( obj = FreeCAD.ActiveDocument.addObject ("App :: FeaturePython", "Box") ), ses propriétés sont directement accessibles, vous pouvez obtenir la liste,
en faisant:
obj.supportedProperties()
Et voici, la liste des propriétés disponibles:
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
Lors de l'ajout de propriétés à vos objets, prenez soin de ceci:
- Ne pas utiliser de caractères "<" ou ">" dans les descriptions des propriétés (qui coupent des portions de code dans le fichier xml.Fcstd)
- Les propriétés sont stockées dans un fichier texte .Fcstd.
- Toutes les propriétés dont le nom vient après "Shape" sont triés dans l'ordre alphabétique, donc, si vous avez une forme dans vos propriétés, et comme les propriétés sont chargées après la forme, il peut y avoir des comportements inattendus!
Une liste complète des attributs de propriété est disponible dans le fichier d’en-tête PropertyStandard C++. Par exemple, si vous souhaitez autoriser l'utilisateur à saisir uniquement une plage de valeurs limitée (par exemple, à l'aide de PropertyIntegerConstraint), vous affecterez à Python un tuple contenant non seulement la valeur de la propriété, mais également les limites inférieure et supérieure, ainsi que l'incrément, comme ci-dessous :
prop = (value, lower, upper, stepsize)
Property Type
Par défaut, les propriétés peuvent être actualisées. Il est possible de rendre les propriétés en lecture seule, par exemple dans le cas ou l'on veut montrer le résultat d'une méthode. Il est également possible de cacher la propriété. Le type de propriété peut être définie à l'aide
obj.setEditorMode("MyPropertyName", mode)
Mode est un int court qui peut avoir la valeur: 0 -- mode par défaut, lecture et écriture 1 -- lecture seule 2 -- caché
Les EditorModes ne sont pas fixés dans le fichier reload de FreeCAD. Cela pourrait être fait par la fonction __setstate__. Voir http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=10#p108072 En utilisant les propriétés de setEditorMode vous ne savez que lire dans PropertyEditor. Les propriétés pourraient encore être modifiées à partir d'une commande Python. Pour faire une lecture seul le réglage doit être transmis directement à la fonction d'ajout de propriété. Voir le topic http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 pour voir un exemple.
En utilisant le paramètre direct dans la fonction addProperty, vous avez également plus de possibilités. En particulier, un point intéressant est de marquer une propriété en tant que propriété en sortie. De cette façon, FreeCAD ne marquera pas la fonctionnalité comme étant touchée lors de la modification (inutile donc de recalculer).
Exemple de sortie de property (see also https://forum.freecadweb.org/viewtopic.php?t=24928):
obj.addProperty("App::PropertyString","MyCustomProperty","","",8)
Les types de propriétés pouvant être définis au dernier paramètre de la fonction addProperty sont les suivants:
0 - Prop_None, pas de type de propriété spécial 1 - Prop_ReadOnly, la propriété est en lecture seule dans l'éditeur 2 - Prop_Transient, la propriété ne sera pas sauvegardée dans un fichier 4 - Prop_Hidden, la propriété n'apparaîtra pas dans l'éditeur 8 - Prop_Output, modifier la propriété ne touche pas son conteneur parent 16 - Prop_NoRecompute, modifier la propriété ne touche pas son conteneur pour le recalcul
Vous pouvez trouver ces différents types de propriétés définis dans source code C++ header for PropertyContainer
Autres exemples plus complexes
Cet exemple utilise le module Atelier Part pour créer un octaèdre, puis crée sa représentation coin avec pivy
En premier, c'est l'objet document lui-même:
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
Puis, nous avons view provider object, qui est responsable d'afficher l'objet dans la scène 3D (votre projet à l'écran):
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
Enfin, une fois que notre objet et son viewobject sont définis, nous n'avons qu'a les appeler:
FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron")
Octahedron(a)
ViewProviderOctahedron(a.ViewObject)
Création d'objets sélectionnables
Si vous voulez travailler sur un objet sélectionné, ou du moins une partie de celui-ci, vous cliquez sur l'objet dans la fenêtre, vous devez inclure la forme géométrique à l'intérieur d'un noeud SoFCSelection node.
Si votre objet a une représentation complexe, avec des widgets, des annotations, etc, vous pouvez n'inclure qu'une partie de celui-ci dans un SoFCSelection.
Tout ce qui est SoFCSelection est constamment "scanné" par FreeCAD pour voir s'il est sélectionné/présélectionné, il est donc logique de ne rien surcharger avec des scans inutiles.
Voici un exemple de ce que vous devrez faire pour inclure un self.face:
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)
Vous créez Simplement un SoFCSelection node (noeud), puis vous lui ajoutez vos noeuds géométriques, alors seulement vous l'ajoutez à votre noeud principal, au lieu d'ajouter vos noeuds géométriques directement.
Travailler avec des formes simples
Si votre objet paramétrique renvoie simplement une forme, vous n'avez pas besoin d'utiliser un objet créateur de vue (view provider object).
La forme sera affichée à l'aide du module standard de représentation des formes de FreeCAD:
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()
Même code en utilisant 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()
Plus d'informations
Quelques discussions intéressantes sur le forum à propos des objets scriptés:
- Python object attributes lost at load
- Eigenmode frequency always 0?
- how to implement python feature's setEdit properly?
En plus de ces exemples, vous pouvez voir dans le code source de FreeCAD src/Mod/TemplatePyMod/FeaturePython.py pour plus d'exemples.
- Démarrer avec FreeCAD
- Installation : Téléchargements, Windows, Linux, Mac, Logiciels supplémentaires, Docker, AppImage, Ubuntu Snap
- Bases : À propos de FreeCAD, Interface, Navigation par la souris, Méthodes de sélection, Objet name, Préférences, Ateliers, Structure du document, Propriétés, Contribuer à FreeCAD, Faire un don
- Aide : Tutoriels, Tutoriels vidéo
- Ateliers : Std Base, Arch, Assembly, CAM, Draft, FEM, Inspection, Mesh, OpenSCAD, Part, PartDesign, Points, Reverse Engineering, Robot, Sketcher, Spreadsheet, Start, Surface, TechDraw, Test, Web