Line drawing function/fr: Difference between revisions

From FreeCAD Documentation
(Created page with "Maintenant, nous définissons la fonction '''GetPoint''', qui sera exécutée quand un bouton de la souris sera pressé dans une vue 3D.<br /> Cette fonction recevra un argume...")
(Created page with "La fonction '''GetPoint''' sera appelée dès qu'un bouton de la souris est enfoncé ou relâché. Mais, nous ne voulons prendre un point 3D uniquement lorsqu'il est pressé (...")
Line 79: Line 79:
if event.getState() == SoMouseButtonEvent.DOWN:
if event.getState() == SoMouseButtonEvent.DOWN:
</syntaxhighlight>
</syntaxhighlight>
La fonction '''GetPoint''' sera appelée dès qu'un bouton de la souris est enfoncé ou relâché. Mais, nous ne voulons prendre un point 3D uniquement lorsqu'il est pressé (sinon, nous aurons deux points 3D très proches l'un de l'autre).<br />
The getpoint function will be called when a mouse button is pressed or released. But we want to pick a 3D point only when pressed (otherwise we would get two 3D points very close to each other). So we must check for that here.
Donc, nous devons vérifier cela avec:
<syntaxhighlight>
<syntaxhighlight>
pos = event.getPosition()
pos = event.getPosition()

Revision as of 22:00, 4 November 2014

Cette page montre comment construire facilement des fonctionnalités avancées en Python. Dans cet exercice, nous allons construire un nouvel outil qui trace une ligne. Cet outil peut alors être lié à une commande FreeCAD, et cette commande peut être appelée par n'importe quel élément de l'interface, comme un élément de menu ou un bouton de la barre d'outils.

Script principal

Première chose, nous allons écrire un script contenant toutes nos fonctionnalités, puis, nous allons l'enregistrer dans un fichier, et l'importer dans FreeCAD, alors toutes les classes et fonctions que nous écrirons seront accessibles à partir de FreeCAD.
Alors, lancez votre éditeur de texte favori, et entrez les lignes suivantes:

 import FreeCADGui, Part
 from pivy.coin import *
 
 class line:
     "this class will create a line after the user clicked 2 points on the screen"
     def __init__(self):
         self.view = FreeCADGui.ActiveDocument.ActiveView
         self.stack = []
         self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)  
 
     def getpoint(self,event_cb):
         event = event_cb.getEvent()
         if event.getState() == SoMouseButtonEvent.DOWN:
             pos = event.getPosition()
             point = self.view.getPoint(pos[0],pos[1])
             self.stack.append(point)
             if len(self.stack) == 2:
                 l = Part.Line(self.stack[0],self.stack[1])
                 shape = l.toShape()
                 Part.show(shape)
                 self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)

Explications détaillées

 import Part, FreeCADGui
 from pivy.coin import *

En Python, lorsque vous voulez utiliser les fonctions d'un autre module, vous avez besoin de l'importer.
Dans notre cas, nous aurons besoin de fonctions du Part Module, pour la création de la ligne, et du Gui module (FreeCADGui), pour accéder à la vue 3D.
Nous avons également besoin de tout le contenu de la bibliothèque de pièces, afin que nous puissions utiliser directement tous les objets comme coin, SoMouseButtonEvent (évènement souris) etc ..

 class line:

Ici, nous définissons notre classe principale.
Mais pourquoi utilisons-nous une classe et non une fonction ? La raison en est que nous avons besoin que notre outil reste "vivant" en attendant que l'utilisateur clique sur l'écran.

  • Une fonction se termine lorsque sa tâche est terminée,
  • mais un objet, (une classe définit un objet) reste en vie (actif) jusqu'à ce qu'il soit détruit.
 "this class will create a line after the user clicked 2 points on the screen"

En Python, toutes les classes ou fonctions peuvent avoir une description.
Ceci est particulièrement utile dans FreeCAD, parce que quand vous appelez cette classe dans l'interpréteur, la description sera affichée comme une info-bulle.

 def __init__(self):

