Create a FeaturePython object part II/fr: Difference between revisions
No edit summary |
(Updating to match new version of source page) |
||
(48 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
<languages/> |
<languages/> |
||
<div class="mw-translate-fuzzy"> |
|||
{{docnav/fr |
{{docnav/fr |
||
|[[FeaturePython Objects/fr|Objects FeaturePython]] |
|[[FeaturePython Objects/fr|Objects FeaturePython]] |
||
|[[Scripting examples/fr|Exemples de Scripts]] |
|[[Scripting examples/fr|Exemples de Scripts]] |
||
}} |
}} |
||
</div> |
|||
{{TOCright}} |
|||
<div class="mw-translate-fuzzy"> |
|||
=== App::FeaturePython vs. Part::FeaturePython === |
=== App::FeaturePython vs. Part::FeaturePython === |
||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
Jusqu'à présent, nous nous sommes concentrés sur les aspects internes d'une classe Python construite autour d'un objet FeaturePython - en particulier, un objet App::FeaturePython object.<br> |
Jusqu'à présent, nous nous sommes concentrés sur les aspects internes d'une classe Python construite autour d'un objet FeaturePython - en particulier, un objet App::FeaturePython object.<br> |
||
Nous avons créé l'objet, défini certaines propriétés et ajouté des rappels d'événements au niveau du document qui permettent à notre objet de répondre à un recalcul de document avec la méthode {{incode|execute()}}. |
Nous avons créé l'objet, défini certaines propriétés et ajouté des rappels d'événements au niveau du document qui permettent à notre objet de répondre à un recalcul de document avec la méthode {{incode|execute()}}. |
||
</div> |
|||
==Adding a box== |
|||
<div class="mw-translate-fuzzy"> |
|||
'''Mais nous n'avons toujours pas de boîte.''' |
'''Mais nous n'avons toujours pas de boîte.''' |
||
</div> |
|||
{{Code|code= |
|||
Cependant, nous pouvons facilement en créer une en ajoutant seulement deux lignes. |
|||
import Part |
|||
}} |
|||
Then in {{incode|execute()}} delete the {{incode|print()}} statement and add the following line in its place: |
|||
Tout d'abord, ajoutez une nouvelle importation en haut du fichier: {{incode|import Part}} |
|||
Ensuite, dans {{incode|execute()}}, ajoutez la ligne suivante (vous pouvez supprimer l'instruction {{incode|print()}}): |
|||
{{Code|code= |
{{Code|code= |
||
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height)) |
|||
def execute(): |
|||
""" |
|||
Called on document recompute |
|||
""" |
|||
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height) |
|||
}} |
}} |
||
[[Image:App_featurepython_box.png | right]] |
|||
<div class="mw-translate-fuzzy"> |
|||
Ces commandes exécutent des scripts python fournis avec FreeCAD par défaut. |
Ces commandes exécutent des scripts python fournis avec FreeCAD par défaut. |
||
* La méthode {{incode|makeBox()}} génère une nouvelle forme de boîte. |
* La méthode {{incode|makeBox()}} génère une nouvelle forme de boîte. |
||
* L'appel englobant à {{incode|Part.show()}} ajoute la forme à l'arborescence du document et la rend visible. |
* L'appel englobant à {{incode|Part.show()}} ajoute la forme à l'arborescence du document et la rend visible. |
||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
Si FreeCAD est ouvert, rechargez le module boîte et créez un nouvel objet boîte en utilisant {{incode|box.create()}} (supprimez tous les objets boîte existants, juste pour garder les choses propres). |
Si FreeCAD est ouvert, rechargez le module boîte et créez un nouvel objet boîte en utilisant {{incode|box.create()}} (supprimez tous les objets boîte existants, juste pour garder les choses propres). |
||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
Notice how a box immediately appears on the screen. that's because the execute method is called immediately after the box is created, because we force the document to recompute at the end of {{incode|box.create()}} |
|||
Cela devrait être assez évident. La boîte elle-même est représentée par un objet entièrement différent de notre objet FeaturePython. La raison en est que {{incode|Part.show()}} crée un objet boîte séparé et l'ajoute au document. En fait, si vous accédez à votre objet FeaturePython et modifiez les dimensions, vous verrez une autre forme de boîte créée et l'ancienne laissée en place. Ce n'est pas bon! De plus, si la vue du rapport est ouverte, vous pouvez remarquer une erreur indiquant 'Les recalculs imbriqués d'un document ne sont pas autorisés'. Cela a à voir avec l'utilisation de la méthode Part.show() dans un objet FeaturePython. Nous voulons éviter de faire cela. |
|||
</div> |
|||
[[#top|top]] |
|||
==Fixing the code== |
|||
[[File:App_featurepython_box.png]] |
|||
<div class="mw-translate-fuzzy"> |
|||
'''But there's a problem.''' |
|||
'''Alors, comment pouvons-nous résoudre ce problème?''' |
|||
</div> |
|||
{{Code|code= |
|||
It should be pretty obvious. The box itself is represented by an entirely different object than our FeaturePython object. The reason is because {{incode|Part.show()}} creates a separate box object and adds it to the document. In fact, if you go to your FeaturePython object and change the dimensions, you'll see another box shape gets created and the old one is left in place. That's not good! Additionally, if you have the Report View open, you may notice an error stating 'Nested recomputes of a document are not allowed". This has to do with using the Part.show() method inside a FeaturePython object. We want to avoid doing that. |
|||
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name) |
|||
}} |
|||
<div class="mw-translate-fuzzy"> |
|||
'''Tip''' |
|||
pour lire: |
|||
</div> |
|||
{{Code|code= |
|||
The problem is, we're relying on the {{incode|Part.make*()}} methods, which only generate non-parametric Part::Feature objects (simple, dumb shapes), just as you'd get if you copied a parametric object using [[Part SimpleCopy|Part Simple Copy]]. |
|||
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name) |
|||
}} |
|||
<div class="mw-translate-fuzzy"> |
|||
What we want, of course, is a '''parametric''' box object that resizes the existing box as we change it's properties. We could delete the previous Part::Feature object and regenerate it every time we change a property, but we still have two objects to manage - our custom FeaturePython object and the Part::Feature object it generates. |
|||
Pour terminer les modifications dont nous avons besoin, la ligne suivante de la méthode {{incode|execute()}} doit être modifiée: |
|||
</div> |
|||
{{Code|code= |
|||
'''So how do we solve this problem?''' |
|||
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height)) |
|||
}} |
|||
en: |
|||
First, we need to use the right type of object. |
|||
{{Code|code= |
|||
To this point we've been using {{incode|App::FeaturePython}} objects. They're great, but they're not intended for use as geometry objects. Rather, they are better used as document objects which do not require a visual representation in the 3D view. So we need to use a {{incode|Part::FeaturePython}} object ibstead. |
|||
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height) |
|||
}} |
|||
In {{incode|create()}}, change the following line: |
|||
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name) |
|||
to read: |
|||
obj = App.ActiveDocument.addObject('{{ColoredText||red|Part::FeaturePython}}', obj_name) |
|||
To finish making the changes we need, the following line in the {{incode|execute()}} method needs to be changed: |
|||
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height)) |
|||
to: |
|||
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height) |
|||
Your code should look like this: |
|||
import FreeCAD as App |
|||
import Part |
|||
def create(obj_name): |
|||
""" |
|||
Object creation method |
|||
""" |
|||
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name) |
|||
fpo = box(obj) |
|||
App.ActiveDocument.recompute() |
|||
return fpo |
|||
class box(): |
|||
def __init__(self, obj): |
|||
""" |
|||
Default Constructor |
|||
""" |
|||
self.Type = 'box' |
|||
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = "" |
|||
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0 |
|||
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm' |
|||
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm' |
|||
obj.Proxy = self |
|||
self.Object = obj |
|||
def execute(self, obj): |
|||
""" |
|||
Called on document recompute |
|||
""" |
|||
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height) |
|||
[[File:Part_featurepython_no_vp.png|right]] |
[[File:Part_featurepython_no_vp.png|right]] |
||
<div class="mw-translate-fuzzy"> |
|||
Now, save your changes and switch back to FreeCAD. Delete any existing objects, reload the box module, and create a new box object. |
|||
Maintenant, enregistrez vos modifications et revenez à FreeCAD. Supprimez tous les objets existants, rechargez le module de boîte et créez un nouvel objet de boîte. |
|||
</div> |
|||
[[#top|top]] |
|||
The results may seem a bit mixed. The icon in the treeview is different - it's a box, now. But there's no cube. And the icon is still gray! |
|||
<div class="mw-translate-fuzzy"> |
|||
What happened? Although we've properly created our box shape and assigned it to a {{incode|Part::FeaturePython}} object, before we can make it show up in our 3D view, we need to assign a '''ViewProvider.''' |
|||
=== Écrire un ViewProvider === |
|||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
=== Writing a ViewProvider === |
|||
Un fournisseur de vues est le composant d'un objet qui lui permet d'avoir une représentation visuelle dans l'interface graphique - en particulier dans la vue 3D. FreeCAD utilise une structure d'application appelée 'vue du modèle' qui est conçue pour séparer les données (le 'modèle') de sa représentation visuelle (la «vue»). Si vous avez passé du temps à travailler avec FreeCAD en Python, vous en serez probablement déjà conscient grâce à l'utilisation de deux modules Python principaux: FreeCAD et FreeCADGui (souvent aliasés respectivement «App» et «Gui»). |
|||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
A View Provider is the component of an object which allows it to have visual representation in the GUI - specifically in the 3D view. FreeCAD uses an application structure known as 'model-view', which is designed to separate the data (the 'model') from it's visual representation (the 'view'). If you've spent any time working with FreeCAD in Python, you'll likely already be aware of this through the use of two core Python modules: FreeCAD and FreeCADGui (often aliased as 'App' and 'Gui' repectively). |
|||
Ainsi, notre implémentation de FeaturePython Box nécessite également ces éléments. Jusqu'à présent, nous nous sommes concentrés uniquement sur la partie «modèle», il est donc temps d'écrire la «vue». Heureusement, la plupart des implémentations de vues sont simples et nécessitent peu d'efforts pour écrire, au moins pour commencer. Voici un exemple de ViewProvider, emprunté et légèrement modifié de [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py] |
|||
</div> |
|||
{{Code|code= |
|||
Thus, our FeaturePython Box implementation also requires these elements. Thus far, we've focused purely on the 'model' portion, so now it's time to write the 'view'. Fortunately, most view implementations are simple and require little effort to write, at least to get started. Here's an example ViewProvider, borrowed and slightly modified from [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py] |
|||
class ViewProviderBox: |
|||
class ViewProviderBox: |
|||
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.Proxy = self |
obj.Proxy = self |
||
def attach(self, obj): |
def attach(self, obj): |
||
""" |
""" |
||
Line 140: | Line 120: | ||
""" |
""" |
||
return |
return |
||
def updateData(self, fp, prop): |
def updateData(self, fp, prop): |
||
""" |
""" |
||
Line 146: | Line 126: | ||
""" |
""" |
||
return |
return |
||
def getDisplayModes(self,obj): |
def getDisplayModes(self,obj): |
||
""" |
""" |
||
Return a list of display modes. |
Return a list of display modes. |
||
""" |
""" |
||
return |
return [] |
||
def getDefaultDisplayMode(self): |
def getDefaultDisplayMode(self): |
||
""" |
""" |
||
Line 158: | Line 138: | ||
""" |
""" |
||
return "Shaded" |
return "Shaded" |
||
def setDisplayMode(self,mode): |
def setDisplayMode(self,mode): |
||
""" |
""" |
||
Line 166: | Line 146: | ||
""" |
""" |
||
return mode |
return mode |
||
def onChanged(self, vp, prop): |
def onChanged(self, vp, prop): |
||
""" |
""" |
||
Print the name of the property that has changed |
Print the name of the property that has changed |
||
""" |
""" |
||
App.Console.PrintMessage("Change property: " + str(prop) + "\n") |
App.Console.PrintMessage("Change property: " + str(prop) + "\n") |
||
def getIcon(self): |
def getIcon(self): |
||
""" |
""" |
||
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown. |
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown. |
||
""" |
""" |
||
return """ |
return """ |
||
/* XPM */ |
/* XPM */ |
||
static const char * ViewProviderBox_xpm[] = { |
static const char * ViewProviderBox_xpm[] = { |
||
"16 16 6 1", |
"16 16 6 1", |
||
" |
" c None", |
||
". |
". c #141010", |
||
"+ |
"+ c #615BD2", |
||
"@ |
"@ c #C39D55", |
||
"# |
"# c #000000", |
||
"$ |
"$ c #57C355", |
||
" ........", |
" ........", |
||
" ......++..+..", |
" ......++..+..", |
||
Line 206: | Line 186: | ||
" ####### "}; |
" ####### "}; |
||
""" |
""" |
||
def __getstate__(self): |
def __getstate__(self): |
||
""" |
""" |
||
Line 212: | Line 192: | ||
""" |
""" |
||
return None |
return None |
||
def __setstate__(self,state): |
def __setstate__(self,state): |
||
""" |
|||
Called during document restore. |
Called during document restore. |
||
""" |
|||
return None |
return None |
||
}} |
|||
Note in the above code, we also define an XMP icon for this object. Icon design is beyond the scope of this tutorial, but basic icon designs can be managed using open source tools like [https://www.gimp.org GIMP], [https://krita.org/en/ Krita], and [https://inkscape.org/ Inkscape]. The getIcon method is optional, as well. If it is not provided, FreeCAD will provide a default icon. |
|||
<div class="mw-translate-fuzzy"> |
|||
With out ViewProvider defined, we now need to put it to use to give our object the gift of visualization. |
|||
Remarquez dans le code ci-dessus, nous définissons également une icône XMP pour cet objet. La conception d'icônes dépasse le cadre de ce didacticiel, mais les conceptions d'icônes de base peuvent être gérées à l'aide d'outils open source tels que [https://www.gimp.org GIMP], [https://krita.org/fr/ Krita] et [https://inkscape.org/ Inkscape]. La méthode getIcon est également facultative. S'il n'est pas fourni, FreeCAD fournira une icône par défaut. |
|||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
Return to the {{incode|create()}} method in your code and add the following near the end: |
|||
Sans un ViewProvider défini, nous devons maintenant l'utiliser pour donner à notre objet la possibilité de la visualisation. |
|||
</div> |
|||
{{Code|code= |
|||
ViewProviderBox(obj.ViewObject) |
ViewProviderBox(obj.ViewObject) |
||
}} |
|||
<div class="mw-translate-fuzzy"> |
|||
This instances the custom ViewProvider class and passes the FeaturePython's built-in ViewObject to it. The ViewObject won't do anything without our custom class implementation, so when the ViewProvider class initializes, it saves a reference to itself in the FeaturePython's ViewObject.Proxy attribute. This way, when FreeCAD needs to render our Box visually, it can find the ViewProvider class to do that. |
|||
Cette instance de la classe ViewProvider personnalisée lui transmet le ViewObject intégré de FeaturePython. Le ViewObject ne fera rien sans notre implémentation de la classe personnalisée donc, quand la classe ViewProvider s'initialise, elle enregistre une référence à elle-même dans l'attribut ViewObject.Proxy de FeaturePython. De cette façon, lorsque FreeCAD a besoin de rendre notre Box visuellement, il peut trouver la classe ViewProvider pour le faire. |
|||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
Now, save the changes and return to FreeCAD. Import or reload the Box module and call {{incode|box.create()}}. |
|||
Maintenant, enregistrez les modifications et revenez à FreeCAD. Importez ou rechargez le module Box et appelez {{incode|box.create()}}. |
|||
</div> |
|||
[[#top|top]] |
|||
We still don't see anything, but notice what happened to the icon next to the box object. It's in color! And it has a shape! That's a clue that our ViewProvider is working as expected. |
|||
<div class="mw-translate-fuzzy"> |
|||
So now, it's time to actually *add* a box. |
|||
=== Piégeage d'événements === |
|||
</div> |
|||
<div class="mw-translate-fuzzy"> |
|||
Return to the code and add the following line to the execute() method: |
|||
Jusqu'à présent, nous n'avons pas abordé explicitement le piégeage d'événements. Presque toutes les méthodes d'une classe FeaturePython servent de rappel accessible à l'objet FeaturePython (qui obtient l'accès à notre instance de classe via l'attribut {{incode|Proxy}}, si vous vous en souvenez). |
|||
</div> |
|||
Voici une liste des rappels pouvant être implémentés dans l'objet FeaturePython de base: |
|||
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height) |
|||
{| class="wikitable" cellpadding="5px" width="100%" |
|||
Now save, reload the box module in FreeCAD and create a new box. |
|||
|+ FeaturePython basic callbacks |
|||
|style="width:25%" | {{incode|execute(self, obj)}} |
|||
You should see a box appear on screen! If it's too big (or maybe doesn't show up at all), click one of the 'ViewFit' buttons to fit it to the 3D view. |
|||
|style="width:25%" | Called during document recomputes |
|||
|style="width:50%" | Do not call {{incode|recompute()}} from this method (or any method called from {{incode|execute()}}) as this causes a nested recompute. |
|||
Note what we did here that differs from the way it was implemented for App::FeaturePython above. In the {{incode|execute()}} method, we called the {{incode|Part.makeBox()}} macro and passed it the box properties as before. But rather than call {{incode|Part.show()}} on the resulting object (which created a new, separate box object), we simply assigned it to the {{incode|Shape}} property of our Part::FeaturePython object instead. |
|||
You can even alter the box dimensions by changing the values in the FreeCAD property panel. Give it a try! |
|||
=== Trapping Events === |
|||
To this point, we haven't explicitly addressed event trapping. Nearly every method of a FeaturePython class serves as a callback accessible to the FeaturePython object (which gets access to our class instance through the {{incode|Proxy}} attribute, if you recall). |
|||
Below is a list of the callbacks that may be implemented in the basic FeaturePython object: |
|||
{| class="wikitable" |
|||
|+style="caption-side:bottom; | FeaturePython basic callbacks |
|||
|{{incode|execute(self, obj)}} || Called during document recomputes || Do not call {{incode|recompute()}} from this method (or any method called from {{incode|execute()}}) as this causes a nested recompute. |
|||
|- |
|- |
||
| {{incode|onBeforeChanged(self, obj, prop)}} |
|||
|{{incode|onBeforeChanged(self, obj, prop)}} || Called before a property value is changed || {{incode|prop}} is the name of the property to be changed, not the property object itself. Property changes cannot be cancelled. Previous / next property values are not simultaneously available for comparison. |
|||
| Called before a property value is changed |
|||
| {{incode|prop}} is the name of the property to be changed, not the property object itself. Property changes cannot be cancelled. Previous / next property values are not simultaneously available for comparison. |
|||
|- |
|- |
||
|{{incode|onChanged(self, obj, prop)}} |
| {{incode|onChanged(self, obj, prop)}} |
||
| Called after a property is changed |
|||
| {{incode|prop}} is the name of the property to be changed, not the property object itself. |
|||
|- |
|- |
||
| {{Incode|onDocumentRestored(self, obj)}} |
|||
|{{Incode|onDocumentRestored(self, obj)}} || Called after a document is restored or aFeaturePython object is copied / duplicated. || Occasionally, references to the FeaturePython object from the class, or the class from the FeaturePython object may be broken, as the class {{incode|__init__()}} method is not called when the object is reconstructed. Adding {{incode|self.Object = obj}} or {{incode|obj.Proxy = self}} often solves these issues. |
|||
| Called after a document is restored or a FeaturePython object is copied. |
|||
| Occasionally, references to the FeaturePython object from the class, or the class from the FeaturePython object may be broken, as the class {{incode|__init__()}} method is not called when the object is reconstructed. Adding {{incode|self.Object <nowiki>=</nowiki> obj}} or {{incode|obj.Proxy <nowiki>=</nowiki> self}} often solves these issues. |
|||
|} |
|} |
||
En outre, il existe deux rappels dans la classe ViewProvider qui peuvent parfois s'avérer utiles: |
|||
In addition, there are two callbacks in the ViewProvider class that may occasionally prove useful: |
|||
{| class="wikitable" |
{| class="wikitable" cellpadding="5px" width="100%" |
||
|+ ViewProvider basic callbacks |
|||
|+style="caption-side:bottom; | ViewProvider basic callbacks |
|||
|- |
|- |
||
|style="width:25%" | {{incode|updateData(self, obj, prop)}} |
|||
|{{incode|updateData(self, obj, prop)}} || Called after a data (model) property is changed || {{incode|obj}} is a reference to the FeaturePython class instance, not the ViewProvider instance. {{incode|prop}} is the name of the property to be changed, not the property object itself. |
|||
|style="width:25%" | Called after a data (model) property is changed |
|||
|style="width:50%" | {{incode|obj}} is a reference to the FeaturePython class instance, not the ViewProvider instance. {{incode|prop}} is the name of the property to be changed, not the property object itself. |
|||
|- |
|- |
||
| {{incode|onChanged(self, vobj, prop)}} |
|||
|{{incode|onChanged(self, vobj, prop)}} || Called after a view property is changed || {{incode|vobj}} is a reference to the ViewProvider instance. {{incode|prop}} is the name of the view property which was changed. |
|||
| Called after a view property is changed |
|||
| {{incode|vobj}} is a reference to the ViewProvider instance. {{incode|prop}} is the name of the view property which was changed. |
|||
|} |
|} |
||
<div class="mw-translate-fuzzy"> |
|||
{| class="wikitable" |
|||
!{{ColoredParagraph|Tip}} |
|||
|- |
|- |
||
| |
|Il n'est pas rare de rencontrer une situation où les rappels Python ne sont pas déclenchés comme ils le devraient. Les débutants dans ce domaine doivent être assurés que le système de rappel FeaturePython n'est pas fragile ou cassé. Invariablement, lorsque les rappels ne s'exécutent pas, c'est parce qu'une référence est perdue ou indéfinie dans le code sous-jacent. Si, cependant, les rappels semblent rompre sans explication, la fourniture de références d'objet / proxy dans le rappel {{incode|onDocumentRestored()}} (comme indiqué dans le premier tableau ci-dessus) peut atténuer ces problèmes. Jusqu'à ce que vous soyez à l'aise avec le système de rappel, il peut être utile d'ajouter des instructions d'impression dans chaque rappel pour imprimer des messages sur la console comme diagnostic pendant le développement. |
||
</div> |
|||
[[#top|top]] |
|||
|} |
|||
<div class="mw-translate-fuzzy"> |
|||
=== The Code === |
|||
=== Le code === |
|||
</div> |
|||
{{Code|code= |
|||
The complete code for this example: |
|||
import FreeCAD as App |
|||
import Part |
|||
def create(obj_name): |
|||
import FreeCAD as App |
|||
import Part |
|||
def create(obj_name): |
|||
""" |
""" |
||
Object creation method |
Object creation method |
||
""" |
""" |
||
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name) |
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name) |
||
box(obj) |
|||
ViewProviderBox(obj.ViewObject) |
ViewProviderBox(obj.ViewObject) |
||
App.ActiveDocument.recompute() |
App.ActiveDocument.recompute() |
||
return |
return obj |
||
class box(): |
|||
class box(): |
|||
def __init__(self, obj): |
def __init__(self, obj): |
||
""" |
""" |
||
Default |
Default constructor |
||
""" |
""" |
||
self.Type = 'box' |
self.Type = 'box' |
||
self.ratios = None |
|||
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = 'Hello World!' |
|||
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0 |
|||
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width'). Width = '10 mm' |
|||
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box Height').Height = '1 cm' |
|||
obj.addProperty('App::PropertyBool', 'Aspect Ratio', 'Dimensions', 'Lock the box aspect ratio').Aspect_Ratio = False |
|||
obj.Proxy = self |
obj.Proxy = self |
||
self.Object = obj |
|||
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = "" |
|||
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0 |
|||
def __getstate__(self): |
|||
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm' |
|||
return self.Type |
|||
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm' |
|||
def __setstate__(self, state): |
|||
if state: |
|||
self.Type = state |
|||
def execute(self, obj): |
def execute(self, obj): |
||
""" |
""" |
||
Called on document recompute |
Called on document recompute |
||
""" |
""" |
||
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height) |
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height) |
||
class ViewProviderBox: |
|||
class ViewProviderBox: |
|||
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.Proxy = self |
obj.Proxy = self |
||
def attach(self, obj): |
def attach(self, obj): |
||
""" |
""" |
||
Line 357: | Line 332: | ||
""" |
""" |
||
return |
return |
||
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 |
||
""" |
""" |
||
return |
return |
||
def getDisplayModes(self,obj): |
def getDisplayModes(self,obj): |
||
""" |
""" |
||
Return a list of display modes. |
Return a list of display modes. |
||
""" |
""" |
||
return |
return [] |
||
def getDefaultDisplayMode(self): |
def getDefaultDisplayMode(self): |
||
""" |
""" |
||
Line 376: | Line 350: | ||
""" |
""" |
||
return "Shaded" |
return "Shaded" |
||
def setDisplayMode(self,mode): |
def setDisplayMode(self,mode): |
||
""" |
""" |
||
Line 384: | Line 358: | ||
""" |
""" |
||
return mode |
return mode |
||
def onChanged(self, |
def onChanged(self, vp, prop): |
||
""" |
""" |
||
Print the name of the property that has changed |
Print the name of the property that has changed |
||
""" |
""" |
||
App.Console.PrintMessage("Change property: " + str(prop) + "\n") |
App.Console.PrintMessage("Change property: " + str(prop) + "\n") |
||
def getIcon(self): |
def getIcon(self): |
||
""" |
""" |
||
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown. |
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown. |
||
""" |
""" |
||
return """ |
return """ |
||
/* XPM */ |
/* XPM */ |
||
static const char * ViewProviderBox_xpm[] = { |
static const char * ViewProviderBox_xpm[] = { |
||
"16 16 6 1", |
"16 16 6 1", |
||
" |
" c None", |
||
". |
". c #141010", |
||
"+ |
"+ c #615BD2", |
||
"@ |
"@ c #C39D55", |
||
"# |
"# c #000000", |
||
"$ |
"$ c #57C355", |
||
" ........", |
" ........", |
||
" ......++..+..", |
" ......++..+..", |
||
Line 424: | Line 398: | ||
" ####### "}; |
" ####### "}; |
||
""" |
""" |
||
def __getstate__(self): |
def __getstate__(self): |
||
""" |
|||
Called during document saving. |
|||
""" |
|||
return None |
return None |
||
def __setstate__(self,state): |
def __setstate__(self,state): |
||
""" |
|||
Called during document restore. |
|||
""" |
|||
return None |
return None |
||
}} |
|||
[[#top|top]] |
|||
<div class="mw-translate-fuzzy"> |
|||
{{docnav/fr |
{{docnav/fr |
||
|[[FeaturePython Objects/fr|Objects FeaturePython]] |
|[[FeaturePython Objects/fr|Objects FeaturePython]] |
||
|[[Scripting examples/fr|Exemples de Scripts]] |
|[[Scripting examples/fr|Exemples de Scripts]] |
||
}} |
}} |
||
</div> |
|||
{{Powerdocnavi{{#translation:}}}} |
|||
{{Userdocnavi/fr}} |
|||
[[Category:Developer Documentation{{#translation:}}]] |
|||
[[Category: |
[[Category:Python Code{{#translation:}}]] |
||
{{clear}} |
|||
[[Category:Python Code/fr]] |
|||
[[Category:Poweruser Documentation/fr]] |
Revision as of 11:00, 9 June 2020
App::FeaturePython vs. Part::FeaturePython
Jusqu'à présent, nous nous sommes concentrés sur les aspects internes d'une classe Python construite autour d'un objet FeaturePython - en particulier, un objet App::FeaturePython object.
Nous avons créé l'objet, défini certaines propriétés et ajouté des rappels d'événements au niveau du document qui permettent à notre objet de répondre à un recalcul de document avec la méthode execute()
.
Adding a box
Mais nous n'avons toujours pas de boîte.
import Part
Then in execute()
delete the print()
statement and add the following line in its place:
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))
Ces commandes exécutent des scripts python fournis avec FreeCAD par défaut.
- La méthode
makeBox()
génère une nouvelle forme de boîte. - L'appel englobant à
Part.show()
ajoute la forme à l'arborescence du document et la rend visible.
Si FreeCAD est ouvert, rechargez le module boîte et créez un nouvel objet boîte en utilisant box.create()
(supprimez tous les objets boîte existants, juste pour garder les choses propres).
Cela devrait être assez évident. La boîte elle-même est représentée par un objet entièrement différent de notre objet FeaturePython. La raison en est que Part.show()
crée un objet boîte séparé et l'ajoute au document. En fait, si vous accédez à votre objet FeaturePython et modifiez les dimensions, vous verrez une autre forme de boîte créée et l'ancienne laissée en place. Ce n'est pas bon! De plus, si la vue du rapport est ouverte, vous pouvez remarquer une erreur indiquant 'Les recalculs imbriqués d'un document ne sont pas autorisés'. Cela a à voir avec l'utilisation de la méthode Part.show() dans un objet FeaturePython. Nous voulons éviter de faire cela.
Fixing the code
Alors, comment pouvons-nous résoudre ce problème?
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)
pour lire:
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name)
Pour terminer les modifications dont nous avons besoin, la ligne suivante de la méthode execute()
doit être modifiée:
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))
en:
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
Maintenant, enregistrez vos modifications et revenez à FreeCAD. Supprimez tous les objets existants, rechargez le module de boîte et créez un nouvel objet de boîte.
Écrire un ViewProvider
Un fournisseur de vues est le composant d'un objet qui lui permet d'avoir une représentation visuelle dans l'interface graphique - en particulier dans la vue 3D. FreeCAD utilise une structure d'application appelée 'vue du modèle' qui est conçue pour séparer les données (le 'modèle') de sa représentation visuelle (la «vue»). Si vous avez passé du temps à travailler avec FreeCAD en Python, vous en serez probablement déjà conscient grâce à l'utilisation de deux modules Python principaux: FreeCAD et FreeCADGui (souvent aliasés respectivement «App» et «Gui»).
Ainsi, notre implémentation de FeaturePython Box nécessite également ces éléments. Jusqu'à présent, nous nous sommes concentrés uniquement sur la partie «modèle», il est donc temps d'écrire la «vue». Heureusement, la plupart des implémentations de vues sont simples et nécessitent peu d'efforts pour écrire, au moins pour commencer. Voici un exemple de ViewProvider, emprunté et légèrement modifié de [1]
class ViewProviderBox:
def __init__(self, obj):
"""
Set this object to the proxy object of the actual view provider
"""
obj.Proxy = self
def attach(self, obj):
"""
Setup the scene sub-graph of the view provider, this method is mandatory
"""
return
def updateData(self, fp, prop):
"""
If a property of the handled feature has changed we have the chance to handle this here
"""
return
def getDisplayModes(self,obj):
"""
Return a list of display modes.
"""
return []
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):
"""
Print the name of the property that has changed
"""
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
def getIcon(self):
"""
Return the icon in XMP 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):
"""
Called during document saving.
"""
return None
def __setstate__(self,state):
"""
Called during document restore.
"""
return None
Remarquez dans le code ci-dessus, nous définissons également une icône XMP pour cet objet. La conception d'icônes dépasse le cadre de ce didacticiel, mais les conceptions d'icônes de base peuvent être gérées à l'aide d'outils open source tels que GIMP, Krita et Inkscape. La méthode getIcon est également facultative. S'il n'est pas fourni, FreeCAD fournira une icône par défaut.
Sans un ViewProvider défini, nous devons maintenant l'utiliser pour donner à notre objet la possibilité de la visualisation.
ViewProviderBox(obj.ViewObject)
Cette instance de la classe ViewProvider personnalisée lui transmet le ViewObject intégré de FeaturePython. Le ViewObject ne fera rien sans notre implémentation de la classe personnalisée donc, quand la classe ViewProvider s'initialise, elle enregistre une référence à elle-même dans l'attribut ViewObject.Proxy de FeaturePython. De cette façon, lorsque FreeCAD a besoin de rendre notre Box visuellement, il peut trouver la classe ViewProvider pour le faire.
Maintenant, enregistrez les modifications et revenez à FreeCAD. Importez ou rechargez le module Box et appelez box.create()
.
Piégeage d'événements
Jusqu'à présent, nous n'avons pas abordé explicitement le piégeage d'événements. Presque toutes les méthodes d'une classe FeaturePython servent de rappel accessible à l'objet FeaturePython (qui obtient l'accès à notre instance de classe via l'attribut Proxy
, si vous vous en souvenez).
Voici une liste des rappels pouvant être implémentés dans l'objet FeaturePython de base:
execute(self, obj)
|
Called during document recomputes | Do not call recompute() from this method (or any method called from execute() ) as this causes a nested recompute.
|
onBeforeChanged(self, obj, prop)
|
Called before a property value is changed | prop is the name of the property to be changed, not the property object itself. Property changes cannot be cancelled. Previous / next property values are not simultaneously available for comparison.
|
onChanged(self, obj, prop)
|
Called after a property is changed | prop is the name of the property to be changed, not the property object itself.
|
onDocumentRestored(self, obj)
|
Called after a document is restored or a FeaturePython object is copied. | Occasionally, references to the FeaturePython object from the class, or the class from the FeaturePython object may be broken, as the class __init__() method is not called when the object is reconstructed. Adding self.Object = obj or obj.Proxy = self often solves these issues.
|
En outre, il existe deux rappels dans la classe ViewProvider qui peuvent parfois s'avérer utiles:
updateData(self, obj, prop)
|
Called after a data (model) property is changed | obj is a reference to the FeaturePython class instance, not the ViewProvider instance. prop is the name of the property to be changed, not the property object itself.
|
onChanged(self, vobj, prop)
|
Called after a view property is changed | vobj is a reference to the ViewProvider instance. prop is the name of the view property which was changed.
|
|-
|Il n'est pas rare de rencontrer une situation où les rappels Python ne sont pas déclenchés comme ils le devraient. Les débutants dans ce domaine doivent être assurés que le système de rappel FeaturePython n'est pas fragile ou cassé. Invariablement, lorsque les rappels ne s'exécutent pas, c'est parce qu'une référence est perdue ou indéfinie dans le code sous-jacent. Si, cependant, les rappels semblent rompre sans explication, la fourniture de références d'objet / proxy dans le rappel onDocumentRestored()
(comme indiqué dans le premier tableau ci-dessus) peut atténuer ces problèmes. Jusqu'à ce que vous soyez à l'aise avec le système de rappel, il peut être utile d'ajouter des instructions d'impression dans chaque rappel pour imprimer des messages sur la console comme diagnostic pendant le développement.
Le code
import FreeCAD as App
import Part
def create(obj_name):
"""
Object creation method
"""
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name)
box(obj)
ViewProviderBox(obj.ViewObject)
App.ActiveDocument.recompute()
return obj
class box():
def __init__(self, obj):
"""
Default constructor
"""
self.Type = 'box'
obj.Proxy = self
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = ""
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm'
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm'
def execute(self, obj):
"""
Called on document recompute
"""
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
class ViewProviderBox:
def __init__(self, obj):
"""
Set this object to the proxy object of the actual view provider
"""
obj.Proxy = self
def attach(self, obj):
"""
Setup the scene sub-graph of the view provider, this method is mandatory
"""
return
def updateData(self, fp, prop):
"""
If a property of the handled feature has changed we have the chance to handle this here
"""
return
def getDisplayModes(self,obj):
"""
Return a list of display modes.
"""
return []
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):
"""
Print the name of the property that has changed
"""
App.Console.PrintMessage("Change property: " + str(prop) + "\n")
def getIcon(self):
"""
Return the icon in XMP 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):
"""
Called during document saving.
"""
return None
def __setstate__(self,state):
"""
Called during document restore.
"""
return None
- Scripts FreeCAD : Python, Introduction à Python, Tutoriel sur les scripts Python, Débuter avec les scripts
- Modules : Modules intégrés, Unités, Quantity
- Ateliers : Création d'atelier, Commands Gui, Les commandes, Installer des ateliers supplémentaires
- Maillages et objets Parts : Scripts Mesh, Script de données topologiques, Conversion objet Mesh en Part, PythonOCC
- Objets paramétriques : Objets créés par script, Viewproviders (Icône personnalisée dans l'arborescence)
- Scénographie : Graphe de scène Coin (Inventor), Pivy
- Interface graphique : Création d'interface, Création d'une boite de dialogue (1, 2, 3, 4, 5), PySide, Exemples PySide débutant, intermédiaire, expérimenté
- Macros : Macros, Comment installer des macros
- Intégration : Intégrer FreeCAD, Intégration de FreeCADGui
- Autre : Expressions, Extraits de codes, Fonction - tracer une ligne, Bibliothèque mathématique vectorielle de FreeCAD (déprécié)