Create a FeaturePython object part I/fr: Difference between revisions

From FreeCAD Documentation
(Created page with "Objets FeaturePython")
 
No edit summary
 
(190 intermediate revisions by 2 users not shown)
Line 1: Line 1:
<languages/>
<languages/>


{{Docnav/fr
{{docnav|PySide|Creating a FeaturePython Box, Part II}}
|[[PySide/fr|PySide]]
|[[Create_a_FeaturePython_object_part_II/fr|Créer un objet FeaturePython partie II]]
}}


{{TOCright}}
== Introduction ==


==Introduction==
FeaturePython objects (also often referred to as 'Scripted Objects') provide users the ability to extend FreeCAD's with objects that integrate seamlessly into the FreeCAD framework.


Les objets FeaturePython (également appelés [[Scripted_objects/fr|Objets créés par script]]) offrent aux utilisateurs la possibilité d'étendre FreeCAD avec des objets qui s'intègrent de manière transparente dans le framework FreeCAD.
This encourages:
*'''Rapid prototyping''' of new objects and tools with custom Python classes.
*'''Serialization''' through 'App::Property' objects, ''without embedding any script'' in the FreeCAD document file.
*'''Creative freedom''' to adapt FreeCAD for any task!


Cela favorise:
This wiki will provide you with a complete understanding of how to use FeaturePython objects and custom Python classes in FreeCAD. We're going to construct a complete, working example of a FeaturePython custom class, identifying all of the major components and gaining an intimate understanding of how everything works as we go.
*Le prototypage rapide de nouveaux objets et outils avec des classes personnalisées de Python.
* Sauvegarde et restauration des données (également appelées sérialisation) via des objets {{incode|App::Property}} sans intégrer de script dans le fichier de document FreeCAD.
*Une liberté créative pour adapter FreeCAD à n'importe quelle tâche.


Sur cette page, nous allons construire un exemple de travail d'une classe personnalisée FeaturePython, en identifiant tous les composants principaux et en comprenant comment tout fonctionne au fur et à mesure.
=== How Does It Work? ===


<span id="How_does_it_work?"></span>
FreeCAD comes with a number of default object types for managing different kinds of geometry. Some of them have 'FeaturePython' alternatives that allow for user customization with a custom python class.
== Comment ça marche? ==


FreeCAD est livré avec un certain nombre de types d'objets par défaut pour gérer différents types de géométrie. Certains d'entre eux ont des alternatives "FeaturePython" qui permettent une personnalisation avec une classe Python définie par l'utilisateur.
The custom python class simply takes a reference to one of these objects and modifies it in any number of ways. For example, the python class may add properties directly to the object, modifying other properties when it's recomputed, or linking it to other objects. In addition the python class implements certain methods to enable it to respond to document events, making it possible to trap object property changes and document recomputes.


