Line drawing function

From FreeCAD Documentation
Revision as of 03:24, 1 November 2008 by Yorik (talk)
Jump to: navigation, search

This page shows how advanced functionality can easily be built in python. In this exercise, we will be building a new tool that draws a line. This tool can then be linked to a freecad command, and that command can be called by any element of the interface, like a menu item or a toolbar button.

The main script

First we will write a script containing all our functionality. Then, we will save this in a file, and import it in FreeCAD, so all classes and functions we write will be availible to FreeCAD. So, launch your favorite text editor, and type the following lines:

import FreeCADGui, Part

 class line:
   "this class will creates 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.addEventCallback("SoMouseButtonEvent",self.getpoint)
     print "click the two points of the line"
   def getpoint(self,info):
     if info["State"] == "DOWN":
       pos = info["Position"]
       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.removeEventCallback("SoMouseButtonEvent",self.callback)

Detailed explanation

import Part, FreeCADGui

In python, when you want to use functions from another module, you need to import it. In our case, we will need functions from the Part Module, for creating the line, and from the Gui module (FreeCADGui), for accessing the 3D view.

class line:

Here we define our main class. Why do we use a class and not a function? The reason is that we need our tool to stay "alive" while we are waiting for the user to click on the screen. A function ends when its task has been done, but an object (a class defines an object) stays alive until it is destroyed.

"this class will creates a line after the user clicked 2 points on the screen"

In python, every class or function can have a description string. This is particularly useful in FreeCAD, because when you'll call that class in the interpreter, the description string will be displayed as a tooltip.

def __init__(self):

Python classes can always contain an __Init__ function, which is executed when the class is called to create an object. So, we will put here everything we want to happen when our line tool begins.

self.view = FreeCADGui.ActiveDocument.ActiveView

In a class, you usually want to append self. before a variable name, so it will be easily accessible to all functions inside and outside that class. Here, we will use self.view to access and manipulate the active 3D view.

self.stack = []

Here we create an empty list that will contain the 3D points sent by the getpoint function.

self.callback = self.view.addEventCallback("SoMouseButtonEvent",self.getpoint)

This is the important part: Since it is actually a coin3D scene, the FreeCAD uses coin callback mechanism, that allows a function to be called everytime a certain scene event happens. In our case, we are creating a callback for SoMouseButtonEvent events, and we bind it to the getpoint function. Now, everytime everytime a mouse button is pressed or released, the getpoint function will be executed.

print "click the two points of the line"

It is always a good idea to think about what feedback you will give to the user. You must help him to understand what's going on... So let's print a message to help him to know what he must do next. Of course, our example is very basic. Maybe you can think of something better?

def getpoint(self,info):

Now we define the getpoint function, that will be executed when a mouse button is pressed in a 3D view. This function will receive an argument, that we will call info, which contains several pieces of information (mode info here).

if info["State"] == "DOWN":

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.

pos = info["Position"]

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.removeEventCallback("SoMouseButtonEvent",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 2 files, Init.py and InitGui.py inside your MyScripts directory. The Init.py can be left empty, and the InitGui.py will contain the instructions to create a new workbench, and add our new tool to it.