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

From FreeCAD Documentation
(Created page with "Jusqu'à présent, nous n'avons pas abordé explicitement le piégeage d'événements. Presque toutes les méthodes d'une classe FeaturePython servent de rappel accessible à...")
(Updating to match new version of source page)
 
(55 intermediate revisions by 3 users not shown)
Line 1: Line 1:
<languages/>
<languages/>


{{docnav/fr
{{Docnav/fr
|[[FeaturePython Objects/fr|Objects FeaturePython]]
|[[Create_a_FeaturePython_object_part_I/fr|Créer un objet FeaturePython partie I]]
|
|[[Scripting examples/fr|Exemples de Scripts]]
|IconL=
|IconR=
}}
}}


{{TOCright}}
=== App::FeaturePython vs. Part::FeaturePython ===


<span id="Introduction"></span>
Jusqu'à présent, nous nous sommes concentrés sur les aspects internes d'une classe Python construite autour d'un objet FeaturePython - en particulier, un objet App::FeaturePython object.<br>
== Introduction ==
Nous avons créé l'objet, défini certaines propriétés et ajouté des rappels d'événements au niveau du document qui permettent à notre objet de répondre à un recalcul de document avec la méthode {{incode|execute()}}.


Sur la page [[Create_a_FeaturePython_object_part_I/fr|Créer un objet FeaturePython partie I]], nous nous sommes concentrés sur les aspects internes d'une classe Python construite autour d'un objet FeaturePython, en particulier un objet {{incode|App::FeaturePython}}. Nous avons créé l'objet, défini certaines propriétés et ajouté un rappel d'événement au niveau du document qui permet à notre objet de répondre à un recalcul de document avec la méthode {{incode|execute()}}. Mais notre objet n'a toujours pas de présence dans la [[3D_view/fr|Vue 3D]]. Remédions à cela en ajoutant une boîte.
'''Mais nous n'avons toujours pas de boîte.'''


<span id="Adding_a_box"></span>
Cependant, nous pouvons facilement en créer une en ajoutant seulement deux lignes.
==Ajout d'une boîte==


Tout d'abord, ajoutez une nouvelle importation en haut du fichier: {{incode|import Part}}
Tout d'abord, en haut du fichier {{FileName|box.py}}, sous l'importation existante, ajoutez:

Ensuite, dans {{incode|execute()}}, ajoutez la ligne suivante (vous pouvez supprimer l'instruction {{incode|print()}}):


{{Code|code=
{{Code|code=
import Part
def execute():
"""
Called on document recompute
"""

Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height)
}}
}}


Puis dans {{incode|execute()}} supprimez l'instruction {{incode|print()}} et ajoutez la ligne suivante à sa place:
Ces commandes exécutent des scripts python fournis avec FreeCAD par défaut.
* La méthode {{incode|makeBox()}} génère une nouvelle forme de boîte.
* L'appel englobant à {{incode|Part.show()}} ajoute la forme à l'arborescence du document et la rend visible.


{{Code|code=
Si FreeCAD est ouvert, rechargez le module boîte et créez un nouvel objet boîte en utilisant {{incode|box.create()}} (supprimez tous les objets boîte existants, juste pour garder les choses propres).
Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))
}}


[[Image:App_featurepython_box.png | right]]
Remarquez comment une boîte apparaît immédiatement à l'écran: la méthode {{incode|execute()}} est appelée immédiatement après la création de la boîte et nous forçons le document à recalculer à la fin de {{incode|box.create()}}.


Ces commandes exécutent des méthodes Python fournies avec FreeCAD par défaut.
* La méthode {{incode|Part.makeBox()}} génère une nouvelle forme de boîte.
* L'appel englobant à {{incode|Part.show()}} ajoute la forme à l'arborescence du document et la rend visible.


Supprimez tous les objets existants, rechargez le module boîte et créez un nouvel objet boîte en utilisant {{incode|box.create()}}. Remarquez comment une boîte apparaît immédiatement à l'écran. C'est parce que nous forçons le document à recalculer à la fin de {{incode|box.create()}} qui à son tour déclenche la méthode {{incode|execute()}} de notre classe {{incode|box}}.
[[File:App_featurepython_box.png]]


À première vue, le résultat peut sembler correct mais il y a quelques problèmes. Le plus évident est que la boîte est représentée par un objet entièrement différent de notre objet FeaturePython. {{incode|Part.show()}} crée simplement un objet boîte séparé et l'ajoute au document. Pire encore, si vous modifiez les dimensions de l'objet FeaturePython, une autre forme de boîte est créée et l'ancienne est laissée en place. Et si vous avez ouvert la [[Report_view/fr|Vue rapport]], vous avez peut-être remarqué une erreur indiquant "Appel récursif de recalcul pour le document Sans nom". Cela a à voir avec l'utilisation de la méthode {{incode|Part.show()}} dans un objet FeaturePython.
'''Mais il y a un problème.'''
{{clear}}