Cette classe Python personnalisée prend une référence à l'un de ces objets et le modifie. Par exemple, la classe Python peut ajouter des propriétés à l'objet ou le lier à d'autres objets. De plus, la classe Python peut implémenter certaines méthodes pour permettre à l'objet de répondre aux événements du document, ce qui permet de piéger les modifications de propriété de l'objet et de recalculer le document.
It's important to remember, however, that for as much as one can accomplish with custom classes and FeaturePython objects, when it comes time to save the document, '''only the FeaturePython object itself is serialized'''. The custom class and it's state are not retained between document reloading. Doing so would require embedding script in the FreeCAD document file, which poses a significant security risk, much like the risks posed by [https://www.howtogeek.com/171993/macros-explained-why-microsoft-office-files-can-be-dangerous/ embedding VBA macros in Microsoft Office documents].


Lorsque vous travaillez avec des classes personnalisées et des objets FeaturePython, il est important de savoir que la classe personnalisée et son état ne sont pas enregistrés dans le document car cela nécessiterait l'incorporation d'un script dans un fichier de document FreeCAD, ce qui poserait un risque de sécurité important. Seul l'objet FeaturePython lui-même est enregistré (sérialisé). Mais comme le chemin du module de script est stocké dans le document, un utilisateur n'a qu'à installer le code de classe Python personnalisé en tant que module importable, suivant la même structure de dossier, pour retrouver la fonctionnalité perdue.
Thus, a FeaturePython object ultimately exists entirely apart from it's script. The inconvenience posed by not packing the script with the object in the document file is far less than the risk posed by running a file embedded with an unknown script. However, the script module path is stored in the document file. Therefore, a user need only install the custom python class code as an importable module following the same directory structure to regain the lost functionality.


[[#top|En haut]]


<span id="Setting_things_up"></span>
== Setting up your development environment ==
== Mise en place ==


Les classes d'objets FeaturePython doivent agir comme des modules importables dans FreeCAD. Cela signifie que vous devez les placer dans un chemin qui existe dans votre environnement Python (ou l'ajouter spécifiquement). Pour les besoins de ce tutoriel, nous allons utiliser le dossier Macro utilisateur FreeCAD, mais si vous avez une autre idée en tête, n'hésitez pas à l'utiliser à la place!
To begin, FeaturePython Object classes need to act as importable modules in FreeCAD. That means you need to place them in a path that exists in your Python environment (or add it specifically). For the purposes of this tutorial, we're going to use the FreeCAD user Macro folder, though if you have another idea in mind, feel free to use that instead!


If you don't know where the FreeCAD Macro folder is type 'FreeCAD.getUserMacroDir(True)' in FreeCAD's Python console. The place is configurable but, by default, to go there:
Si vous ne savez pas se trouve le dossier des Macro de FreeCAD, tapez {{incode|FreeCAD.getUserMacroDir(True)}} dans la [[Python_console/fr|console Python]] de FreeCAD :
* Sous Linux, il s'agit généralement de {{FileName|/home/<username>/.local/share/FreeCAD/Mod/}} ({{VersionPlus/fr|0.20}}) ou {{FileName|/home/<username>/.FreeCAD/Mod/}} ({{VersionMinus/fr|0.19}}).
*Windows: Type '%APPDATA%/FreeCAD/Macro' in the filepath bar at the top of Explorer
* Sous Windows, c'est {{FileName|%APPDATA%\FreeCAD\Macro\}}, généralement {{FileName|C:\Users\<username>\Appdata\Roaming\FreeCAD\Macro\}}.
*Linux: Navigate to /home/USERNAME/.FreeCAD/Macro
*Mac: Navigate to /Users/USERNAME/Library/Preferences/FreeCAD/Macro
* Sous macOS, il s'agit généralement de {{FileName|/Users/<username>/Library/Application Support/FreeCAD/Macro/}}.


Maintenant, nous devons créer des fichiers :
Now we need to create some files.
*Dans le dossier {{FileName|Macro}}, créez un nouveau dossier appelé {{FileName|fpo}}.
*In the Macro folder create a new folder called '''fpo'''.
*In the fpo folder create an empty file: '''__init__.py'''.
*Dans le dossier {{FileName|fpo}}, créez un fichier vide : {{FileName|__init__.py}}.
*Dans le dossier {{FileName|fpo}}, créez un nouveau dossier appelé {{FileName|box}}.
*In the fpo folder, create a new folder called '''box'''.
*In the box folder create two files: '''__init__.py''' and '''box.py''' (leave both empty for now)
*Dans le dossier {{FileName|box}}, créez deux fichiers : {{FileName|__init__.py}} et {{FileName|box.py}} (laissez les deux cases vides pour l'instant).


La structure de votre dossier devrait ressembler à ceci :
Notes:
* The '''fpo''' folder provides a nice spot to play with new FeaturePython objects and the '''box''' folder is the module we will be working in.
* '''__init__.py''' tells Python that in the folder is an importable module, and '''box.py''' will be the class file for our new FeaturePython Object.


Macro/
Your directory structure should look like this:
|--> fpo/

|--> __init__.py
.FreeCAD
|--> Macro
|--> box/
|--> fpo
|--> __init__.py
|--> __init__.py
|--> box
|--> box.py
|--> __init__.py
|--> box.py


Le dossier {{FileName|fpo}} est l'endroit pour jouer avec les nouveaux objets FeaturePython et le dossier {{FileName|box}} est le module dans lequel nous allons travailler. {{FileName|__init__.py}} indique à Python qu'il y a un module importable dans le dossier et {{FileName|box.py}} sera le fichier de classe pour notre nouvel objet FeaturePython.
With our module paths and files created, let's make sure FreeCAD is set up properly:


Une fois nos chemins et fichiers de module créés, nous assurons que FreeCAD est correctement configuré :
*Start FreeCAD (if it isn't already open)
* Démarrez FreeCAD (si vous ne l'avez pas déjà fait).
*Enable Python Console and Report Views ('''View -> Panels -> Report view''' and '''Python console''') [[FreeCAD Scripting Basics|(learn more about it here)]]
* Activez la [[Report_view/fr|Vue rapport]] ({{MenuCommand|Affichage → Panneaux → Vue rapport}}).
*In your favorite code editor, navigate to the '''/Macro/fpo/box''' folder and open '''box.py'''
* Activez la [[Python_console/fr|console Python]] ({{MenuCommand|Affichage → Panneaux → Console Python}}) voir [[FreeCAD_Scripting_Basics/fr|FreeCAD Scripting Basics]].


Enfin, naviguez jusqu'au dossier {{FileName|Macro/fpo/box}} et ouvrez {{FileName|box.py}} dans votre éditeur de code préféré. Nous ne modifierons que ce fichier.
It's time to write some code!


[[#top|En haut]]


<span id="A_FeaturePython_object"></span>
-----
==Un objet FeaturePython==


Commençons par écrire notre classe et son constructeur :
== A Very Basic FeaturePython Object ==


{{Code|code=
Let's get started by writing our class and it's constructor:
class box():


def __init__(self, obj):
class box():
"""
Default constructor
def __init__(self, obj):
"""
"""
Constructor
Arguments
---------
- obj: a variable created with FreeCAD.Document.addObject('App::FeaturePython', '{name}').
"""
self.Type = 'box'
obj.Proxy = self


self.Type = 'box'


obj.Proxy = self
'''The <code>__init__()</code> method breakdown'''
}}
{|class="wikitable" cellpadding="5" style="margin-left: 15px; margin-right: 5px"

|style="width:30%" | <code>def __init__(self, obj):</code> || Parameters refer to the Python class itself and the FeaturePython object that it is attached to.
'''La méthode {{incode|__init__()}} se décompose comme suit:'''

{|class="wikitable" cellpadding="5px" width="100%"
|style="width:25%" | {{incode|def __init__(self, obj):}}
|style="width:75%" | Les paramètres font référence à la classe Python elle-même et à l'objet FeaturePython auquel elle est attachée.
|-
|-
| {{incode|self.Type <nowiki>=</nowiki> 'box'}}
|<code> self.Type = 'box'</code> || String definition of the custom python type
| Définition de chaîne du type Python personnalisé.
|-
|-
| {{incode|obj.Proxy <nowiki>=</nowiki> self}}
|<code> obj.Proxy = self</code> || Stores a reference to the Python instance in the FeaturePython object
| Stocke une référence à l'instance Python dans l'objet FeaturePython.
|}
|}


Ajoutez le code suivant en haut du fichier :


{{Code|code=
In the '''box.py''' file at the top, add the following code:
import FreeCAD as App


def create(obj_name):
import FreeCAD as App
"""
Object creation method
def create(obj_name):
"""
"""
Object creation method
"""
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)
fpo = box(obj)
return fpo


obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)


box(obj)
'''The <code>create()</code> method breakdown'''

{|class="wikitable" cellpadding="5" style="float:right; margin-left: 15px; margin-right: 5px"
return obj
|style="width:30%" | <code>import FreeCAD as App</code> || Standard import for most python scripts. The '''App''' alias is not required.
}}

'''La méthode {{incode|create()}} se décompose comme suit :'''

{|class="wikitable" cellpadding="5px" width="100%"
|style="width:25%" | {{incode|import FreeCAD as App}}
|style="width:75%" | Importation standard pour la plupart des scripts Python, l'alias d'application n'est pas requis.
|-
| {{incode|obj <nowiki>=</nowiki> ... addObject(...)}}
| Crée un nouvel objet FeaturePython de FreeCAD avec le nom transmis à la méthode. S'il n'y a pas de conflit de nom, ce sera le libellé et le nom de l'objet créé. Sinon, un nom et une étiquette uniques seront créés en fonction de 'obj_name'.
|-
|-
| {{incode|box(obj)}}
|<code>obj = ... addObject(...)</code> || Creates a new FreeCAD FeaturePython object with the name passed to the method. If there is no name clash, this will be the label and the name of the created object, i.e. what is visible in the model tree. Otherwise, a unique name and label will be created based on 'obj_name'.
| Crée notre instance de classe personnalisée.
|-
|-
| {{incode|return obj}}
|<code>fpo = box(obj)</code> || Create our custom class instance and link it to the FeaturePython object.
| Renvoie l'objet FeaturePython.
|}
|}


La méthode {{incode|create()}} n'est pas requise mais elle fournit un bon moyen d'encapsuler le code de création d'objet.


[[#top|En haut]]
The <code>create()</code> method is not required, but it provides a nice way to encapsulate the object creation code.


<span id="Testing_the_code"></span>
=== Test du code ===


Nous pouvons maintenant tester notre nouvel objet. Enregistrez votre code et revenez à FreeCAD. Assurez-vous que vous avez ouvert un nouveau document, vous pouvez le faire en appuyant sur {{KEY|Ctrl}}+{{KEY|N}} ou en sélectionnant {{MenuCommand|Fichier → Nouveau}}.
-----


Dans la console Python, tapez ce qui suit :
=== Testing the Code ===


{{Code|code=
Now we can try our new object. Save your code and return to FreeCAD, make sure you've '''opened a new document'''. You can do this by pressing '''CTRL+n''' or selecting '''File -> New'''
from fpo.box import box
}}


Maintenant, nous devons créer notre objet :
In the Python Console, type the following:


{{Code|code=
>>> from fpo.box import box
mybox = box.create('my_box')
}}


[[Image:Fpo_treeview.png|right]]
<br>Now, we need to create our object:
Vous devriez voir apparaître un nouvel objet dans la [[Tree_view/fr|Vue en arborescence]] étiqueté "my_box".


Notez que l'icône est grise. FreeCAD nous dit que l'objet ne peut rien afficher dans la [[3D_view/fr|Vue 3D]]. Cliquez sur l'objet et regardez ses propriétés dans l'[[Property_editor/fr|Éditeur de propriétés]]. Il n'y a pas grand-chose là-bas, juste le nom de l'objet.
>>> box.create('my_box')


Notez également qu'il y a une petite coche bleue à côté de l'objet FeaturePython dans l'arborescence. En effet, lorsqu'un objet est créé ou modifié, il est "touché" et doit être recalculé. Appuyez sur le bouton {{Button|[[Image:Std_Refresh.svg|16px]] [[Std_Refresh/fr|Std Rafraîchir]]}} pour y parvenir. Nous ajouterons du code pour automatiser cela plus tard.
[[File:Fpo_treeview.png | right | 192px]]
{{Clear}}
You should see a new object appear in the tree view at the top left labelled '''my_box'''.<br><br>Note that the icon is gray. FreeCAD is simply telling us that the object is not able to display anything in the 3D view... yet. Click on the object and note what appears in the property panel under it.<br>There's not very much - just the name of the object. We'll need to add some properties in a bit.
<br clear="all">
Let's also make referencing our new object a little more convenient:


Jetons un oeil aux attributs de notre objet :
>>> mybox = App.ActiveDocument.my_box
{{Code|code=
dir(mybox)
}}


Cela retournera :
And then we should take a look at our object's attributes:


{{Code|code=
>>> dir(mybox)
['Content', 'Document', 'ExpressionEngine', 'InList', 'InListRecursive', 'Label', 'MemSize', 'Module', 'Name', 'OutList', 'OutListRecursive', 'PropertiesList', 'Proxy', 'State', 'TypeId', 'ViewObject', '__class__',
['Content', 'Document', 'ExpressionEngine', 'FullName', 'ID', 'InList',
...
...
'setEditorMode', 'setExpression', 'supportedProperties', 'touch']
'setPropertyStatus', 'supportedProperties', 'touch']
}}


Il y a beaucoup d'attributs car nous accédons à l'objet FeaturePyton natif de FreeCAD créé à la première ligne de notre méthode {{incode|create()}}. La propriété {{incode|Proxy}} que nous avons ajoutée dans notre méthode {{incode|__init__()}} est là aussi.


Inspectons-la avec la méthode {{incode|dir()}} :
There's a lot of attributes there because we're accessing the native FreeCAD FeaturePyton object that we created in the first line of our <code>create()</code> method.
The <code>Proxy</code> property we added in our <code>__init__()</code> method is there, too.


{{Code|code=
Let's inspect that by calling the <code>dir()</code>on the Proxy object:
dir(mybox.Proxy)
}}


Cela retournera :
>>> dir(mybox.Proxy)
['Object', 'Type', '__class__', '__delattr__', '__dict__', '__dir__',
...
'__str__', '__subclasshook__', '__weakref__']


{{Code|code=
['Type', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
...
'__str__', '__subclasshook__', '__weakref__']
}}


Nous pouvons voir notre propriété {{incode|Type}}. Vérifions :
Once we inspect the Proxy property, we can see our <code>Object</code> and <code>Type</code> properties. This means we're accessing the custom Python object defined in '''box.py'''.


{{Code|code=
Call the <code>Type</code> property and look at the result:
>>> mybox.Proxy.Type
mybox.Proxy.Type
}}
'box'


Cela retournera :
Sure enough, it returns the value we assigned, so we know we're accessing the custom class itself through the FeaturePython object.


{{Code|code=
Likewise, we can access the FreeCAD object (not our Python object) by using the<code>Object</code>method:
'box'
>>> mybox.Proxy.Object
}}


Il s'agit bien de la valeur assignée, nous savons donc que nous accédons à la classe personnalisée via l'objet FeaturePython.
That was fun! But now let's see if we can make our class a little more interesting... and maybe more useful.


Voyons maintenant si nous pouvons rendre notre classe un peu plus intéressante, et peut-être aussi plus utile.


[[#top|En haut]]
-----


<span id="Adding_properties"></span>
=== Adding Properties ===
===Ajout de propriétés===


Les propriétés sont l'élément vital d'une classe FeaturePython. Heureusement, FreeCAD supporte [[FeaturePython_Custom_Properties/fr|un certain nombre de types de propriétés]] pour les classes FeaturePython. Ces propriétés sont attachées directement à l'objet FeaturePython et sont entièrement sérialisées lors de l'enregistrement du fichier. Pour éviter d'avoir à sérialiser les données vous-même, il est conseillé de n'utiliser que ces types de propriétés.
Properties are the lifeblood of a FeaturePython class.
<br><br>Fortunately, FreeCAD supports [[FeaturePython_Custom_Properties|a number of property types]] for FeaturePython classes. These properties are attached directly to the FeaturePython object itself and fully serialized when the file is saved. That means, unless you want to serialize the data yourself, you'll need to find some way to wrangle it into a supported property type.
<br><br>Adding properties is done quite simply using the <code>add_property()</code> method. The syntax for the method is:


L'ajout de propriétés se fait à l'aide de la méthode {{incode|add_property()}}. La syntaxe de cette méthode est la suivante :

<!--Ne pas utiliser de modèle de code pour éviter la mise en évidence de la syntaxe-->
add_property(type, name, section, description)
add_property(type, name, section, description)


Vous pouvez consulter la liste des propriétés prises en charge en tapant :
{|class="wikitable" cellpadding="5" style="float:right; margin-left: 15px; margin-right: 5px; text-align:left"
!Tip
|-
||You can view the list of supported properties for an object by typing:<br><code>>>> mybox.supportedProperties()</code>
|}
<br>Let's try adding a property to our box class.
<br><br>Switch to your code editor and move to the <code>__init__()</code> method.


{{Code|code=
<br clear="all">Then, at the end of the method, add:
mybox.supportedProperties()
}}


Essayons d'ajouter une propriété à notre classe de boîte. Passez à votre éditeur de code, passez à la méthode {{incode|__init__()}}, et à la fin de la méthode, ajoutez :
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = ""
Note how we're using the reference to the (serializable) FeaturePython object, <code>obj</code> and not the (non-serializable) Python class instanace, <code>self</code>.
<br>Anyway, once you're done, save the changes and switch back to FreeCAD.
<br><br>Before we can observe the changes we made to our code, we need to reload the module. This can be accomplished by restarting FreeCAD, but restarting FreeCAD everytime we make a change to the python class code can get a bit inconvenient. To make it easier, try the following in the Python console:


{{Code|code=
>>> from importlib import reload
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = ""
>>> reload(box)
}}
This will reload the box module, incorporating changes you made to the '''box.py''' file, just as if you'd restarted FreeCAD.
<br><br>With the module reloaded, now let's see what we get when we create an object:


Notez que nous utilisons la référence à l'objet FeaturePython (sérialisable) {{incode|obj}}, et non l'instance de classe Python (non sérialisable) {{incode|self}}.
>>> box.create('box_property_test')
[[File:fpo_properties.png | right | 320px]]You should see the new box object appear in the tree view at left.<br><br>
*Select it and look at the Property Panel. There, you should see the 'Description' property.<br>
*Hover over the property name at left and see the tooltip appear with the description text you provided.<br>
*Select the field and type whatever you like. You'll notice that Python update commands are executed and displayed in the console as you type letters and the property changes.
<br><br>But before we leave the topic of properties for the moment, let's go back and add some properties that would make a custom box object *really* useful: namely, ''length'', ''width'', and ''height''.
<br><br>Return to your source code and add the following properties to <code>__init__()</code>:


Une fois que vous avez terminé, enregistrez les modifications et revenez à FreeCAD. Avant de pouvoir observer les modifications apportées à notre code, nous devons recharger le module. Cela peut être accompli en redémarrant FreeCAD, mais redémarrer FreeCAD à chaque fois que nous éditons le code serait gênant. Pour faciliter les choses, tapez ce qui suit dans la console Python :
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'


{{Code|code=
[[File:object_recompute_icon.png | left | 64px]]One last thing: Did you notice how the blue checkmark appears next to the FeaturePython object in the treeview at left?
from importlib import reload
That's because when an object is created or changed, it's "touched" and needs to be recomputed. Clicking the "recycle" arrows (the two arrows forming a circle) will accomplish this.
reload(box)
<br><br>But, we can accomplish that automatically by adding the following line to the end of the <code>create()</code> method:
}}


Avec le module rechargé, voyons ce que nous obtenons lorsque nous créons un objet :
App.ActiveDocument.recompute()
{|class="wikitable" cellpadding="5" style="margin-left: 15px; margin-right: 15px; text-align:left"
!Note
|-
|Be careful where you recompute a FeaturePython object! Generally, you should not try to recompute an object from within itself. Rather, document recomputing should be handled by a method external to the object, if possible.
|}
<br>Now, test your changes as follows:<br>
*Save your changes and return to FreeCAD.<br>
*Delete any existing objects and reload your module.<br>
*Finally, create another box object from the command line by calling <code>>>> box.create('myBox')</code>.
[[File:fpo_box_properties.png | right | 320px]]
<br>Once the box is created (and you've checked to make sure it's been recomputed!), select the object and look at your properties.
<br>You should note two things:<br>
*Three new properties (''length'', ''width'', and ''height'')
*A new property group, ''Dimensions''.
<br>Note also how the properties have dimensions. Specifically, they take on the linear dimension of the units set in the user preferences (see '''Edit''' -> '''Preference...''' -> '''Units tab''').
<br clear="all"><br>In fact, if you were paying attention when you were entering the code, you will have noticed that three separate values were entered for each dimension. The length was a floating-point value (10.0), the width was a string, specifying millimeters ('10 mm') and the height was a string specifying centimeters ('1 cm'). Yet, the property rendered all three values the same way: 10 mm. Specifically, a floating-point value is assumed to be in the current document units, and the string values are parsed according to the units specified, then converted to document units.
<br><br>The nice thing about the <code>App::PropertyLength</code> type is that it's a 'unit' type - values are understood as having specific units. Therefore, whenever you create a property that uses linear dimensions, use <code>App::PropertyLength</code> as the property type.


{{Code|code=
box.create('box_property_test')
}}


Vous devriez voir le nouvel objet boîte apparaître dans l'arborescence :
-----
*Sélectionnez-le et regardez l'éditeur de propriétés. Là, vous devriez voir la propriété ''Description''.
*Passez la souris sur le nom de la propriété à gauche et l'info-bulle devrait apparaître avec la description que vous avez fournie.
*Sélectionnez le champ et tapez ce que vous voulez. Vous remarquerez que les commandes de mise à jour Python sont exécutées et affichées dans la console lorsque vous tapez des lettres et que les propriétés changent.


[[#top|En haut]]
=== Event Trapping ===


Ajoutons quelques propriétés supplémentaires. Retournez à votre code source et ajoutez les propriétés suivantes à la méthode {{incode|__init__()}} :
The last element required for a basic FeaturePython object is event trapping. Specifically, we need to trap the <code>execute()</code> event, which is called when the object is recomputed. There's several other document-level events that can be trapped in our object as well, both in the FeaturePython object itself and in the ViewProvider, which we'll cover in another section.
<br>
Add the following after the <code>__init__()</code> function:


{{Code|code=
def execute(self, obj):
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0
"""
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm'
Called on document recompute
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm'
"""
}}
print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type))


Et ajoutons aussi un code pour recalculer le document automatiquement. Ajoutez la ligne suivante au-dessus de l'instruction {{incode|return()}} de la méthode {{incode|create()}}:


{{Code|code=
Test the code as follows:
App.ActiveDocument.recompute()
*Save changes and reload the box module in the FreeCAD python console.
}}
*Delete any objects in the Treeview
*Re-create the box object.


'''Faites attention à l'endroit où vous recalculez un objet FeaturePython. Le recalcul doit être effectué par une méthode externe à sa classe.'''
You should see the printed output in the Python Console, thanks to the <code>recompute()</code> call we added to the <code>create()</code> method.


