Create a FeaturePython object part I

Introduction
FeaturePython objects (also often referred to as 'Scripted Objects') provide users the ability to extend FreeCAD's with objects that integrate seamlessly into the FreeCAD framework.

This encourages:


 * Rapid prototyping of new objects and tools with custom Python classes.


 * Serialization through 'App::Property' objects, without embedding any script in the FreeCAD document file.


 * Creative freedom to adapt FreeCAD for any task!

How Does It Work?
FreeCAD comes with a number of default object types for managing different kinds of geometry. Some of them have 'FeaturePython' alternatives that allow for user customization with a custom python class.

The custom python class simply takes a reference to one of these objects and modifies it in any number of ways. For example, the python class may add properties directly to the object, modifying other properties when it's recomputed, or linking it to other objects. In addition the python class implements certain methods to enable it to respond to document events, making it possible to trap object property changes and document recomputes.

It's important to remember, however, that for as much as one can accomplish with custom classes and FeaturePython objects, when it comes time to save the document, only the FeaturePython object itself is serialized. The custom class and it's state are not retained between document reloading. Doing so would require embedding script in the FreeCAD document file, which poses a significant security risk, much like the risks posed by embedding VBA macros in Microsoft Office documents.

Thus, a FeaturePython object ultimately exists entirely apart from it's script. The inconvenience posed by not packing the script with the object in the document file is far less than the risk posed by running a file embedded with an unknown script. However, the script module path is stored in the document file. Therefore, a user need only install the custom python class code as an importable module following the same directory structure to regain the lost functionality.

The Complete Beginner's Guide to FeaturePython Objects
Let's start from the very beginning (it's a very good place to start. ;) )

We're going to construct a complete, working example of a FeaturePython custom class, identifying all of the major components and gaining an intimate understanding of how everything works as we go. But before we do, there's a few details to get out of the way.

Setting up your development environment
To begin, FeaturePython Object classes need to act as importable modules in FreeCAD. That means you need to place them in a path that exists in your Python environment (or add it specifically). For the purposes of this tutorial, we're going to use the FreeCAD user Macro folder, though if you have another idea in mind, feel free to use that instead!

Anyway, if you don't know where the FreeCAD Macro folder is:


 * Windows: Type '%APPDATA%/FreeCAD/Macro' in the filepath bar at the top of Explorer


 * Linux: Navigate to /home/USERNAME/.FreeCAD/Macro


 * Mac: Navigate to /Users/USERNAME/Library/Preferences/FreeCAD/Macro

Now we need to create some files.


 * In the Macro folder create a new folder called fpo.
 * In the fpo folder, create a new folder called box.
 * In the box folder create two files: __init__.py and box.py (leave both empty for now)

The fpo folder provides a nice spot to play with new FeaturePython objects and the box folder is the module we will be working in.

__init__.py tells Python that in the folder is an importable module, and box.py will be the class file for our new FeaturePython Object

Your directory structure should look like this:

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

With our module paths and files created, let's make sure FreeCAD is set up properly. If FreeCAD isn't loaded, now is a good time to boot it up.

Make sure the Python Console and Report View are enabled by selecting View -> Panels -> Report view and Python console

If you haven't been introduced to the Python Console in FreeCAD, you can learn more about it here

Now that FreeCAD is set up, switch over to your favorite code editor, navigate to the Macro/fpo/box folder, and open box.py.

It's time to write some code!

A Very Basic FeaturePython Object
We're going to start really simply. It won't be impressive, but it will provide a clear idea of how FeaturePython objects work with custom python classes.

So, let's get started by writing our class and it's constructor:

class box: def __init__(self, obj): """         Default Constructor          """ self.Type = 'box' obj.Proxy = self self.Object = obj

It doesn't look very exciting, but this small piece of code illustrates just how FeaturePython objects interact with Python classes.

First, the Python class is set up in very much standard fashion with an  method with two parameters:  the default   and an additional parameter.

Inside the  method, we have three lines of code:


 * String definition of the custom python type
 * String definition of the custom python type


 * is a reference to the FeaturePython object that must be created first in FreeCAD.   provides a reference to the custom class that the FeaturePython object can call.
 * is a reference to the FeaturePython object that must be created first in FreeCAD.   provides a reference to the custom class that the FeaturePython object can call.


 * Store a reference to the FeaturePython object for the Python class to use.
 * Store a reference to the FeaturePython object for the Python class to use.

Study the code for a moment. It may seem confusing, but it describes very well just how the custom Python class relates to the native FreeCAD FeaturePython objects. The relationships will seem clearer once we create our object in FreeCAD.