[[#top|En haut]]
Cela devrait être assez évident. La boîte elle-même est représentée par un objet entièrement différent de notre objet FeaturePython. La raison en est que {{incode|Part.show()}} crée un objet boîte séparé et l'ajoute au document. En fait, si vous accédez à votre objet FeaturePython et modifiez les dimensions, vous verrez une autre forme de boîte créée et l'ancienne laissée en place. Ce n'est pas bon! De plus, si la vue du rapport est ouverte, vous pouvez remarquer une erreur indiquant 'Les recalculs imbriqués d'un document ne sont pas autorisés'. Cela a à voir avec l'utilisation de la méthode Part.show() dans un objet FeaturePython. Nous voulons éviter de faire cela.


<span id="Fixing_the_code"></span>
'''Astuce'''
==Correction du code==


Pour résoudre ces problèmes, nous devons apporter un certain nombre de changements. Jusqu'à présent, nous avons utilisé un objet {{incode|App::FeaturePython}} qui n'est en fait pas destiné à avoir une représentation visuelle dans la vue 3D. Nous devons utiliser un objet {{incode|Part::FeaturePython}} à la place. Dans {{incode|create()}} modifiez la ligne suivante:
Le problème est que nous nous appuyons sur les méthodes {{incode|Part.make*()}}, qui ne génèrent que des objets Part::Feature non paramétriques (formes simples et stupides), tout comme vous obtiendriez si vous copiez un objet paramétrique à l'aide de [[Part SimpleCopy/fr|Part Copie simple]].


{{Code|code=
Ce que nous voulons, bien sûr, est un objet boîte '''paramétrique''' qui redimensionne la boîte existante lorsque nous modifions ses propriétés. Nous pourrions supprimer l'objet Part::Feature précédent et le régénérer à chaque fois que nous modifions une propriété, mais nous avons encore deux objets à gérer - notre objet FeaturePython personnalisé et l'objet Part::Feature qu'il génère.
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)
}}


par:
'''Alors, comment pouvons-nous résoudre ce problème?'''


{{Code|code=
Tout d'abord, nous devons utiliser le bon type d'objet.
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name)
}}


Pour se débarrasser de l'objet box séparé, nous devons affecter le résultat de la méthode {{incode|makeBox()}} à la propriété {{incode|Shape}} de notre objet {{incode|Part::FeaturePython}}. Modifiez cette ligne dans {{incode|execute()}}:
Jusqu'à présent, nous avons utilisé des objets {{incode|App::FeaturePython}}. Ils sont pas mal mais ils ne sont pas destinés à être utilisés comme objets de géométrie. Ils sont plutôt mieux utilisés comme objets de document qui ne nécessitent pas de représentation visuelle dans la vue 3D. Nous devons donc utiliser un objet {{incode|Part::FeaturePython}} à la place.


{{Code|code=

Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))
Dans {{incode|create()}}, modifiez la ligne suivante:
}}

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

pour lire:

obj = App.ActiveDocument.addObject('{{ColoredText||red|Part::FeaturePython}}', obj_name)

Pour terminer les modifications dont nous avons besoin, la ligne suivante de la méthode {{incode|execute()}} doit être modifiée:

Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))


en:
en:


{{Code|code=
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

}}
Votre code devrait ressembler à ceci:

import FreeCAD as App
import Part
def create(obj_name):
"""
Object creation method
"""
obj = App.ActiveDocument.addObject('Part::FeaturePython', obj_name)
fpo = box(obj)
App.ActiveDocument.recompute()
return fpo
class box():
def __init__(self, obj):
"""
Default Constructor
"""
self.Type = 'box'
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = ""
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm'
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm'
obj.Proxy = self
self.Object = obj
def execute(self, obj):
"""
Called on document recompute
"""
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)


[[File:Part_featurepython_no_vp.png|right]]
[[File:Part_featurepython_no_vp.png|right]]


Enregistrez vos modifications, revenez à FreeCAD, supprimez tous les objets existants, rechargez le module de boîte et créez un nouvel objet boîte. Le nouveau résultat est quelque peu décevant. Il n'y a plus d'objet supplémentaire dans l'arborescence, et l'icône dans l'arborescence a changé, mais notre boîte dans la vue 3D a également disparu (c'est pourquoi l'icône est grise). Qu'est ce qui c'est passé? Bien que nous ayons correctement créé notre forme de boîte et l'avons assignée à un objet {{incode|Part::FeaturePython}}, pour le faire apparaître réellement dans la vue 3D, nous devons attribuer un [[Viewprovider/fr|ViewProvider]].
Maintenant, enregistrez vos modifications et revenez à FreeCAD. Supprimez tous les objets existants, rechargez le module de boîte et créez un nouvel objet de boîte.
{{clear}}


