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

From FreeCAD Documentation
(Created page with "méthode. La propriété <code>Proxy</code> que nous avons ajoutée dans notre méthode <code>__init__()</code> est également là.")
No edit summary
 
(151 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==
Les objets FeaturePython (également appelés «objets scriptés») offrent aux utilisateurs la possibilité d'étendre FreeCAD avec des objets qui s'intègrent de manière transparente dans le framework FreeCAD.

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.


Cela favorise:
Cela favorise:
* '''Le prototypage rapide''' de nouveaux objets et outils avec des classes personnalisées de Python.
*Le prototypage rapide de nouveaux objets et outils avec des classes personnalisées de Python.
* '''La sérialisation''' via les objets 'App::Property' ''sans incorporer de scripts'' dans le fichier document FreeCAD.
* 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!
*Une liberté créative pour adapter FreeCAD à n'importe quelle tâche.


Ce wiki vous fournira une compréhension complète de l'utilisation des objets FeaturePython et des classes Python personnalisées dans FreeCAD. Nous allons construire un exemple complet et fonctionnel d'une classe personnalisée FeaturePython, identifiant tous les principaux composants et comprendre de manieère fine dont tout cela fonctionne au fur et à mesure.
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.


<span id="How_does_it_work?"></span>
=== Comment ça marche? ===
== Comment ça marche? ==


FreeCAD est fourni 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 la personnalisation de l'utilisateur avec une classe python personnalisée.
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.


La classe personnalisée de Python prend simplement une référence à l'un de ces objets et le modifie de plusieurs façons. Par exemple, la classe Python peut ajouter des propriétés directement à l'objet, en modifiant d'autres propriétés lorsqu'il est recalculé ou en le liant à d'autres objets. De plus, la classe Python implémente certaines méthodes pour lui permettre de répondre aux événements du document, ce qui permet de piéger les changements de propriétés des objets et les recalculs du document.
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.


Il est important de se rappeler, cependant, que pour autant que l'on puisse accomplir avec des classes personnalisées et des objets FeaturePython, quand vient le temps d'enregistrer le document, '''seul l'objet FeaturePython lui-même est sérialisé'''. La classe personnalisée et son état ne sont pas conservés entre les rechargements de documents. Cela nécessiterait d'incorporer un script dans le fichier de document FreeCAD, ce qui pose un risque de sécurité important, tout comme les risques posés par [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.


[[#top|En haut]]
Ainsi, un objet FeaturePython existe finalement en dehors de son script. L'inconvénient posé par le fait de ne pas empaqueter le script avec l'objet dans le fichier de document est bien moindre que le risque posé par l'exécution d'un fichier incorporé avec un script inconnu. Cependant, le chemin du module de script est stocké dans le fichier de document. Par conséquent, un utilisateur n'a qu'à installer le code de classe python personnalisé en tant que module importable suivant la même structure de répertoires pour retrouver la fonctionnalité perdue.


<span id="Setting_things_up"></span>
== 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!
== Configuration de votre environnement de développement ==


Si vous ne savez pas où se trouve le dossier des Macro de FreeCAD, tapez {{incode|FreeCAD.getUserMacroDir(True)}} dans la [[Python_console/fr|console Python]] de FreeCAD :
Pour commencer, 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!
* 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}}).
* Sous Windows, c'est {{FileName|%APPDATA%\FreeCAD\Macro\}}, généralement {{FileName|C:\Users\<username>\Appdata\Roaming\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 :
Si vous ne savez pas où se trouve le dossier Macro FreeCAD, saisissez 'FreeCAD.getUserMacroDir(True)' dans la console Python de FreeCAD. L'endroit est configurable mais, par défaut, pour s'y rendre:
*Dans le dossier {{FileName|Macro}}, créez un nouveau dossier appelé {{FileName|fpo}}.
* Windows: tapez '%APPDATA%/FreeCAD/Macro' dans la barre de chemin de fichier en haut de l'Explorateur
*Dans le dossier {{FileName|fpo}}, créez un fichier vide : {{FileName|__init__.py}}.
* Linux: accédez à /home/USERNAME/.FreeCAD/Macro
*Dans le dossier {{FileName|fpo}}, créez un nouveau dossier appelé {{FileName|box}}.
* Mac: accédez à /Users/USERNAME/Library/Preferences/FreeCAD/Macro
*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 :
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 de la boîte, créez deux fichiers: '''__init__.py''' et '''box.py''' (laissez les deux vides pour l'instant)


Macro/
Remarques:
|--> fpo/
* Le dossier '''fpo''' fournit un endroit propice pour jouer avec les nouveaux objets FeaturePython et le dossier '''box''' est le module dans lequel nous travaillerons.
|--> __init__.py
* ''''__init__.py''' indique à Python que dans le dossier se trouve un module importable et '''box.py''' sera le fichier de classe pour notre nouvel objet FeaturePython.
|--> box/
|--> __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.
La structure de votre répertoire devrait ressembler à ceci:


Une fois nos chemins et fichiers de module créés, nous assurons que FreeCAD est correctement configuré :
.FreeCAD
* Démarrez FreeCAD (si vous ne l'avez pas déjà fait).
|--> Macro
* Activez la [[Report_view/fr|Vue rapport]] ({{MenuCommand|Affichage → Panneaux → Vue rapport}}).
|--> fpo
* Activez la [[Python_console/fr|console Python]] ({{MenuCommand|Affichage → Panneaux → Console Python}}) voir [[FreeCAD_Scripting_Basics/fr|FreeCAD Scripting Basics]].
|--> __init__.py
|--> box
|--> __init__.py
|--> box.py


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.
Avec nos chemins de modules et fichiers créés, assurez-vous que FreeCAD est correctement configuré:


[[#top|En haut]]
* Démarrez FreeCAD (s'il n'est pas déjà ouvert)
* Activer la console Python et les vues de rapport ('''Affichage -> Panneaux -> Vue rapport''' et '''console Python''') [[FreeCAD Scripting Basics/fr|(FreeCAD Débuter avec les scripts)]]
* Dans votre éditeur de code préféré, accédez au dossier '''/Macro/fpo/box''' et ouvrez '''box.py'''


<span id="A_FeaturePython_object"></span>
Il est temps d'écrire du code!
==Un objet FeaturePython==


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


{{Code|code=
-----
class box():


def __init__(self, obj):
== Un objet FeaturePython très basique ==
"""
Default constructor
"""


self.Type = 'box'
Commençons par écrire notre classe et son constructeur:


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


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


{|class="wikitable" cellpadding="5px" width="100%"
'''La méthode breakdown <code>__init__()</code>'''
|style="width:25%" | {{incode|def __init__(self, obj):}}
{|class="wikitable" cellpadding="5" style="margin-left: 15px; margin-right: 5px"
|style="width:30%" | <code>def __init__(self, obj):</code> || Les paramètres font référence à la classe Python elle-même et à l'objet FeaturePython auquel elle est attachée.
|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> || Définition de chaîne du type Python personnalisé.
| Définition de chaîne du type Python personnalisé.
|-
|-
| {{incode|obj.Proxy <nowiki>=</nowiki> self}}
|<code> obj.Proxy = self</code> || Stocke une référence à l'instance Python dans l'objet FeaturePython.
| Stocke une référence à l'instance Python dans l'objet FeaturePython.
|}
|}


Ajoutez le code suivant en haut du fichier :


{{Code|code=
Dans le fichier '''box.py''' en haut, ajoutez le code suivant:
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 obj


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


box(obj)
'''La méthode breakdown <code>create()</code>'''

{|class="wikitable" cellpadding="5" style="float:right; margin-left: 15px; margin-right: 5px"
return obj
|style="width:30%" | <code>import FreeCAD as App</code> || Importation standard pour la plupart des scripts python. L'alias '''App''' n'est pas requis.
}}

'''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> || Crée un nouvel objet FreeCAD FeaturePython avec le nom transmis à la méthode. S'il n'y a pas de conflit de noms, ce sera l'étiquette et le nom de l'objet créé, c'est-à-dire ce qui est visible dans l'arbre du modèle. Sinon, un nom et une étiquette uniques seront créés sur la base de 'nom_obj'.
| Crée notre instance de classe personnalisée.
|-
|-
| {{incode|return obj}}
|<code>fpo = box(obj)</code> || Créez l'instance de classe personnalisée et liez-la à l'objet FeaturePython.
| 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]]
La méthode <code>create()</code> n'est pas requise mais elle fournit un bon moyen d'encapsuler le code de création d'objet.


<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 :
=== Test du code ===


{{Code|code=
Maintenant, nous pouvons essayer notre nouvel objet. Enregistrez votre code et revenez à FreeCAD, assurez-vous d'avoir '''ouvert un nouveau document'''. Vous pouvez le faire en appuyant sur '''CTRL+n''' ou en sélectionnant '''Fichier -> Nouveau'''
from fpo.box import box
}}


Maintenant, nous devons créer notre objet :
Dans la console Python, tapez ce qui suit:


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


[[Image:Fpo_treeview.png|right]]
<br>Maintenant, nous devons créer notre objet:
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}}
Vous devriez voir un nouvel objet apparaître dans l'arborescence en haut à gauche intitulé '''my_box'''.<br><br>Notez que l'icône est grise. FreeCAD nous dit simplement que l'objet n'est pas en mesure d'afficher quoi que ce soit dans la vue 3D... pour l'instant. Cliquez sur l'objet et notez ce qui apparaît dans le panneau de propriétés en dessous. <br>Il n'y a pas grand-chose juste le nom de l'objet. Nous devrons ajouter quelques propriétés sous peu.
<br clear="all">
Rendons également le référencement de notre nouvel objet un peu plus pratique:


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


Cela retournera :
Et puis nous devrions jeter un oeil aux attributs de notre objet:


{{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()}} :
Il y a beaucoup d'attributs parce que nous accédons à l'objet FreeCAD FeaturePyton natif que nous avons créé dans la première ligne de notre <code>create()</code> méthode.
La propriété <code>Proxy</code> que nous avons ajoutée dans notre méthode <code>__init__()</code> est également là.


{{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 obj
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