Before we can do that, however, we need to add a little more code to manage object creation.

In the box.py file at the top, add the following code:

import FreeCAD as App def create(obj_name): """     Object creation method      """ obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name) fpo = box(obj) return fpo

This allows us to manage object creation by calling the create factory method. It takes a parameter to define the object name, and handles all of the construction.


 * Standard import for most any FreeCAD python script. Using the App alias is consistent with how the Python console in FreeCAD works, but is not required.
 * Standard import for most any FreeCAD python script. Using the App alias is consistent with how the Python console in FreeCAD works, but is not required.


 * Creates a new FreeCAD object of the type 'App::FeaturePython', and assigns the name passed to the method. Note that when an object is created, it's name cannot be changed.
 * Creates a new FreeCAD object of the type 'App::FeaturePython', and assigns the name passed to the method. Note that when an object is created, it's name cannot be changed.


 * With a FeaturePython object in existence, we can now create our custom class instance and link it to the FeaturePython object with the  method.
 * With a FeaturePython object in existence, we can now create our custom class instance and link it to the FeaturePython object with the  method.

Thats it!

Now we can try our new object. Save your code and return to FreeCAD

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.

That's ok. There's still something to learn here.

Click on the object and note what appears in the property panel under it. Not very much - just the name of the object. We'll need to add some properties in a bit.

Now, back at the console, let's see just what that code we wrote in the  function does for us.

First, let's make referencing our new object a little more convenient:

>>> mybox = App.ActiveDocument.my_box

Now, let's list the attributes of the object:

>>> 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! That's because we're accessing the native FreeCAD FeaturePyton object that we created in the first line of our  method.

Remember the  property we added in our class   method? Did you see it in the attribute list?

Let's try looking at it's attributes:

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

Among the list of protected methods and properties, we see our  and   properties!

Call the  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, if you call: >>> mybox.Proxy.Object

You'll get back the FeaturePython Object, the same thing that  refers to.

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

Available properties
Properties are the true building stones of FeaturePython objects. Through them, the user will be able to interact and modify your object. After creating a new FeaturePython object in your document ( obj=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Box") ), you can get a list of the available properties by issuing:

You will get a list of available properties:

When adding properties to your custom objects, take care of this:
 * Do not use characters "<" or ">" in the properties descriptions (that would break the xml pieces in the .fcstd file)
 * Properties are stored alphabetically in a .fcstd file. If you have a shape in your properties, any property whose name comes after "Shape" in alphabetic order, will be loaded AFTER the shape, which can cause strange behaviours.

Property Type
By default the properties can be updated. It is possible to make the properties read-only, for instance in the case one wants to show the result of a method. It is also possible to hide the property. The property type can be set using

where mode is a short int that can be set to: 0 -- default mode, read and write 1 -- read-only 2 -- hidden

The EditorModes are not set at FreeCAD file reload. This could to be done by the __setstate__ function. See http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=10#p108072. By using the setEditorMode the properties are only read only in PropertyEditor. They could still be changed from python. To really make them read only the setting has to be passed directly inside the addProperty function. See http://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 for an example.

Other more complex example
This example makes use of the Part Module to create an octahedron, then creates its coin representation with pivy.

First is the Document object itself:

Then, we have the view provider object, responsible for showing the object in the 3D scene:

Finally, once our object and its viewobject are defined, we just need to call them (The Octahedron class and viewprovider class code could be copied in the FreeCAD python console directly):

Making objects selectable
If you want to make your object selectable, or at least part of it, by clicking on it in the viewport, you must include its coin geometry inside a SoFCSelection node. If your object has complex representation, with widgets, annotations, etc, you might want to include only a part of it in a SoFCSelection. Everything that is a SoFCSelection is constantly scanned by FreeCAD to detect selection/preselection, so it makes sense try not to overload it with unneeded scanning. This is what you would do to include a self.face from the example above:

Simply, you create a SoFCSelection node, then you add your geometry nodes to it, then you add it to your main node, instead of adding your geometry nodes directly.

Working with simple shapes
If your parametric object simply outputs a shape, you don't need to use a view provider object. The shape will be displayed using FreeCAD's standard shape representation:

Same code with use ViewProviderLine

Further information
There are a few very interesting forum threads about scripted objects:

- http://forum.freecadweb.org/viewtopic.php?f=22&t=13740

- http://forum.freecadweb.org/viewtopic.php?t=12139

- https://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709

- https://forum.freecadweb.org/viewtopic.php?f=22&t=21330

In addition to the examples presented here have a look at FreeCAD source code src/Mod/TemplatePyMod/FeaturePython.py for more examples.