Les classes Python doivent toujours contenir une fonction __ init__, qui est exécutée lorsque la classe est appelée pour créer un objet.
Donc, nous allons mettre ici tout ce que nous voulons produire lorsque notre outil de création de ligne commence (appelé).

 self.view = FreeCADGui.ActiveDocument.ActiveView

Dans une classe, il est généralement souhaitable d'ajouter self. devant un nom de variable, de sorte que la variable sera facilement accessible à toutes les fonctions à l'intérieur et à l'extérieur de cette classe.
Ici, nous allons utiliser self.view pour accéder et manipuler la vue active 3D.

 self.stack = []

Ici, nous créons une liste vide qui contiendra les points en 3D envoyés par la fonction GetPoint.

 self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint)

Ceci est un point important:
Du fait qu'il s'agit d'une scène coin3D, FreeCAD utilise les mécanismes de rappel de coin, qui permet à une fonction d'être appelée à chaque fois qu'un évènement se passe sur la scène.
Dans notre cas, nous créons un appel pour SoMouseButtonEvent, et nous le lions à la fonction GetPoint.
Maintenant, chaque fois qu'un bouton de la souris est enfoncé ou relâché, la fonction GetPoint sera exécutée.

Notez qu'il existe aussi une alternative à addEventCallbackPivy() appelée addEventCallback() qui dispense l'utilisation de pivy. Mais, pivy est un moyen très simple et efficace d'accéder à n'importe quelle partie de la scène coin, il est conseillé de l'utiliser autant que possible !

 def getpoint(self,event_cb):

Maintenant, nous définissons la fonction GetPoint, qui sera exécutée quand un bouton de la souris sera pressé dans une vue 3D.
Cette fonction recevra un argument, que nous appellerons event_cb. A partir de l'appel de cet événement, nous pouvons accéder à l'objet événement, qui contient plusieurs éléments d'information (plus d'informations sur cette page).

 if event.getState() == SoMouseButtonEvent.DOWN:

La fonction GetPoint sera appelée dès qu'un bouton de la souris est enfoncé ou relâché. Mais, nous ne voulons prendre un point 3D uniquement lorsqu'il est pressé (sinon, nous aurons deux points 3D très proches l'un de l'autre).
Donc, nous devons vérifier cela avec:

 pos = event.getPosition()

Here we get the screen coordinates of the mouse cursor

 point = self.view.getPoint(pos[0],pos[1])

This function gives us a FreeCAD vector (x,y,z) containing the 3D point that lies on the focal plane, just under our mouse cursor. If you are in camera view, imagine a ray coming from the camera, passing through the mouse cursor, and hitting the focal plane. There is our 3D point. If we are in orthogonal view, the ray is parallel to the view direction.

 self.stack.append(point)

We add our new point to the stack

 if len(self.stack) == 2:

Do we have enough points already? if yes, then let's draw the line!

 l = Part.Line(self.stack[0],self.stack[1])

Here we use the function Line() from the Part Module that creates a line from two FreeCAD vectors. Everything we create and modify inside the Part module, stays in the Part module. So, until now, we created a Line Part. It is not bound to any object of our active document, so nothing appears on the screen.

 shape = l.toShape()

The FreeCAD document can only accept shapes from the Part module. Shapes are the most generic type of the Part module. So, we must convert our line to a shape before adding it to the document.

 Part.show(shape)

The Part module has a very handy show() function that creates a new object in the document and binds a shape to it. We could also have created a new object in the document first, then bound the shape to it manually.

 self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)

Since we are done with our line, let's remove the callback mechanism, that consumes precious CPU cycles.

Testing & Using the script

Now, let's save our script to some place where the FreeCAD python interpreter will find it. When importing modules, the interpreter will look in the following places: the python installation paths, the FreeCAD bin directory, and all FreeCAD modules directories. So, the best solution is to create a new directory in one of the FreeCAD Mod directories, and to save our script in it. For example, let's make a "MyScripts" directory, and save our script as "exercise.py".

Now, everything is ready, let's start FreeCAD, create a new document, and, in the python interpreter, issue:

 import exercise