[[Image:fpo_box_properties.png | right]]
Of course, the <code>execute()</code> method doesn't do anything here (except tell us that it was called), but it is the key to the magic of FeaturePython objects.


Maintenant, testez vos modifications comme suit:
<br>'''So that's it!'''
* Enregistrez vos modifications et rechargez votre module.
<br>You now know how to build a basic, functional FeaturePython object!
* Supprimez tous les objets dans l'arborescence.
* Créez un nouvel objet boîte à partir de la console Python en appelant {{incode|box.create('myBox')}}.


Une fois que la boîte est créée et que vous avez vérifié pour vous assurer qu'elle a été recalculée, sélectionnez l'objet et regardez ses propriétés. Vous devez noter deux choses:
* Un nouveau groupe de propriétés: ''Dimensions''.
* Trois nouvelles propriétés: ''Height'', ''Length'' et ''Width''.


Notez également que les propriétés ont des unités. Plus précisément, elles ont repris les unités linéaires définies dans les préférences de l'utilisateur ({{MenuCommand|Edition → Préférences... → Général → Unités}}).
-----
{{Clear}}


Vous avez sans doute remarqué que trois valeurs différentes ont été saisies pour les dimensions : une valeur en virgule flottante ({{incode|10.0}}) et deux chaînes de caractères différentes ({{incode|'10 mm'}} et {{incode|'1 cm'}}). Le type {{incode|App::PropertyLength}} suppose que les valeurs en virgule flottante sont en millimètres, les valeurs des chaînes sont analysées en fonction des unités spécifiées et, dans l'interface graphique, toutes les valeurs sont converties dans les unités spécifiées dans les préférences de l'utilisateur ({{incode|mm}} dans l'image). Ce comportement intégré rend le type {{incode|App::PropertyLength}} idéal pour les dimensions.
=== The Completed Code ===

import FreeCAD as App
[[#top|En haut]]

def create(obj_name):
<span id="Trapping_events"></span>
=== Piège à événements ===

Le dernier élément requis pour un objet FeaturePython de base est le recouvrement d'événements. Un objet FeaturePython peut réagir aux événements avec des fonctions de rappel. Dans notre cas, nous voulons que l'objet réagisse chaque fois qu'il est recalculé. En d'autres termes, nous voulons intercepter les recalculs. Pour ce faire, nous devons ajouter une fonction avec un nom spécifique, {{incode|execute()}}, à la classe d'objets. Il existe plusieurs autres événements qui peuvent être piégés, à la fois dans l'objet FeaturePython lui-même et dans le [[Viewprovider/fr|ViewProvider]], que nous aborderons dans la [[Create_a_FeaturePython_object_part_II/fr|Création d'une boîte FeaturePython, partie II]].

Pour une référence complète des méthodes disponibles à implémenter sur les classes FeautrePython, voir [[FeaturePython_methods/fr|Méthodes FeaturePython]].

Ajoutez ce qui suit après la fonction {{incode|__init__()}}:

{{Code|code=
def execute(self, obj):
"""
Called on document recompute
"""

print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type))
}}

Testez le code en suivant à nouveau ces étapes:
*Enregistrez et rechargez le module.
*Supprimer tous les objets.
*Créez un nouvel objet boîte.

Vous devriez voir la sortie imprimée dans la console Python, grâce à l'appel {{incode|recompute()}} que nous avons ajouté à la méthode {{incode|create()}}. Bien sûr, la méthode {{incode|execute()}} ne fait rien ici, sauf nous dire qu'elle a été appelée, mais elle est la clé de la magie des objets FeaturePython.

Voilà, vous savez maintenant comment construire un objet FeaturePython basique et fonctionnel !

[[#top|En haut]]

<span id="Complete_code"></span>
=== Le code terminé ===

{{Code|code=
import FreeCAD as App

def create(obj_name):
"""
"""
Object creation method
Object creation method
"""
"""

obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name))
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)

fpo = box(obj)
box(obj)

App.ActiveDocument.recompute()
return fpo

return obj
class box():

class box():

def __init__(self, obj):
def __init__(self, obj):
"""
"""
Default Constructor
Default constructor
"""
"""

self.Type = 'box'
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
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):
def execute(self, obj):
"""
"""
Called on document recompute
Called on document recompute
"""
"""
print('Recomputing {0:s} {1:s}'.format(obj.Name, self.Type))
{{docnav|PySide|Creating a FeaturePython Box, Part II}}


print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type))
{{Userdocnavi}}
}}

