Créer un objet FeaturePython partie I

From FreeCAD Documentation
Revision as of 15:28, 27 December 2019 by David69 (talk | contribs) (Created page with "'''La méthode de découpage <code>create()</code>''' {|class="wikitable" cellpadding="5" style="float:right; margin-left: 15px; margin-right: 5px" |style="width:30%" | <code>...")
Other languages:
PySide
Creating a FeaturePython Box, Part II

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.

Cela favorise:

  • 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.
  • 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.

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.

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.

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 embedding VBA macros in Microsoft Office documents.

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.


Configuration de votre environnement de développement

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!

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:

  • Windows: tapez '%APPDATA%/FreeCAD/Macro' dans la barre de chemin de fichier en haut de l'Explorateur
  • Linux: accédez à /home/USERNAME/.FreeCAD/Macro
  • Mac: accédez à /Users/USERNAME/Library/Preferences/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 de la boîte, créez deux fichiers: __init__.py et box.py (laissez les deux vides pour l'instant)

Remarques:

  • 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 indique à Python que dans le dossier se trouve un module importable et box.py sera le fichier de classe pour notre nouvel objet FeaturePython.

La structure de votre répertoire devrait ressembler à ceci:

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

Avec nos chemins de modules et fichiers créés, assurez-vous que FreeCAD est correctement configuré:

  • 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 Débuter avec les scripts)
  • Dans votre éditeur de code préféré, accédez au dossier /Macro/fpo/box et ouvrez box.py

Il est temps d'écrire du code!



Un objet FeaturePython très basique

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

 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 pas-à-pas __init__()

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.


Dans le fichier box.py en haut, ajoutez le code suivant:

 import FreeCAD as App

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

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

     fpo = box(obj)

     return obj


La méthode de découpage create()

import FreeCAD as App Importation standard pour la plupart des scripts python. L'alias App n'est pas requis.
obj = ... addObject(...) 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'.
fpo = box(obj) Créez l'instance de classe personnalisée et liez-la à l'objet FeaturePython.


The create() method is not required, but it provides a nice way to encapsulate the object creation code.



Testing the 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

In the Python Console, type the following:

>>> from fpo.box import box


Now, we need to create our object:

>>> box.create('my_box')

You should see a new object appear in the tree view at the top left labelled my_box.

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.
There's not very much - just the name of the object. We'll need to add some properties in a bit.
Let's also make referencing our new object a little more convenient:

>>> mybox = App.ActiveDocument.my_box

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

>>> dir(mybox)
['Content', 'Document', 'ExpressionEngine', 'InList', 'InListRecursive', 'Label', 'MemSize', 'Module', 'Name', 'OutList', 'OutListRecursive', 'PropertiesList', 'Proxy', 'State', 'TypeId', 'ViewObject', '__class__', 
 ...
 'setEditorMode', 'setExpression', 'supportedProperties', 'touch']


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 create() method. The Proxy property we added in our __init__() method is there, too.

Let's inspect that by calling the dir()on the Proxy object:

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


Once we inspect the Proxy property, we can see our Object and Type properties. This means we're accessing the custom Python object defined in box.py.

Call the Type property and look at the result:

>>> mybox.Proxy.Type
'box'

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

Likewise, we can access the FreeCAD object (not our Python object) by using theObjectmethod:

>>> mybox.Proxy.Object

That was fun! But now let's see if we can make our class a little more interesting... and maybe more useful.



Adding Properties

Properties are the lifeblood of a FeaturePython class.

Fortunately, FreeCAD supports 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.

Adding properties is done quite simply using the add_property() method. The syntax for the method is:

add_property(type, name, section, description)
Tip
You can view the list of supported properties for an object by typing:
>>> mybox.supportedProperties()


Let's try adding a property to our box class.

Switch to your code editor and move to the __init__() method.


Then, at the end of the method, add:

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

Note how we're using the reference to the (serializable) FeaturePython object, obj and not the (non-serializable) Python class instanace, self.
Anyway, once you're done, save the changes and switch back to FreeCAD.

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:

>>> from importlib import reload
>>> reload(box)

This will reload the box module, incorporating changes you made to the box.py file, just as if you'd restarted FreeCAD.

With the module reloaded, now let's see what we get when we create an object:

>>> box.create('box_property_test')
File:Fpo properties.png

You should see the new box object appear in the tree view at left.

  • Select it and look at the Property Panel. There, you should see the 'Description' property.
  • Hover over the property name at left and see the tooltip appear with the description text you provided.
  • 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.



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.

Return to your source code and add the following properties to __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'
File:Object recompute icon.png

One last thing: Did you notice how the blue checkmark appears next to the FeaturePython object in the treeview at left?

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.

But, we can accomplish that automatically by adding the following line to the end of the create() method:

App.ActiveDocument.recompute()
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.


Now, test your changes as follows:

  • Save your changes and return to FreeCAD.
  • Delete any existing objects and reload your module.
  • Finally, create another box object from the command line by calling >>> box.create('myBox').


Once the box is created (and you've checked to make sure it's been recomputed!), select the object and look at your properties.
You should note two things:

  • Three new properties (length, width, and height)
  • A new property group, Dimensions.


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).

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.

The nice thing about the App::PropertyLength 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 App::PropertyLength as the property type.



Event Trapping

The last element required for a basic FeaturePython object is event trapping. Specifically, we need to trap the execute() 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.
Add the following after the __init__() function:

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

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


Test the code as follows:

  • Save changes and reload the box module in the FreeCAD python console.
  • Delete any objects in the Treeview
  • Re-create the box object.

You should see the printed output in the Python Console, thanks to the recompute() call we added to the create() method.

Of course, the execute() method doesn't do anything here (except tell us that it was called), but it is the key to the magic of FeaturePython objects.


So that's it!
You now know how to build a basic, functional FeaturePython object!



The Completed Code

import FreeCAD as App

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

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

   fpo = box(obj)

   return obj

class box():

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

       self.Type = 'box'

      obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = ""
      obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0
      obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm'
      obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm'

       obj.Proxy = self

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

       print('Recomputing {0:s} {1:s}'.format(obj.Name, self.Type))
PySide
Creating a FeaturePython Box, Part II