If no error message appear, that means our exercise script has been loaded. We can now check its contents with:

 dir(exercise)

The command dir() is a built-in python command that lists the contents of a module. We can see that our line() class is there, waiting for us. Now let's test it:

 exercise.line()

Then, click two times in the 3D view, and bingo, here is our line! To do it again, just type exercise.line() again, and again, and again... Feels great, no?

Registering the script in the FreeCAD interface

Now, for our new line tool to be really cool, it should have a button on the interface, so we don't need to type all that stuff everytime. The easiest way is to transform our new MyScripts directory into a full FreeCAD workbench. It is easy, all that is needed is to put a file called InitGui.py inside your MyScripts directory. The InitGui.py will contain the instructions to create a new workbench, and add our new tool to it. Besides that we will also need to transform a bit our exercise code, so the line() tool is recognized as an official FreeCAD command. Let's start by making an InitGui.py file, and write the following code in it:

 class MyWorkbench (Workbench): 
    MenuText = "MyScripts"
    def Initialize(self):
        import exercise
        commandslist = ["line"]
        self.appendToolbar("My Scripts",commandslist)
 Gui.addWorkbench(MyWorkbench())

By now, you should already understand the above script by yourself, I think: We create a new class that we call MyWorkbench, we give it a title (MenuText), and we define an Initialize() function that will be executed when the workbench is loaded into FreeCAD. In that function, we load in the contents of our exercise file, and append the FreeCAD commands found inside to a command list. Then, we make a toolbar called "My Scripts" and we assign our commands list to it. Currently, of course, we have only one tool, so our command list contains only one element. Then, once our workbench is ready, we add it to the main interface.

But this still won't work, because a FreeCAD command must be formatted in a certain way to work. So we will need to transform a bit our line() tool. Our new exercise.py script will now look like this:

 import FreeCADGui, Part
 from pivy.coin import *
 class line:
  "this class will create a line after the user clicked 2 points on the screen"
  def Activated(self):
    self.view = FreeCADGui.ActiveDocument.ActiveView
    self.stack = []
    self.callback = self.view.addEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.getpoint) 
  def getpoint(self,event_cb):
    event = event_cb.getEvent()
    if event.getState() == SoMouseButtonEvent.DOWN:
      pos = event.getPosition()
      point = self.view.getPoint(pos[0],pos[1])
      self.stack.append(point)
      if len(self.stack) == 2:
        l = Part.Line(self.stack[0],self.stack[1])
        shape = l.toShape()
        Part.show(shape)
        self.view.removeEventCallbackPivy(SoMouseButtonEvent.getClassTypeId(),self.callback)
  def GetResources(self): 
      return {'Pixmap' : 'path_to_an_icon/line_icon.png', 'MenuText': 'Line', 'ToolTip': 'Creates a line by clicking 2 points on the screen'} 
 FreeCADGui.addCommand('line', line())

What we did here is transform our __init__() function into an Activated() function, because when FreeCAD commands are run, they automatically execute the Activated() function. We also added a GetResources() function, that informs FreeCAD where it can find an icon for the tool, and what will be the name and tooltip of our tool. Any jpg, png or svg image will work as an icon, it can be any size, but it is best to use a size that is close to the final aspect, like 16x16, 24x24 or 32x32. Then, we add the line() class as an official FreeCAD command with the addCommand() method.

That's it, we now just need to restart FreeCAD and we'll have a nice new workbench with our brand new line tool!

So you want more?

If you liked this exercise, why not try to improve this little tool? There are many things that can be done, like for example:

  • Add user feedback: until now we did a very bare tool, the user might be a bit lost when using it. So we could add some feedback, telling him what to do next. For example, you could issue messages to the FreeCAD console. Have a look in the FreeCAD.Console module
  • Add a possibility to type the 3D points coordinates manually. Look at the python input() function, for example
  • Add the possibility to add more than 2 points
  • Add events for other things: Now we just check for Mouse button events, what if we would also do something when the mouse is moved, like displaying current coordinates?
  • Give a name to the created object

Don't hesitate to write your questions or ideas on the talk page!

Code snippets
Dialog creation