[[#top|En haut]]

{{Docnav/fr
|[[PySide/fr|PySide]]
|[[Create_a_FeaturePython_object_part_II/fr|Créer un objet FeaturePython partie II]]
}}


[[Category:Poweruser Documentation]]


{{Powerdocnavi{{#translation:}}}}
[[Category:Python Code]]
[[Category:Developer Documentation{{#translation:}}]]
[[Category:Python Code{{#translation:}}]]
{{clear}}

Latest revision as of 09:47, 18 February 2023

Other languages:

Introduction

Les objets FeaturePython (également appelés Objets créés par script) offrent aux utilisateurs la possibilité d'étendre FreeCAD avec des objets qui s'intègrent de manière transparente dans le framework FreeCAD.

Cela favorise:

  • Le prototypage rapide de nouveaux objets et outils avec des classes personnalisées de Python.
  • Sauvegarde et restauration des données (également appelées sérialisation) via des objets App::Property sans intégrer de script dans le fichier de document FreeCAD.
  • Une liberté créative pour adapter FreeCAD à n'importe quelle tâche.

Sur cette page, nous allons construire un exemple de travail d'une classe personnalisée FeaturePython, en identifiant tous les composants principaux et en comprenant comment tout fonctionne au fur et à mesure.

Comment ça marche?

FreeCAD est livré avec un certain nombre de types d'objets par défaut pour gérer différents types de géométrie. Certains d'entre eux ont des alternatives "FeaturePython" qui permettent une personnalisation avec une classe Python définie par l'utilisateur.

Cette classe Python personnalisée prend une référence à l'un de ces objets et le modifie. Par exemple, la classe Python peut ajouter des propriétés à l'objet ou le lier à d'autres objets. De plus, la classe Python peut implémenter certaines méthodes pour permettre à l'objet de répondre aux événements du document, ce qui permet de piéger les modifications de propriété de l'objet et de recalculer le document.

Lorsque vous travaillez avec des classes personnalisées et des objets FeaturePython, il est important de savoir que la classe personnalisée et son état ne sont pas enregistrés dans le document car cela nécessiterait l'incorporation d'un script dans un fichier de document FreeCAD, ce qui poserait un risque de sécurité important. Seul l'objet FeaturePython lui-même est enregistré (sérialisé). Mais comme le chemin du module de script est stocké dans le document, un utilisateur n'a qu'à installer le code de classe Python personnalisé en tant que module importable, suivant la même structure de dossier, pour retrouver la fonctionnalité perdue.

En haut

Mise en place

Les classes d'objets FeaturePython doivent agir comme des modules importables dans FreeCAD. Cela signifie que vous devez les placer dans un chemin qui existe dans votre environnement Python (ou l'ajouter spécifiquement). Pour les besoins de ce tutoriel, nous allons utiliser le dossier Macro utilisateur FreeCAD, mais si vous avez une autre idée en tête, n'hésitez pas à l'utiliser à la place!

Si vous ne savez pas où se trouve le dossier des Macro de FreeCAD, tapez FreeCAD.getUserMacroDir(True) dans la console Python de FreeCAD :

  • Sous Linux, il s'agit généralement de /home/<username>/.local/share/FreeCAD/Mod/ (version 0.20 et suivantes) ou /home/<username>/.FreeCAD/Mod/ (version 0.19 et précédentes).
  • Sous Windows, c'est %APPDATA%\FreeCAD\Macro\, généralement C:\Users\<username>\Appdata\Roaming\FreeCAD\Macro\.
  • Sous macOS, il s'agit généralement de /Users/<username>/Library/Application Support/FreeCAD/Macro/.

Maintenant, nous devons créer des fichiers :

  • Dans le dossier Macro, créez un nouveau dossier appelé fpo.
  • Dans le dossier fpo, créez un fichier vide : __init__.py.
  • Dans le dossier fpo, créez un nouveau dossier appelé box.
  • Dans le dossier box, créez deux fichiers : __init__.py et box.py (laissez les deux cases vides pour l'instant).

La structure de votre dossier devrait ressembler à ceci :

Macro/
    |--> fpo/
        |--> __init__.py
        |--> box/
            |--> __init__.py
            |--> box.py

Le dossier fpo est l'endroit pour jouer avec les nouveaux objets FeaturePython et le dossier box est le module dans lequel nous allons travailler. __init__.py indique à Python qu'il y a un module importable dans le dossier et box.py sera le fichier de classe pour notre nouvel objet FeaturePython.

Une fois nos chemins et fichiers de module créés, nous assurons que FreeCAD est correctement configuré :

Enfin, naviguez jusqu'au dossier Macro/fpo/box et ouvrez box.py dans votre éditeur de code préféré. Nous ne modifierons que ce fichier.

En haut

Un objet FeaturePython

Commençons par écrire notre classe et son constructeur :

class box():

    def __init__(self, obj):
        """
        Default constructor
        """

        self.Type = 'box'

        obj.Proxy = self

La méthode __init__() se décompose comme suit:

def __init__(self, obj): Les paramètres font référence à la classe Python elle-même et à l'objet FeaturePython auquel elle est attachée.
self.Type = 'box' Définition de chaîne du type Python personnalisé.
obj.Proxy = self Stocke une référence à l'instance Python dans l'objet FeaturePython.

Ajoutez le code suivant en haut du fichier :

import FreeCAD as App

def create(obj_name):
    """
    Object creation method
    """

    obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)

    box(obj)

    return obj

La méthode create() se décompose comme suit :

import FreeCAD as App Importation standard pour la plupart des scripts Python, l'alias d'application n'est pas requis.
obj = ... addObject(...) Crée un nouvel objet FeaturePython de FreeCAD avec le nom transmis à la méthode. S'il n'y a pas de conflit de nom, ce sera le libellé et le nom de l'objet créé. Sinon, un nom et une étiquette uniques seront créés en fonction de 'obj_name'.
box(obj) Crée notre instance de classe personnalisée.
return obj Renvoie l'objet FeaturePython.

La méthode create() n'est pas requise mais elle fournit un bon moyen d'encapsuler le code de création d'objet.

En haut

Test du code

Nous pouvons maintenant tester notre nouvel objet. Enregistrez votre code et revenez à FreeCAD. Assurez-vous que vous avez ouvert un nouveau document, vous pouvez le faire en appuyant sur Ctrl+N ou en sélectionnant Fichier → Nouveau.

Dans la console Python, tapez ce qui suit :

from fpo.box import box

Maintenant, nous devons créer notre objet :

mybox = box.create('my_box')

Vous devriez voir apparaître un nouvel objet dans la Vue en arborescence étiqueté "my_box".

Notez que l'icône est grise. FreeCAD nous dit que l'objet ne peut rien afficher dans la Vue 3D. Cliquez sur l'objet et regardez ses propriétés dans l'Éditeur de propriétés. Il n'y a pas grand-chose là-bas, juste le nom de l'objet.

Notez également qu'il y a une petite coche bleue à côté de l'objet FeaturePython dans l'arborescence. En effet, lorsqu'un objet est créé ou modifié, il est "touché" et doit être recalculé. Appuyez sur le bouton Std Rafraîchir pour y parvenir. Nous ajouterons du code pour automatiser cela plus tard.

Jetons un oeil aux attributs de notre objet :

dir(mybox)

Cela retournera :

['Content', 'Document', 'ExpressionEngine', 'FullName', 'ID', 'InList',
...
'setPropertyStatus', 'supportedProperties', 'touch']

Il y a beaucoup d'attributs car nous accédons à l'objet FeaturePyton natif de FreeCAD créé à la première ligne de notre méthode create(). La propriété Proxy que nous avons ajoutée dans notre méthode __init__() est là aussi.

Inspectons-la avec la méthode dir() :

dir(mybox.Proxy)

Cela retournera :

['Type', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
...
'__str__', '__subclasshook__', '__weakref__']

Nous pouvons voir notre propriété Type. Vérifions :

mybox.Proxy.Type

Cela retournera :

'box'

Il s'agit bien de la valeur assignée, nous savons donc que nous accédons à la classe personnalisée via l'objet FeaturePython.

Voyons maintenant si nous pouvons rendre notre classe un peu plus intéressante, et peut-être aussi plus utile.

En haut

Ajout de propriétés

Les propriétés sont l'élément vital d'une classe FeaturePython. Heureusement, FreeCAD supporte un certain nombre de types de propriétés pour les classes FeaturePython. Ces propriétés sont attachées directement à l'objet FeaturePython et sont entièrement sérialisées lors de l'enregistrement du fichier. Pour éviter d'avoir à sérialiser les données vous-même, il est conseillé de n'utiliser que ces types de propriétés.

L'ajout de propriétés se fait à l'aide de la méthode add_property(). La syntaxe de cette méthode est la suivante :

add_property(type, name, section, description)

Vous pouvez consulter la liste des propriétés prises en charge en tapant :

mybox.supportedProperties()

Essayons d'ajouter une propriété à notre classe de boîte. Passez à votre éditeur de code, passez à la méthode __init__(), et à la fin de la méthode, ajoutez :

obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = ""

Notez que nous utilisons la référence à l'objet FeaturePython (sérialisable) obj, et non l'instance de classe Python (non sérialisable) self.

Une fois que vous avez terminé, enregistrez les modifications et revenez à FreeCAD. Avant de pouvoir observer les modifications apportées à notre code, nous devons recharger le module. Cela peut être accompli en redémarrant FreeCAD, mais redémarrer FreeCAD à chaque fois que nous éditons le code serait gênant. Pour faciliter les choses, tapez ce qui suit dans la console Python :

from importlib import reload
reload(box)

Avec le module rechargé, voyons ce que nous obtenons lorsque nous créons un objet :

box.create('box_property_test')

Vous devriez voir le nouvel objet boîte apparaître dans l'arborescence :

  • Sélectionnez-le et regardez l'éditeur de propriétés. Là, vous devriez voir la propriété Description.
  • Passez la souris sur le nom de la propriété à gauche et l'info-bulle devrait apparaître avec la description que vous avez fournie.
  • Sélectionnez le champ et tapez ce que vous voulez. Vous remarquerez que les commandes de mise à jour Python sont exécutées et affichées dans la console lorsque vous tapez des lettres et que les propriétés changent.

En haut

Ajoutons quelques propriétés supplémentaires. Retournez à votre code source et ajoutez les propriétés suivantes à la méthode __init__() :

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'

Et ajoutons aussi un code pour recalculer le document automatiquement. Ajoutez la ligne suivante au-dessus de l'instruction return() de la méthode create():

App.ActiveDocument.recompute()

Faites attention à l'endroit où vous recalculez un objet FeaturePython. Le recalcul doit être effectué par une méthode externe à sa classe.

Maintenant, testez vos modifications comme suit:

  • Enregistrez vos modifications et rechargez votre module.
  • Supprimez tous les objets dans l'arborescence.
  • Créez un nouvel objet boîte à partir de la console Python en appelant box.create('myBox').

Une fois que la boîte est créée et que vous avez vérifié pour vous assurer qu'elle a été recalculée, sélectionnez l'objet et regardez ses propriétés. Vous devez noter deux choses:

  • Un nouveau groupe de propriétés: Dimensions.
  • Trois nouvelles propriétés: Height, Length et Width.

Notez également que les propriétés ont des unités. Plus précisément, elles ont repris les unités linéaires définies dans les préférences de l'utilisateur (Edition → Préférences... → Général → Unités).

Vous avez sans doute remarqué que trois valeurs différentes ont été saisies pour les dimensions : une valeur en virgule flottante (10.0) et deux chaînes de caractères différentes ('10 mm' et '1 cm'). Le type App::PropertyLength suppose que les valeurs en virgule flottante sont en millimètres, les valeurs des chaînes sont analysées en fonction des unités spécifiées et, dans l'interface graphique, toutes les valeurs sont converties dans les unités spécifiées dans les préférences de l'utilisateur (mm dans l'image). Ce comportement intégré rend le type App::PropertyLength idéal pour les dimensions.

En haut

Piège à événements

Le dernier élément requis pour un objet FeaturePython de base est le recouvrement d'événements. Un objet FeaturePython peut réagir aux événements avec des fonctions de rappel. Dans notre cas, nous voulons que l'objet réagisse chaque fois qu'il est recalculé. En d'autres termes, nous voulons intercepter les recalculs. Pour ce faire, nous devons ajouter une fonction avec un nom spécifique, execute(), à la classe d'objets. Il existe plusieurs autres événements qui peuvent être piégés, à la fois dans l'objet FeaturePython lui-même et dans le ViewProvider, que nous aborderons dans la Création d'une boîte FeaturePython, partie II.

Pour une référence complète des méthodes disponibles à implémenter sur les classes FeautrePython, voir Méthodes FeaturePython.

Ajoutez ce qui suit après la fonction __init__():

def execute(self, obj):
    """
    Called on document recompute
    """

    print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type))

Testez le code en suivant à nouveau ces étapes:

  • Enregistrez et rechargez le module.
  • Supprimer tous les objets.
  • Créez un nouvel objet boîte.

Vous devriez voir la sortie imprimée dans la console Python, grâce à l'appel recompute() que nous avons ajouté à la méthode create(). Bien sûr, la méthode execute() ne fait rien ici, sauf nous dire qu'elle a été appelée, mais elle est la clé de la magie des objets FeaturePython.

Voilà, vous savez maintenant comment construire un objet FeaturePython basique et fonctionnel !

En haut

Le code terminé

import FreeCAD as App

def create(obj_name):
    """
    Object creation method
    """

    obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)

    box(obj)

    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
        """

        print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type))

En haut