[[#top|En haut]]
Les résultats peuvent sembler un peu mitigés. L'icône dans l'arborescence est différente - c'est une boîte, maintenant. Mais il n'y a pas de cube. Et l'icône est toujours grise!


<span id="Writing_a_ViewProvider"></span>
Qu'est-il arrivé? Bien que nous ayons correctement créé notre forme de boîte et l'avons affectée à un objet {{incode|Part::FeaturePython}}, avant de pouvoir l'afficher dans notre vue 3D, nous devons attribuer un '''ViewProvider.'''
==Écrire un ViewProvider==


Un fournisseur de vue (View Provider) est le composant d'un objet qui lui permet d'avoir une représentation visuelle dans la vue 3D. FreeCAD utilise une structure d'application conçue pour séparer les données (le "modèle") de sa représentation visuelle (la "vue"). Si vous avez passé du temps à travailler avec FreeCAD en Python, vous en êtes probablement déjà conscient grâce à l'utilisation des deux modules Python de base: {{incode|FreeCAD}} et {{incode|FreeCADGui}} (souvent sous les alias { {incode|App}} et {{incode|Gui}} respectivement).
=== Écrire un ViewProvider ===


Notre objet FeaturePython nécessite également ces éléments. Jusqu'à présent, nous nous sommes concentrés uniquement sur la partie "modèle" du code, il est maintenant temps d'écrire la partie "vue". Heureusement, la plupart des ViewProviders sont simples et nécessitent peu d'efforts pour écrire, du moins pour commencer. Voici un exemple de ViewProvider emprunté et légèrement modifié à [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py]:
Un fournisseur de vues est le composant d'un objet qui lui permet d'avoir une représentation visuelle dans l'interface graphique - en particulier dans la vue 3D. FreeCAD utilise une structure d'application appelée 'vue du modèle' qui est conçue pour séparer les données (le 'modèle') de sa représentation visuelle (la «vue»). Si vous avez passé du temps à travailler avec FreeCAD en Python, vous en serez probablement déjà conscient grâce à l'utilisation de deux modules Python principaux: FreeCAD et FreeCADGui (souvent aliasés respectivement «App» et «Gui»).


{{Code|code=
Ainsi, notre implémentation de FeaturePython Box nécessite également ces éléments. Jusqu'à présent, nous nous sommes concentrés uniquement sur la partie «modèle», il est donc temps d'écrire la «vue». Heureusement, la plupart des implémentations de vues sont simples et nécessitent peu d'efforts pour écrire, au moins pour commencer. Voici un exemple de ViewProvider, emprunté et légèrement modifié de [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py]
class ViewProviderBox:


class ViewProviderBox:
def __init__(self, obj):
def __init__(self, obj):
"""
"""
Set this object to the proxy object of the actual view provider
Set this object to the proxy object of the actual view provider
"""
"""

obj.Proxy = self
obj.Proxy = self

def attach(self, obj):
def attach(self, obj):
"""
"""
Line 140: Line 99:
"""
"""
return
return

def updateData(self, fp, prop):
def updateData(self, fp, prop):
"""
"""
Line 146: Line 105:
"""
"""
return
return

def getDisplayModes(self,obj):
def getDisplayModes(self,obj):
"""
"""
Return a list of display modes.
Return a list of display modes.
"""
"""
return None
return []

def getDefaultDisplayMode(self):
def getDefaultDisplayMode(self):
"""
"""
Line 158: Line 117:
"""
"""
return "Shaded"
return "Shaded"

def setDisplayMode(self,mode):
def setDisplayMode(self,mode):
"""
"""
Line 166: Line 125:
"""
"""
return mode
return mode

def onChanged(self, vp, prop):
def onChanged(self, vp, prop):
"""
"""
Print the name of the property that has changed
Print the name of the property that has changed
"""
"""

App.Console.PrintMessage("Change property: " + str(prop) + "\n")
App.Console.PrintMessage("Change property: " + str(prop) + "\n")

def getIcon(self):
def getIcon(self):
"""
"""
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
"""
"""

return """
return """
/* XPM */
/* XPM */
static const char * ViewProviderBox_xpm[] = {
static const char * ViewProviderBox_xpm[] = {
"16 16 6 1",
"16 16 6 1",
" c None",
" c None",
". c #141010",
". c #141010",
"+ c #615BD2",
"+ c #615BD2",
"@ c #C39D55",
"@ c #C39D55",
"# c #000000",
"# c #000000",
"$ c #57C355",
"$ c #57C355",
" ........",
" ........",
" ......++..+..",
" ......++..+..",
Line 206: Line 165:
" ####### "};
" ####### "};
"""
"""

def __getstate__(self):
def dumps(self):
"""
"""
Called during document saving.
Called during document saving.
"""
"""
return None
return None

def __setstate__(self,state):
def loads(self,state):
""""
"""
Called during document restore.
Called during document restore.
""""
"""
return None
return None
}}


Remarquez dans le code ci-dessus, nous définissons également une icône XMP pour cet objet. La conception d'icônes dépasse le cadre de ce didacticiel, mais les conceptions d'icônes de base peuvent être gérées à l'aide d'outils open source tels que [https://www.gimp.org GIMP], [https://krita.org/fr/ Krita] et [https://inkscape.org/ Inkscape]. La méthode getIcon est également facultative. S'il n'est pas fourni, FreeCAD fournira une icône par défaut.
Dans le code ci-dessus, nous définissons une icône XMP pour cet objet. La conception des icônes dépasse le cadre de ce tutoriel mais la conception de base peut être gérée à l'aide d'outils open source tels que [https://www.gimp.org GIMP], [https://krita.org/en/ Krita] et [ https://inkscape.org/ Inkscape]. La méthode {{incode|getIcon()}} est facultative, FreeCAD utilisera une icône par défaut si cette méthode n'est pas fournie.


Ajoutez le code ViewProvider à la fin de {{FileName|box.py}} et dans la méthode {{incode|create()}} insérez la ligne suivante au-dessus de l'instruction {{incode|recompute()}}:


{{Code|code=
Sans un ViewProvider défini, nous devons maintenant l'utiliser pour donner à notre objet la possibilité de la visualisation.

Revenez à la méthode {{incode|create()}} dans votre code et ajoutez ce qui suit vers la fin:

ViewProviderBox(obj.ViewObject)
ViewProviderBox(obj.ViewObject)
}}


Cette instance de la classe ViewProvider personnalisée lui transmet le ViewObject intégré de FeaturePython. Le ViewObject ne fera rien sans notre implémentation de la classe personnalisée donc, quand la classe ViewProvider s'initialise, elle enregistre une référence à elle-même dans l'attribut ViewObject.Proxy de FeaturePython. De cette façon, lorsque FreeCAD a besoin de rendre notre Box visuellement, il peut trouver la classe ViewProvider pour le faire.
Cela instancie la classe ViewProvider personnalisée et lui transmet le ViewObject intégré de FeaturePython. Lorsque la classe ViewProvider s'initialise, elle enregistre une référence à elle-même dans l'attribut {{incode|ViewObject.Proxy}} de FeaturePython. De cette façon, lorsque FreeCAD a besoin de rendre notre boîte visuellement, il peut trouver la classe ViewProvider pour le faire.


Maintenant, enregistrez les modifications et revenez à FreeCAD. Importez ou rechargez le module Box et appelez {{incode|box.create()}}.
Maintenant, enregistrez les modifications et revenez à FreeCAD. Importez ou rechargez le module box et appelez {{incode|box.create()}}. Vous devriez maintenant voir deux choses:
* L'icône de l'objet boîte a changé.
* Et, plus important encore, il y a une boîte dans la vue 3D. Si vous ne le voyez pas, appuyez sur le bouton {{Button|[[Image:Std_ViewFitAll.svg|16px]] [[Std_ViewFitAll/fr|Std Tout afficher]]}}. Vous pouvez même modifier les dimensions de la boîte en modifiant les valeurs dans l'[[Property_editor/fr|Éditeur de propriétés]]. Essayez!


[[#top|En haut]]
Nous ne voyons toujours rien, mais notez ce qui est arrivé à l'icône à côté de l'objet boîte. C'est en couleur! Et ça a une forme! C'est un indice que notre ViewProvider fonctionne comme prévu.


<span id="Trapping_events"></span>
Alors maintenant, il est temps de réellement *ajouter* une boîte.
==Piégeage d'événements==


Nous avons déjà discuté du piégeage d'événements. Presque toutes les méthodes d'une classe FeaturePython servent de rappel accessible à l'objet FeaturePython (qui obtient l'accès à notre instance de classe via l'attribut {{incode|Proxy}}, si vous vous en souvenez).
Revenez au code et ajoutez la ligne suivante à la méthode execute():


Voici une liste des rappels pouvant être implémentés dans l'objet FeaturePython de base:
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)


{| class="wikitable" cellpadding="5px" width="100%"
Maintenant, enregistrez, rechargez le module de boîte dans FreeCAD et créez une nouvelle boîte.
|+ FeaturePython basic callbacks

|style="width:25%" | {{incode|execute(self, obj)}}
Vous devriez voir une boîte apparaître à l'écran! Si elle est trop grande (ou ne s'affiche pas du tout), cliquez sur l'un des boutons 'ViewFit' pour l'adapter à la vue 3D.
|style="width:25%" | Appelé pendant le recalcul du document

|style="width:50%" | N'appelez pas {{incode|recompute()}} depuis cette méthode (ou toute méthode appelée depuis {{incode|execute()}}) car cela provoque un recalcul imbriqué.
Notez ce que nous avons fait ici qui diffère de la façon dont il a été implémenté pour App::FeaturePython ci-dessus. Dans la méthode {{incode|execute()}}, nous avons appelé la macro {{incode|Part.makeBox()}} et lui avons transmis les propriétés de la boîte comme précédemment. Mais plutôt que d'appeler {{incode|Part.show()}} sur l'objet résultant (qui a créé un nouvel objet boîte séparé), nous l'avons simplement affecté à la propriété {{incode|Shape}} de notre Part::FeaturePython objet à la place.

Vous pouvez même modifier les dimensions de la boîte en modifiant les valeurs dans le panneau de propriétés FreeCAD. Essayez!

=== Piégeage d'événements ===

Jusqu'à présent, nous n'avons pas abordé explicitement le piégeage d'événements. Presque toutes les méthodes d'une classe FeaturePython servent de rappel accessible à l'objet FeaturePython (qui obtient l'accès à notre instance de classe via l'attribut {{incode|Proxy}}, si vous vous en souvenez).

Below is a list of the callbacks that may be implemented in the basic FeaturePython object:

{| class="wikitable"

|+style="caption-side:bottom; | FeaturePython basic callbacks
|{{incode|execute(self, obj)}} || Called during document recomputes || Do not call {{incode|recompute()}} from this method (or any method called from {{incode|execute()}}) as this causes a nested recompute.
|-
|-
| {{incode|onBeforeChange(self, obj, prop)}}
|{{incode|onBeforeChanged(self, obj, prop)}} || Called before a property value is changed || {{incode|prop}} is the name of the property to be changed, not the property object itself. Property changes cannot be cancelled. Previous / next property values are not simultaneously available for comparison.
| Appelé avant la modification d'une valeur de propriété
| {{incode|prop}} est le nom de la propriété à modifier, pas l'objet de propriété lui-même. Les modifications de propriété ne peuvent pas être annulées. Les valeurs de propriétés précédentes/suivantes ne sont pas disponibles simultanément pour la comparaison.
|-
|-
|{{incode|onChanged(self, obj, prop)}} || Called after a property is changed || {{incode|prop}} is the name of the property to be changed, not the property object itself.
| {{incode|onChanged(self, obj, prop)}}
| Appelé après la modification d'une propriété
| {{incode|prop}} est le nom de la propriété à modifier, pas l'objet de propriété lui-même.
|-
|-
| {{Incode|onDocumentRestored(self, obj)}}
|{{Incode|onDocumentRestored(self, obj)}} || Called after a document is restored or aFeaturePython object is copied / duplicated. || Occasionally, references to the FeaturePython object from the class, or the class from the FeaturePython object may be broken, as the class {{incode|__init__()}} method is not called when the object is reconstructed. Adding {{incode|self.Object = obj}} or {{incode|obj.Proxy = self}} often solves these issues.
| Appelé après la restauration d'un document ou la copie d'un objet FeaturePython.

| Parfois, les références à l'objet FeaturePython de la classe ou à la classe de l'objet FeaturePython peuvent être interrompues, car la méthode class {{incode|__init __()}} n'est pas appelée lors de la reconstruction de l'objet. L'ajout de {{incode|self.Object <nowiki>=</nowiki> obj}} ou {{incode|obj.Proxy <nowiki>=</nowiki> self}} résout souvent ces problèmes.
|}
|}


Pour une référence complète des méthodes FeaturePython disponibles, voir [[FeaturePython_methods/fr|Méthodes FeaturePython]].
In addition, there are two callbacks in the ViewProvider class that may occasionally prove useful:


En outre, il existe deux rappels dans la classe ViewProvider qui peuvent parfois s'avérer utiles:
{| class="wikitable"


{| class="wikitable" cellpadding="5px" width="100%"
|+style="caption-side:bottom; | ViewProvider basic callbacks
|+ ViewProvider basic callbacks
|-
|-
|style="width:25%" | {{incode|updateData(self, obj, prop)}}
|{{incode|updateData(self, obj, prop)}} || Called after a data (model) property is changed || {{incode|obj}} is a reference to the FeaturePython class instance, not the ViewProvider instance. {{incode|prop}} is the name of the property to be changed, not the property object itself.
|style="width:25%" | Appelé après la modification d'une propriété de données (modèle)
|style="width:50%" | {{incode|obj}} est une référence à l'instance de classe FeaturePython et non à l'instance ViewProvider. {{incode|prop}} est le nom de la propriété à modifier, pas l'objet de propriété lui-même.
|-
|-
| {{incode|onChanged(self, vobj, prop)}}
|{{incode|onChanged(self, vobj, prop)}} || Called after a view property is changed || {{incode|vobj}} is a reference to the ViewProvider instance. {{incode|prop}} is the name of the view property which was changed.
| Appelé après la modification d'une propriété de vue
| {{incode|vobj}} est une référence à l'instance ViewProvider. {{incode|prop}} est le nom de la propriété de vue qui a été modifiée.
|}
|}


Il n'est pas rare de rencontrer une situation où les rappels Python ne sont pas déclenchés comme ils le devraient. Les débutants dans ce domaine peuvent être assurés que le système de rappel FeaturePython n'est pas fragile ou cassé. Invariablement, lorsque les rappels ne s'exécutent pas, c'est parce qu'une référence est perdue ou indéfinie dans le code sous-jacent. Si, cependant, les rappels semblent interrompre sans explication, fournir des références d'objet/proxy dans le rappel {{incode|onDocumentRestored()}} (comme indiqué dans le premier tableau ci-dessus) peut atténuer ces problèmes. Tant que vous n'êtes pas à l'aise avec le système de rappel, il peut être utile d'ajouter des instructions d'impression dans chaque rappel pour imprimer des messages sur la console pendant le développement.


[[#top|En haut]]
{| class="wikitable"
!{{ColoredParagraph|Tip}}


<span id="Complete_code"></span>
|-
==Le code terminé==
|It is not uncommon to encounter a situation where the Python callbacks are not being triggered as they should. Beginners in this area need to rest assured that the FeaturePython callback system is not fragile or broken. Invariably, when callbacks fail to run, it is because a reference is lost or undefined in the underlying code. If, however, callbacks appear to be breaking with no explanation, providing object / proxy references in the {{incode|onDocumentRestored()}} callback (as noted in the first table above) may alleviate these problems. Until you are comfortable with the callback system, it may be useful to add print statements in each callback to print messages to the console as a diagnostic during development.


{{Code|code=
|}
import FreeCAD as App

import Part
=== The Code ===

The complete code for this example:


def create(obj_name):
import FreeCAD as App
import Part
def create(obj_name):
"""
"""
Object creation method
Object creation method
"""
"""

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

fpo = box(obj)
box(obj)

ViewProviderBox(obj.ViewObject)
ViewProviderBox(obj.ViewObject)

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

self.ratios = None
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = 'Hello World!'
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width'). Width = '10 mm'
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box Height').Height = '1 cm'
obj.addProperty('App::PropertyBool', 'Aspect Ratio', 'Dimensions', 'Lock the box aspect ratio').Aspect_Ratio = False
obj.Proxy = self
obj.Proxy = self

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

def __setstate__(self, state):
if state:
self.Type = state
def execute(self, obj):
def execute(self, obj):
"""
"""
Called on document recompute
Called on document recompute
"""
"""

obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)
obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

class ViewProviderBox:

class ViewProviderBox:
def __init__(self, obj):
def __init__(self, obj):
"""
"""
Set this object to the proxy object of the actual view provider
Set this object to the proxy object of the actual view provider
"""
"""

obj.Proxy = self
obj.Proxy = self

def attach(self, obj):
def attach(self, obj):
"""
"""
Line 357: Line 300:
"""
"""
return
return

def updateData(self, fp, prop):
def updateData(self, fp, prop):
"""
"""
If a property of the handled feature has changed we have the chance to handle this here
If a property of the handled feature has changed we have the chance to handle this here
"""
"""
return
return

def getDisplayModes(self,obj):
def getDisplayModes(self,obj):
"""
"""
Return a list of display modes.
Return a list of display modes.
"""
"""
return None
return []

def getDefaultDisplayMode(self):
def getDefaultDisplayMode(self):
"""
"""
Line 376: Line 318:
"""
"""
return "Shaded"
return "Shaded"

def setDisplayMode(self,mode):
def setDisplayMode(self,mode):
"""
"""
Line 384: Line 326:
"""
"""
return mode
return mode

def onChanged(self, vobj, prop):
def onChanged(self, vp, prop):
"""
"""
Print the name of the property that has changed
Print the name of the property that has changed
"""
"""

App.Console.PrintMessage("Change property: " + str(prop) + "\n")
App.Console.PrintMessage("Change property: " + str(prop) + "\n")

def getIcon(self):
def getIcon(self):
"""
"""
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
"""
"""

return """
return """
/* XPM */
/* XPM */
static const char * ViewProviderBox_xpm[] = {
static const char * ViewProviderBox_xpm[] = {
"16 16 6 1",
"16 16 6 1",
" c None",
" c None",
". c #141010",
". c #141010",
"+ c #615BD2",
"+ c #615BD2",
"@ c #C39D55",
"@ c #C39D55",
"# c #000000",
"# c #000000",
"$ c #57C355",
"$ c #57C355",
" ........",
" ........",
" ......++..+..",
" ......++..+..",
Line 424: Line 366:
" ####### "};
" ####### "};
"""
"""

def __getstate__(self):
def dumps(self):
return None
"""
Called during document saving.
"""
def __setstate__(self,state):
return None
return None


def loads(self,state):
{{docnav/fr
"""
|[[FeaturePython Objects/fr|Objects FeaturePython]]
Called during document restore.
|[[Scripting examples/fr|Exemples de Scripts]]
"""
return None
}}
}}


[[#top|En haut]]
{{Userdocnavi/fr}}


{{Docnav/fr
[[Category:Tutorials/fr]]
|[[Create_a_FeaturePython_object_part_I/fr|Créer un objet FeaturePython partie I]]

|
[[Category:Python Code/fr]]
|IconL=
|IconR=
}}


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

Latest revision as of 09:00, 27 January 2024

Other languages:

Introduction

Sur la page Créer un objet FeaturePython partie I, nous nous sommes concentrés sur les aspects internes d'une classe Python construite autour d'un objet FeaturePython, en particulier un objet App::FeaturePython. Nous avons créé l'objet, défini certaines propriétés et ajouté un rappel d'événement au niveau du document qui permet à notre objet de répondre à un recalcul de document avec la méthode execute(). Mais notre objet n'a toujours pas de présence dans la Vue 3D. Remédions à cela en ajoutant une boîte.

Ajout d'une boîte

Tout d'abord, en haut du fichier box.py, sous l'importation existante, ajoutez:

import Part

Puis dans execute() supprimez l'instruction print() et ajoutez la ligne suivante à sa place:

Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))

Ces commandes exécutent des méthodes Python fournies avec FreeCAD par défaut.

  • La méthode Part.makeBox() génère une nouvelle forme de boîte.
  • L'appel englobant à Part.show() ajoute la forme à l'arborescence du document et la rend visible.

Supprimez tous les objets existants, rechargez le module boîte et créez un nouvel objet boîte en utilisant box.create(). Remarquez comment une boîte apparaît immédiatement à l'écran. C'est parce que nous forçons le document à recalculer à la fin de box.create() qui à son tour déclenche la méthode execute() de notre classe box.

À première vue, le résultat peut sembler correct mais il y a quelques problèmes. Le plus évident est que la boîte est représentée par un objet entièrement différent de notre objet FeaturePython. Part.show() crée simplement un objet boîte séparé et l'ajoute au document. Pire encore, si vous modifiez les dimensions de l'objet FeaturePython, une autre forme de boîte est créée et l'ancienne est laissée en place. Et si vous avez ouvert la Vue rapport, vous avez peut-être remarqué une erreur indiquant "Appel récursif de recalcul pour le document Sans nom". Cela a à voir avec l'utilisation de la méthode Part.show() dans un objet FeaturePython.

En haut

Correction du code

Pour résoudre ces problèmes, nous devons apporter un certain nombre de changements. Jusqu'à présent, nous avons utilisé un objet App::FeaturePython qui n'est en fait pas destiné à avoir une représentation visuelle dans la vue 3D. Nous devons utiliser un objet Part::FeaturePython à la place. Dans create() modifiez la ligne suivante:

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

par:

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

Pour se débarrasser de l'objet box séparé, nous devons affecter le résultat de la méthode makeBox() à la propriété Shape de notre objet Part::FeaturePython. Modifiez cette ligne dans execute():

Part.show(Part.makeBox(obj.Length, obj.Width, obj.Height))

en:

obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

Enregistrez vos modifications, revenez à FreeCAD, supprimez tous les objets existants, rechargez le module de boîte et créez un nouvel objet boîte. Le nouveau résultat est quelque peu décevant. Il n'y a plus d'objet supplémentaire dans l'arborescence, et l'icône dans l'arborescence a changé, mais notre boîte dans la vue 3D a également disparu (c'est pourquoi l'icône est grise). Qu'est ce qui c'est passé? Bien que nous ayons correctement créé notre forme de boîte et l'avons assignée à un objet Part::FeaturePython, pour le faire apparaître réellement dans la vue 3D, nous devons attribuer un ViewProvider.

En haut

Écrire un ViewProvider

Un fournisseur de vue (View Provider) est le composant d'un objet qui lui permet d'avoir une représentation visuelle dans la vue 3D. FreeCAD utilise une structure d'application conçue pour séparer les données (le "modèle") de sa représentation visuelle (la "vue"). Si vous avez passé du temps à travailler avec FreeCAD en Python, vous en êtes probablement déjà conscient grâce à l'utilisation des deux modules Python de base: FreeCAD et FreeCADGui (souvent sous les alias { {incode|App}} et Gui respectivement).

Notre objet FeaturePython nécessite également ces éléments. Jusqu'à présent, nous nous sommes concentrés uniquement sur la partie "modèle" du code, il est maintenant temps d'écrire la partie "vue". Heureusement, la plupart des ViewProviders sont simples et nécessitent peu d'efforts pour écrire, du moins pour commencer. Voici un exemple de ViewProvider emprunté et légèrement modifié à [1]:

class ViewProviderBox:

    def __init__(self, obj):
        """
        Set this object to the proxy object of the actual view provider
        """

        obj.Proxy = self

    def attach(self, obj):
        """
        Setup the scene sub-graph of the view provider, this method is mandatory
        """
        return

    def updateData(self, fp, prop):
        """
        If a property of the handled feature has changed we have the chance to handle this here
        """
        return

    def getDisplayModes(self,obj):
        """
        Return a list of display modes.
        """
        return []

    def getDefaultDisplayMode(self):
        """
        Return the name of the default display mode. It must be defined in getDisplayModes.
        """
        return "Shaded"

    def setDisplayMode(self,mode):
        """
        Map the display mode defined in attach with those defined in getDisplayModes.
        Since they have the same names nothing needs to be done.
        This method is optional.
        """
        return mode

    def onChanged(self, vp, prop):
        """
        Print the name of the property that has changed
        """

        App.Console.PrintMessage("Change property: " + str(prop) + "\n")

    def getIcon(self):
        """
        Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
        """

        return """
            /* XPM */
            static const char * ViewProviderBox_xpm[] = {
            "16 16 6 1",
            "    c None",
            ".   c #141010",
            "+   c #615BD2",
            "@   c #C39D55",
            "#   c #000000",
            "$   c #57C355",
            "        ........",
            "   ......++..+..",
            "   .@@@@.++..++.",
            "   .@@@@.++..++.",
            "   .@@  .++++++.",
            "  ..@@  .++..++.",
            "###@@@@ .++..++.",
            "##$.@@$#.++++++.",
            "#$#$.$$$........",
            "#$$#######      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            " #$#$$$$$#      ",
            "  ##$$$$$#      ",
            "   #######      "};
            """

    def dumps(self):
        """
        Called during document saving.
        """
        return None

    def loads(self,state):
        """
        Called during document restore.
        """
        return None

Dans le code ci-dessus, nous définissons une icône XMP pour cet objet. La conception des icônes dépasse le cadre de ce tutoriel mais la conception de base peut être gérée à l'aide d'outils open source tels que GIMP, Krita et [ https://inkscape.org/ Inkscape]. La méthode getIcon() est facultative, FreeCAD utilisera une icône par défaut si cette méthode n'est pas fournie.

Ajoutez le code ViewProvider à la fin de box.py et dans la méthode create() insérez la ligne suivante au-dessus de l'instruction recompute():

ViewProviderBox(obj.ViewObject)

Cela instancie la classe ViewProvider personnalisée et lui transmet le ViewObject intégré de FeaturePython. Lorsque la classe ViewProvider s'initialise, elle enregistre une référence à elle-même dans l'attribut ViewObject.Proxy de FeaturePython. De cette façon, lorsque FreeCAD a besoin de rendre notre boîte visuellement, il peut trouver la classe ViewProvider pour le faire.

Maintenant, enregistrez les modifications et revenez à FreeCAD. Importez ou rechargez le module box et appelez box.create(). Vous devriez maintenant voir deux choses:

  • L'icône de l'objet boîte a changé.
  • Et, plus important encore, il y a une boîte dans la vue 3D. Si vous ne le voyez pas, appuyez sur le bouton Std Tout afficher. Vous pouvez même modifier les dimensions de la boîte en modifiant les valeurs dans l'Éditeur de propriétés. Essayez!

En haut

Piégeage d'événements

Nous avons déjà discuté du piégeage d'événements. Presque toutes les méthodes d'une classe FeaturePython servent de rappel accessible à l'objet FeaturePython (qui obtient l'accès à notre instance de classe via l'attribut Proxy, si vous vous en souvenez).

Voici une liste des rappels pouvant être implémentés dans l'objet FeaturePython de base:

FeaturePython basic callbacks
execute(self, obj) Appelé pendant le recalcul du document N'appelez pas recompute() depuis cette méthode (ou toute méthode appelée depuis execute()) car cela provoque un recalcul imbriqué.
onBeforeChange(self, obj, prop) Appelé avant la modification d'une valeur de propriété prop est le nom de la propriété à modifier, pas l'objet de propriété lui-même. Les modifications de propriété ne peuvent pas être annulées. Les valeurs de propriétés précédentes/suivantes ne sont pas disponibles simultanément pour la comparaison.
onChanged(self, obj, prop) Appelé après la modification d'une propriété prop est le nom de la propriété à modifier, pas l'objet de propriété lui-même.
onDocumentRestored(self, obj) Appelé après la restauration d'un document ou la copie d'un objet FeaturePython. Parfois, les références à l'objet FeaturePython de la classe ou à la classe de l'objet FeaturePython peuvent être interrompues, car la méthode class __init __() n'est pas appelée lors de la reconstruction de l'objet. L'ajout de self.Object = obj ou obj.Proxy = self résout souvent ces problèmes.

Pour une référence complète des méthodes FeaturePython disponibles, voir Méthodes FeaturePython.

En outre, il existe deux rappels dans la classe ViewProvider qui peuvent parfois s'avérer utiles:

ViewProvider basic callbacks
updateData(self, obj, prop) Appelé après la modification d'une propriété de données (modèle) obj est une référence à l'instance de classe FeaturePython et non à l'instance ViewProvider. prop est le nom de la propriété à modifier, pas l'objet de propriété lui-même.
onChanged(self, vobj, prop) Appelé après la modification d'une propriété de vue vobj est une référence à l'instance ViewProvider. prop est le nom de la propriété de vue qui a été modifiée.

Il n'est pas rare de rencontrer une situation où les rappels Python ne sont pas déclenchés comme ils le devraient. Les débutants dans ce domaine peuvent être assurés que le système de rappel FeaturePython n'est pas fragile ou cassé. Invariablement, lorsque les rappels ne s'exécutent pas, c'est parce qu'une référence est perdue ou indéfinie dans le code sous-jacent. Si, cependant, les rappels semblent interrompre sans explication, fournir des références d'objet/proxy dans le rappel onDocumentRestored() (comme indiqué dans le premier tableau ci-dessus) peut atténuer ces problèmes. Tant que vous n'êtes pas à l'aise avec le système de rappel, il peut être utile d'ajouter des instructions d'impression dans chaque rappel pour imprimer des messages sur la console pendant le développement.

En haut

Le code terminé

import FreeCAD as App
import Part

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

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

    box(obj)

    ViewProviderBox(obj.ViewObject)

    App.ActiveDocument.recompute()

    return obj

class box():

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

        self.Type = 'box'

        obj.Proxy = self

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

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

        obj.Shape = Part.makeBox(obj.Length, obj.Width, obj.Height)

class ViewProviderBox:

    def __init__(self, obj):
        """
        Set this object to the proxy object of the actual view provider
        """

        obj.Proxy = self

    def attach(self, obj):
        """
        Setup the scene sub-graph of the view provider, this method is mandatory
        """
        return

    def updateData(self, fp, prop):
        """
        If a property of the handled feature has changed we have the chance to handle this here
        """
        return

    def getDisplayModes(self,obj):
        """
        Return a list of display modes.
        """
        return []

    def getDefaultDisplayMode(self):
        """
        Return the name of the default display mode. It must be defined in getDisplayModes.
        """
        return "Shaded"

    def setDisplayMode(self,mode):
        """
        Map the display mode defined in attach with those defined in getDisplayModes.
        Since they have the same names nothing needs to be done.
        This method is optional.
        """
        return mode

    def onChanged(self, vp, prop):
        """
        Print the name of the property that has changed
        """

        App.Console.PrintMessage("Change property: " + str(prop) + "\n")

    def getIcon(self):
        """
        Return the icon in XMP format which will appear in the tree view. This method is optional and if not defined a default icon is shown.
        """

        return """
            /* XPM */
            static const char * ViewProviderBox_xpm[] = {
            "16 16 6 1",
            "    c None",
            ".   c #141010",
            "+   c #615BD2",
            "@   c #C39D55",
            "#   c #000000",
            "$   c #57C355",
            "        ........",
            "   ......++..+..",
            "   .@@@@.++..++.",
            "   .@@@@.++..++.",
            "   .@@  .++++++.",
            "  ..@@  .++..++.",
            "###@@@@ .++..++.",
            "##$.@@$#.++++++.",
            "#$#$.$$$........",
            "#$$#######      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            "#$$#$$$$$#      ",
            " #$#$$$$$#      ",
            "  ##$$$$$#      ",
            "   #######      "};
            """

    def dumps(self):
        """
        Called during document saving.
        """
        return None

    def loads(self,state):
        """
        Called during document restore.
        """
        return None

En haut