Create a FeaturePython object part I: Difference between revisions
Joel graff (talk | contribs) |
Marc-Medley (talk | contribs) m (update: Mac OSX …/Preferences/… -> macOS …/Application Support/…) |
||
(166 intermediate revisions by 10 users not shown) | |||
Line 1: | Line 1: | ||
<languages/> |
<languages/> |
||
<translate> |
<translate> |
||
<!--T:28--> |
|||
{{docnav|PySide|Embedding FreeCAD}} |
|||
<!--T:1--> |
|||
== Introduction == |
|||
{{Docnav |
|||
| |
|||
|[[Create_a_FeaturePython_object_part_II|Create a FeaturePython object part II]] |
|||
|IconL= |
|||
|IconR= |
|||
}} |
|||
</translate> |
|||
{{TOCright}} |
|||
<translate> |
|||
==Introduction== <!--T:2--> |
|||
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. |
|||
<!--T:3--> |
|||
FeaturePython objects (also referred to as [[Scripted_objects|Scripted objects]]) provide the ability to extend FreeCAD with objects that integrate seamlessly into the FreeCAD framework. |
|||
<!--T:4--> |
|||
This encourages: |
This encourages: |
||
*Rapid prototyping of new objects and tools with custom Python classes. |
|||
*Saving and restoring data (also known as serialization) through {{incode|App::Property}} objects, without embedding any script in the FreeCAD document file. |
|||
*Creative freedom to adapt FreeCAD for any task. |
|||
<!--T:5--> |
|||
*'''Rapid prototyping''' of new objects and tools with custom Python classes. |
|||
On this page we are going to construct a working example of a FeaturePython custom class, identifying all the major components and gaining an understanding of how everything works as we go along. |
|||
==How does it work?== <!--T:6--> |
|||
*'''Serialization''' through 'App::Property' objects, ''without embedding any script'' in the FreeCAD document file. |
|||
<!--T:7--> |
|||
*'''Creative freedom''' to adapt FreeCAD for any task! |
|||
FreeCAD comes with a number of default object types for managing different kinds of geometry. Some of them have "FeaturePython" alternatives that allow for customization with a user defined Python class. |
|||
<!--T:8--> |
|||
This custom Python class takes a reference to one of these objects and modifies it. For example, the Python class may add properties to the object or link it to other objects. In addition the Python class may implement certain methods to enable the object to respond to document events, making it possible to trap object property changes and document recomputes. |
|||
<!--T:9--> |
|||
=== How Does It Work? === |
|||
When working with custom classes and FeaturePython objects it is important to know that the custom class and its state are not saved in the document as this would require embedding a script in a FreeCAD document file, which would pose a significant security risk. Only the FeaturePython object itself is saved (serialized). But since the script module path is stored in the document, a user need only install the custom Python class code as an importable module, following the same folder structure, to regain the lost functionality. |
|||
<!--T:94--> |
|||
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. |
|||
[[#top|top]] |
|||
==Setting things up== <!--T:11--> |
|||
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. |
|||
<!--T:12--> |
|||
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 [https://www.howtogeek.com/171993/macros-explained-why-microsoft-office-files-can-be-dangerous/ embedding VBA macros in Microsoft Office documents]. |
|||
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. But if you have another idea in mind, feel free to use that instead. |
|||
<!--T:13--> |
|||
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. |
|||
If you don't know where the FreeCAD Macro folder is type {{incode|FreeCAD.getUserMacroDir(True)}} in FreeCAD's [[Python_console|Python console]]: |
|||
* On Linux it is usually {{FileName|/home/<username>/.local/share/FreeCAD/Macro/}} ({{VersionPlus|0.20}}) or {{FileName|/home/<username>/.FreeCAD/Macro/}} ({{VersionMinus|0.19}}). |
|||
* On Windows it is {{FileName|%APPDATA%\FreeCAD\Macro\}}, which is usually {{FileName|C:\Users\<username>\Appdata\Roaming\FreeCAD\Macro\}}. |
|||
* On macOS it is usually {{FileName|/Users/<username>/Library/Application Support/FreeCAD/Macro/}}. |
|||
<!--T:14--> |
|||
== The Complete Beginner's Guide to FeaturePython Objects == |
|||
Now we need to create some folders and files: |
|||
*In the {{FileName|Macro}} folder create a new folder called {{FileName|fpo}}. |
|||
*In the {{FileName|fpo}} folder create an empty file: {{FileName|__init__.py}}. |
|||
*In the {{FileName|fpo}} folder, create a new folder called {{FileName|box}}. |
|||
*In the {{FileName|box}} folder create two files: {{FileName|__init__.py}} and {{FileName|box.py}} (leave both empty for now). |
|||
<!--T:16--> |
|||
Let's start from the very beginning (it's a very good place to start. ;) ) |
|||
Your folder structure should look like this: |
|||
</translate> |
|||
Macro/ |
|||
|--> fpo/ |
|||
|--> __init__.py |
|||
|--> box/ |
|||
|--> __init__.py |
|||
|--> box.py |
|||
<translate> |
|||
<!--T:15--> |
|||
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. |
|||
The {{FileName|fpo}} folder provides a nice place to play with new FeaturePython objects and the {{FileName|box}} folder is the module we will be working in. {{FileName|__init__.py}} tells Python that there is an importable module in the folder, and {{FileName|box.py}} will be the class file for our new FeaturePython Object. |
|||
<!--T:18--> |
|||
=== Setting up your development environment === |
|||
With our module paths and files created, let's make sure FreeCAD is set up properly: |
|||
*Start FreeCAD (if you haven't done so already). |
|||
*Enable the [[Report_view|Report view]] ({{MenuCommand|View → Panels → Report view}}). |
|||
*Enable the [[Python_console|Python console]] ({{MenuCommand|View → Panels → Python console}}) see [[FreeCAD_Scripting_Basics|FreeCAD Scripting Basics]]. |
|||
<!--T:95--> |
|||
Finally, navigate to the {{FileName|Macro/fpo/box}} folder and open {{FileName|box.py}} in your favorite code editor. We will only edit that file. |
|||
<!--T:96--> |
|||
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! |
|||
[[#top|top]] |
|||
==A FeaturePython object== <!--T:22--> |
|||
<!--T:23--> |
|||
Anyway, if you don't know where the FreeCAD Macro folder is: |
|||
Let's get started by writing our class and its constructor: |
|||
</translate> |
|||
*Windows: Type '%APPDATA%/FreeCAD/Macro' in the filepath bar at the top of Explorer |
|||
{{Code|code= |
|||
class box(): |
|||
def __init__(self, obj): |
|||
*Linux: Navigate to /home/USERNAME/.FreeCAD/Macro |
|||
""" |
|||
Default constructor |
|||
""" |
|||
self.Type = 'box' |
|||
*Mac: Navigate to /Users/USERNAME/Library/Preferences/FreeCAD/Macro |
|||
obj.Proxy = self |
|||
}} |
|||
<translate> |
|||
<!--T:24--> |
|||
Now we need to create some files. |
|||
'''The {{incode|__init__()}} method breakdown:''' |
|||
<!--T:97--> |
|||
*In the Macro folder create a new folder called '''fpo'''. |
|||
{|class="wikitable" cellpadding="5px" width="100%" |
|||
*In the fpo folder, create a new folder called '''box'''. |
|||
|style="width:25%" | {{incode|def __init__(self, obj):}} |
|||
*In the box folder create two files: '''__init__.py''' and '''box.py''' (leave both empty for now) |
|||
|style="width:75%" | Parameters refer to the Python class itself and the FeaturePython object that it is attached to. |
|||
|- |
|||
| {{incode|self.Type <nowiki>=</nowiki> 'box'}} |
|||
| String definition of the custom Python type. |
|||
|- |
|||
| {{incode|obj.Proxy <nowiki>=</nowiki> self}} |
|||
| Stores a reference to the Python instance in the FeaturePython object. |
|||
|} |
|||
<!--T:25--> |
|||
Add the following code at the top of the file: |
|||
</translate> |
|||
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. |
|||
{{Code|code= |
|||
import FreeCAD as App |
|||
def create(obj_name): |
|||
'''__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 |
|||
""" |
|||
Object creation method |
|||
""" |
|||
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name) |
|||
box(obj) |
|||
Your directory structure should look like this: |
|||
return obj |
|||
.FreeCAD |
|||
}} |
|||
|--> Macro |
|||
<translate> |
|||
|--> fpo |
|||
|--> box |
|||
|--> __init__.py |
|||
|--> box.py |
|||
<!--T:26--> |
|||
'''The {{incode|create()}} method breakdown:''' |
|||
<!--T:98--> |
|||
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. |
|||
{|class="wikitable" cellpadding="5px" width="100%" |
|||
|style="width:25%" | {{incode|import FreeCAD as App}} |
|||
|style="width:75%" | Standard import for most Python scripts, the App alias is not required. |
|||
|- |
|||
| {{incode|obj <nowiki>=</nowiki> ... addObject(...)}} |
|||
| Creates a new FreeCAD FeaturePython object with the name passed to the method. If there is no name clash, this will be the label and the name of the created object. Otherwise, a unique name and label will be created based on 'obj_name'. |
|||
|- |
|||
| {{incode|box(obj)}} |
|||
| Creates our custom class instance. |
|||
|- |
|||
| {{incode|return obj}} |
|||
| Returns the FeaturePython object. |
|||
|} |
|||
<!--T:27--> |
|||
Make sure the Python Console and Report View are enabled by selecting '''View -> Panels -> 'Report view''' and '''Python console''' |
|||
The {{incode|create()}} method is not required, but it provides a nice way to encapsulate the object creation code. |
|||
<!--T:99--> |
|||
If you haven't been introduced to the Python Console in FreeCAD, you can [learn more about it here] |
|||
[[#top|top]] |
|||
===Testing the code=== <!--T:29--> |
|||
<!--T:30--> |
|||
Now we can test our new object. Save your code and return to FreeCAD. Make sure you have opened a new document, you can do this by pressing {{KEY|Ctrl}}+{{KEY|N}} or selecting {{MenuCommand|File → New}}. |
|||
<!--T:31--> |
|||
In the Python console type the following: |
|||
== Available properties == <!--T:6--> |
|||
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: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
from fpo.box import box |
|||
obj.supportedProperties() |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:7--> |
|||
<!--T:33--> |
|||
You will get a list of available properties: |
|||
Now we need to create our object: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
mybox = box.create('my_box') |
|||
App::PropertyBool |
|||
App::PropertyBoolList |
|||
App::PropertyFloat |
|||
App::PropertyFloatList |
|||
App::PropertyFloatConstraint |
|||
App::PropertyQuantity |
|||
App::PropertyQuantityConstraint |
|||
App::PropertyAngle |
|||
App::PropertyDistance |
|||
App::PropertyLength |
|||
App::PropertySpeed |
|||
App::PropertyAcceleration |
|||
App::PropertyForce |
|||
App::PropertyPressure |
|||
App::PropertyInteger |
|||
App::PropertyIntegerConstraint |
|||
App::PropertyPercent |
|||
App::PropertyEnumeration |
|||
App::PropertyIntegerList |
|||
App::PropertyIntegerSet |
|||
App::PropertyMap |
|||
App::PropertyString |
|||
App::PropertyUUID |
|||
App::PropertyFont |
|||
App::PropertyStringList |
|||
App::PropertyLink |
|||
App::PropertyLinkSub |
|||
App::PropertyLinkList |
|||
App::PropertyLinkSubList |
|||
App::PropertyMatrix |
|||
App::PropertyVector |
|||
App::PropertyVectorList |
|||
App::PropertyPlacement |
|||
App::PropertyPlacementLink |
|||
App::PropertyColor |
|||
App::PropertyColorList |
|||
App::PropertyMaterial |
|||
App::PropertyPath |
|||
App::PropertyFile |
|||
App::PropertyFileIncluded |
|||
App::PropertyPythonObject |
|||
Part::PropertyPartShape |
|||
Part::PropertyGeometryList |
|||
Part::PropertyShapeHistory |
|||
Part::PropertyFilletEdges |
|||
Sketcher::PropertyConstraintList |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:8--> |
|||
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. |
|||
<!--T:35--> |
|||
[[Image:Fpo_treeview.png | right]] |
|||
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. |
|||
You should see a new object appear in the [[Tree_view|Tree view]] labelled "my_box". |
|||
The property type can be set using |
|||
<!--T:100--> |
|||
Note that the icon is gray. FreeCAD is telling us that the object is not able to display anything in the [[3D_view|3D view]]. Click on the object and look at its properties in the [[Property_editor|Property editor]]. There is not much there, just the name of the object. |
|||
<!--T:101--> |
|||
Also note that there is a small blue check mark next to the FeaturePython object in the Tree view. That is because when an object is created or changed it is "touched" and needs to be recomputed. Pressing the {{Button|[[Image:Std_Refresh.svg|16px]] [[Std_Refresh|Std Refresh]]}} button will accomplish this. We will add some code to automate this later. |
|||
{{Clear}} |
|||
<!--T:37--> |
|||
Let's look at our object's attributes: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
dir(mybox) |
|||
obj.setEditorMode("MyPropertyName", mode) |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:10--> |
|||
where mode is a short int that can be set to: |
|||
0 -- default mode, read and write |
|||
1 -- read-only |
|||
2 -- hidden |
|||
<!--T: |
<!--T:102--> |
||
This will return: |
|||
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 == <!--T:11--> |
|||
This example makes use of the [[Part Module|Part Module]] to create an octahedron, then creates its coin representation with pivy. |
|||
<!--T:12--> |
|||
First is the Document object itself: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
['Content', 'Document', 'ExpressionEngine', 'FullName', 'ID', 'InList', |
|||
import FreeCAD, FreeCADGui, Part |
|||
... |
|||
import pivy |
|||
'setPropertyStatus', 'supportedProperties', 'touch'] |
|||
from pivy import coin |
|||
}} |
|||
<translate> |
|||
<!--T:38--> |
|||
class Octahedron: |
|||
There are a lot of attributes because we're accessing the native FreeCAD FeaturePyton object created in the first line of our {{incode|create()}} method. The {{incode|Proxy}} property we added in our {{incode|__init__()}} method is there too. |
|||
def __init__(self, obj): |
|||
"Add some custom properties to our box feature" |
|||
obj.addProperty("App::PropertyLength","Length","Octahedron","Length of the octahedron").Length=1.0 |
|||
obj.addProperty("App::PropertyLength","Width","Octahedron","Width of the octahedron").Width=1.0 |
|||
obj.addProperty("App::PropertyLength","Height","Octahedron", "Height of the octahedron").Height=1.0 |
|||
obj.addProperty("Part::PropertyPartShape","Shape","Octahedron", "Shape of the octahedron") |
|||
obj.Proxy = self |
|||
<!--T:40--> |
|||
def execute(self, fp): |
|||
Let's inspect it with the {{incode|dir()}} method: |
|||
# Define six vetices for the shape |
|||
v1 = FreeCAD.Vector(0,0,0) |
|||
v2 = FreeCAD.Vector(fp.Length,0,0) |
|||
v3 = FreeCAD.Vector(0,fp.Width,0) |
|||
v4 = FreeCAD.Vector(fp.Length,fp.Width,0) |
|||
v5 = FreeCAD.Vector(fp.Length/2,fp.Width/2,fp.Height/2) |
|||
v6 = FreeCAD.Vector(fp.Length/2,fp.Width/2,-fp.Height/2) |
|||
# Make the wires/faces |
|||
f1 = self.make_face(v1,v2,v5) |
|||
f2 = self.make_face(v2,v4,v5) |
|||
f3 = self.make_face(v4,v3,v5) |
|||
f4 = self.make_face(v3,v1,v5) |
|||
f5 = self.make_face(v2,v1,v6) |
|||
f6 = self.make_face(v4,v2,v6) |
|||
f7 = self.make_face(v3,v4,v6) |
|||
f8 = self.make_face(v1,v3,v6) |
|||
shell=Part.makeShell([f1,f2,f3,f4,f5,f6,f7,f8]) |
|||
solid=Part.makeSolid(shell) |
|||
fp.Shape = solid |
|||
</translate> |
|||
# helper mehod to create the faces |
|||
{{Code|code= |
|||
def make_face(self,v1,v2,v3): |
|||
dir(mybox.Proxy) |
|||
wire = Part.makePolygon([v1,v2,v3,v1]) |
|||
face = Part.Face(wire) |
|||
return face |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:13--> |
|||
<!--T:103--> |
|||
Then, we have the view provider object, responsible for showing the object in the 3D scene: |
|||
This will return: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
['Type', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', |
|||
class ViewProviderOctahedron: |
|||
... |
|||
def __init__(self, obj): |
|||
'__str__', '__subclasshook__', '__weakref__'] |
|||
"Set this object to the proxy object of the actual view provider" |
|||
}} |
|||
obj.addProperty("App::PropertyColor","Color","Octahedron","Color of the octahedron").Color=(1.0,0.0,0.0) |
|||
<translate> |
|||
obj.Proxy = self |
|||
<!--T:42--> |
|||
def attach(self, obj): |
|||
We can see our {{incode|Type}} property. Let's check it: |
|||
"Setup the scene sub-graph of the view provider, this method is mandatory" |
|||
self.shaded = coin.SoGroup() |
|||
self.wireframe = coin.SoGroup() |
|||
self.scale = coin.SoScale() |
|||
self.color = coin.SoBaseColor() |
|||
</translate> |
|||
self.data=coin.SoCoordinate3() |
|||
{{Code|code= |
|||
self.face=coin.SoIndexedLineSet() |
|||
mybox.Proxy.Type |
|||
}} |
|||
<translate> |
|||
<!--T:104--> |
|||
self.shaded.addChild(self.scale) |
|||
This will return: |
|||
self.shaded.addChild(self.color) |
|||
self.shaded.addChild(self.data) |
|||
self.shaded.addChild(self.face) |
|||
obj.addDisplayMode(self.shaded,"Shaded"); |
|||
style=coin.SoDrawStyle() |
|||
style.style = coin.SoDrawStyle.LINES |
|||
self.wireframe.addChild(style) |
|||
self.wireframe.addChild(self.scale) |
|||
self.wireframe.addChild(self.color) |
|||
self.wireframe.addChild(self.data) |
|||
self.wireframe.addChild(self.face) |
|||
obj.addDisplayMode(self.wireframe,"Wireframe"); |
|||
self.onChanged(obj,"Color") |
|||
</translate> |
|||
def updateData(self, fp, prop): |
|||
{{Code|code= |
|||
"If a property of the handled feature has changed we have the chance to handle this here" |
|||
'box' |
|||
# fp is the handled feature, prop is the name of the property that has changed |
|||
}} |
|||
if prop == "Shape": |
|||
<translate> |
|||
s = fp.getPropertyByName("Shape") |
|||
self.data.point.setNum(6) |
|||
cnt=0 |
|||
for i in s.Vertexes: |
|||
self.data.point.set1Value(cnt,i.X,i.Y,i.Z) |
|||
cnt=cnt+1 |
|||
self.face.coordIndex.set1Value(0,0) |
|||
self.face.coordIndex.set1Value(1,1) |
|||
self.face.coordIndex.set1Value(2,2) |
|||
self.face.coordIndex.set1Value(3,-1) |
|||
<!--T:46--> |
|||
self.face.coordIndex.set1Value(4,1) |
|||
This is indeed the assigned value, so we know we're accessing the custom class through the FeaturePython object. |
|||
self.face.coordIndex.set1Value(5,3) |
|||
self.face.coordIndex.set1Value(6,2) |
|||
self.face.coordIndex.set1Value(7,-1) |
|||
<!--T:49--> |
|||
self.face.coordIndex.set1Value(8,3) |
|||
Now let's see if we can make our class a little more interesting, and maybe more useful as well. |
|||
self.face.coordIndex.set1Value(9,4) |
|||
self.face.coordIndex.set1Value(10,2) |
|||
self.face.coordIndex.set1Value(11,-1) |
|||
<!--T:105--> |
|||
self.face.coordIndex.set1Value(12,4) |
|||
[[#top|top]] |
|||
self.face.coordIndex.set1Value(13,0) |
|||
self.face.coordIndex.set1Value(14,2) |
|||
self.face.coordIndex.set1Value(15,-1) |
|||
===Adding properties=== <!--T:51--> |
|||
self.face.coordIndex.set1Value(16,1) |
|||
self.face.coordIndex.set1Value(17,0) |
|||
self.face.coordIndex.set1Value(18,5) |
|||
self.face.coordIndex.set1Value(19,-1) |
|||
<!--T:52--> |
|||
self.face.coordIndex.set1Value(20,3) |
|||
Properties are the lifeblood of a FeaturePython class. Fortunately, FreeCAD supports [[FeaturePython_Custom_Properties|a number of property types]] for FeaturePython classes. These properties are attached directly to the FeaturePython object and are fully serialized when the file is saved. To avoid having to serialize data yourself, it is advisable to only use these property types. |
|||
self.face.coordIndex.set1Value(21,1) |
|||
self.face.coordIndex.set1Value(22,5) |
|||
self.face.coordIndex.set1Value(23,-1) |
|||
<!--T:84--> |
|||
self.face.coordIndex.set1Value(24,4) |
|||
Adding properties is done using the {{incode|add_property()}} method. The syntax for the method is: |
|||
self.face.coordIndex.set1Value(25,3) |
|||
self.face.coordIndex.set1Value(26,5) |
|||
self.face.coordIndex.set1Value(27,-1) |
|||
<!--T:106--> |
|||
self.face.coordIndex.set1Value(28,0) |
|||
<!--Do not use Code template to avoid syntax highlighting--> |
|||
self.face.coordIndex.set1Value(29,4) |
|||
</translate> |
|||
self.face.coordIndex.set1Value(30,5) |
|||
add_property(type, name, section, description) |
|||
self.face.coordIndex.set1Value(31,-1) |
|||
<translate> |
|||
<!--T:55--> |
|||
def getDisplayModes(self,obj): |
|||
You can view the list of supported properties by typing: |
|||
"Return a list of display modes." |
|||
modes=[] |
|||
modes.append("Shaded") |
|||
modes.append("Wireframe") |
|||
return modes |
|||
</translate> |
|||
def getDefaultDisplayMode(self): |
|||
{{Code|code= |
|||
"Return the name of the default display mode. It must be defined in getDisplayModes." |
|||
mybox.supportedProperties() |
|||
return "Shaded" |
|||
}} |
|||
<translate> |
|||
<!--T:57--> |
|||
def setDisplayMode(self,mode): |
|||
Let's try adding a property to our box class. Switch to your code editor, move to the {{incode|__init__()}} method, and at the end of the method add: |
|||
return mode |
|||
</translate> |
|||
def onChanged(self, vp, prop): |
|||
{{Code|code= |
|||
"Here we can do something when a single property got changed" |
|||
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = "" |
|||
FreeCAD.Console.PrintMessage("Change property: " + str(prop) + "\n") |
|||
}} |
|||
if prop == "Color": |
|||
<translate> |
|||
c = vp.getPropertyByName("Color") |
|||
self.color.rgb.setValue(c[0],c[1],c[2]) |
|||
<!--T:58--> |
|||
def getIcon(self): |
|||
Note how we're using the reference to the (serializable) FeaturePython object {{incode|obj}}, and not the (non-serializable) Python class instance {{incode|self}}. |
|||
return """ |
|||
/* XPM */ |
|||
static const char * ViewProviderBox_xpm[] = { |
|||
"16 16 6 1", |
|||
" c None", |
|||
". c #141010", |
|||
"+ c #615BD2", |
|||
"@ c #C39D55", |
|||
"# c #000000", |
|||
"$ c #57C355", |
|||
" ........", |
|||
" ......++..+..", |
|||
" .@@@@.++..++.", |
|||
" .@@@@.++..++.", |
|||
" .@@ .++++++.", |
|||
" ..@@ .++..++.", |
|||
"###@@@@ .++..++.", |
|||
"##$.@@$#.++++++.", |
|||
"#$#$.$$$........", |
|||
"#$$####### ", |
|||
"#$$#$$$$$# ", |
|||
"#$$#$$$$$# ", |
|||
"#$$#$$$$$# ", |
|||
" #$#$$$$$# ", |
|||
" ##$$$$$# ", |
|||
" ####### "}; |
|||
""" |
|||
<!--T:60--> |
|||
def __getstate__(self): |
|||
Once you're done, save the changes and switch back to FreeCAD. Before we can observe the changes made to our code, we need to reload the module. This can be accomplished by restarting FreeCAD, but restarting FreeCAD every time we edit the code would be inconvenient. To make things easier type the following in the Python console: |
|||
return None |
|||
</translate> |
|||
def __setstate__(self,state): |
|||
{{Code|code= |
|||
return None |
|||
from importlib import reload |
|||
reload(box) |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:14--> |
|||
<!--T:61--> |
|||
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): |
|||
With the module reloaded, let's see what we get when we create an object: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
box.create('box_property_test') |
|||
FreeCAD.newDocument() |
|||
a=FreeCAD.ActiveDocument.addObject("App::FeaturePython","Octahedron") |
|||
Octahedron(a) |
|||
ViewProviderOctahedron(a.ViewObject) |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:62--> |
|||
You should see the new box object appear in the Tree view: |
|||
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: |
|||
*Select it and look at the Property editor. There, you should see the ''Description'' property. |
|||
*Hover over the property name on the left and the tooltip should appear with the description 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. |
|||
<!--T:107--> |
|||
[[#top|top]] |
|||
<!--T:86--> |
|||
Let's add some more properties. Return to your source code and add the following properties to the {{incode|__init__()}} method: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0 |
|||
selectionNode = coin.SoType.fromName("SoFCSelection").createInstance() |
|||
obj.addProperty('App::PropertyLength', 'Width', 'Dimensions', 'Box width').Width = '10 mm' |
|||
selectionNode.documentName.setValue(FreeCAD.ActiveDocument.Name) |
|||
obj.addProperty('App::PropertyLength', 'Height', 'Dimensions', 'Box height').Height = '1 cm' |
|||
selectionNode.objectName.setValue(obj.Object.Name) # here obj is the ViewObject, we need its associated App Object |
|||
selectionNode.subElementName.setValue("Face") |
|||
selectNode.addChild(self.face) |
|||
... |
|||
self.shaded.addChild(selectionNode) |
|||
self.wireframe.addChild(selectionNode) |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:16--> |
|||
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. |
|||
<!--T:108--> |
|||
== Working with simple shapes == <!--T:17--> |
|||
And let's also add some code to recompute the document automatically. Add the following line above the {{incode|return()}} statement in the {{incode|create()}} method : |
|||
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: |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
App.ActiveDocument.recompute() |
|||
import FreeCAD as App |
|||
}} |
|||
import FreeCADGui |
|||
<translate> |
|||
import FreeCAD |
|||
import Part |
|||
class Line: |
|||
def __init__(self, obj): |
|||
'''"App two point properties" ''' |
|||
obj.addProperty("App::PropertyVector","p1","Line","Start point") |
|||
obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(1,0,0) |
|||
obj.Proxy = self |
|||
<!--T:65--> |
|||
def execute(self, fp): |
|||
'''Be careful where you recompute a FeaturePython object. Recomputing should be handled by a method external to its class.''' |
|||
'''"Print a short message when doing a recomputation, this method is mandatory" ''' |
|||
fp.Shape = Part.makeLine(fp.p1,fp.p2) |
|||
</translate> |
|||
a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line") |
|||
[[Image:fpo_box_properties.png | right]] |
|||
Line(a) |
|||
<translate> |
|||
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification) |
|||
FreeCAD.ActiveDocument.recompute() |
|||
<!--T:109--> |
|||
Now, test your changes as follows: |
|||
*Save your changes and reload your module. |
|||
*Delete all objects in the Tree view. |
|||
*Create a new box object from the Python console by calling {{incode|box.create('myBox')}}. |
|||
<!--T:87--> |
|||
Once the box is created and you've checked to make sure it has been recomputed, select the object and look at its properties. You should note two things: |
|||
*A new property group: ''Dimensions''. |
|||
*Three new properties: ''Height'', ''Length'' and ''Width''. |
|||
<!--T:89--> |
|||
Note also how the properties have units. More specifically, they have taken on the linear units set in the user preferences ({{MenuCommand|Edit → Preference... → General → Units}}). |
|||
{{Clear}} |
|||
<!--T:90--> |
|||
No doubt you noticed that three different values were entered for the dimensions: a floating-point value ({{incode|10.0}}) and two different strings ({{incode|'10 mm'}} and {{incode|'1 cm'}}). The {{incode|App::PropertyLength}} type assumes floating-point values are in millimeters, string values are parsed according to the units specified, and in the GUI all values are converted to the units specified in the user preferences ({{incode|mm}} in the image). This built-in behavior makes the {{incode|App::PropertyLength}} type ideal for dimensions. |
|||
<!--T:110--> |
|||
[[#top|top]] |
|||
===Trapping events=== <!--T:69--> |
|||
<!--T:70--> |
|||
The last element required for a basic FeaturePython object is event trapping. A FeaturePython object can react to events with callback functions. In our case we want the object to react whenever it is recomputed. In other words we want to trap recomputes. To accomplish this we need to add a function with a specific name, {{incode|execute()}}, to the object class. There are several other events that can be trapped, both in the FeaturePython object itself and in the [[Viewprovider|ViewProvider]], which we'll cover in [[Create_a_FeaturePython_object_part_II|Create a FeaturePython object part II]]. |
|||
<!--T:113--> |
|||
For a complete reference of methods available to implement on FeautrePython classes, see [[FeaturePython_methods|FeaturePython methods]]. |
|||
<!--T:92--> |
|||
Add the following after the {{incode|__init__()}} function: |
|||
</translate> |
|||
{{Code|code= |
|||
def execute(self, obj): |
|||
""" |
|||
Called on document recompute |
|||
""" |
|||
print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type)) |
|||
}} |
}} |
||
<translate> |
<translate> |
||
<!--T:20--> |
|||
<!--T:72--> |
|||
Same code with use '''ViewProviderLine''' |
|||
Test the code by again following these steps: |
|||
*Save and reload the module. |
|||
*Delete all objects. |
|||
*Create a new box object. |
|||
<!--T:73--> |
|||
You should see the printed output in the Python Console, thanks to the {{incode|recompute()}} call we added to the {{incode|create()}} method. Of course, the {{incode|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. |
|||
<!--T:93--> |
|||
That's it, you now know how to build a basic, functional FeaturePython object! |
|||
<!--T:111--> |
|||
[[#top|top]] |
|||
===Complete code=== <!--T:77--> |
|||
</translate> |
</translate> |
||
{{Code|code= |
{{Code|code= |
||
import FreeCAD as App |
import FreeCAD as App |
||
import FreeCADGui |
|||
import FreeCAD |
|||
import Part |
|||
def create(obj_name): |
|||
class Line: |
|||
""" |
|||
def __init__(self, obj): |
|||
Object creation method |
|||
'''"App two point properties" ''' |
|||
""" |
|||
obj.addProperty("App::PropertyVector","p1","Line","Start point") |
|||
obj.addProperty("App::PropertyVector","p2","Line","End point").p2=FreeCAD.Vector(100,0,0) |
|||
obj.Proxy = self |
|||
def execute(self, fp): |
|||
'''"Print a short message when doing a recomputation, this method is mandatory" ''' |
|||
fp.Shape = Part.makeLine(fp.p1,fp.p2) |
|||
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name) |
|||
class ViewProviderLine: |
|||
def __init__(self, obj): |
|||
''' Set this object to the proxy object of the actual view provider ''' |
|||
obj.Proxy = self |
|||
box(obj) |
|||
def getDefaultDisplayMode(self): |
|||
''' Return the name of the default display mode. It must be defined in getDisplayModes. ''' |
|||
return "Flat Lines" |
|||
App.ActiveDocument.recompute() |
|||
Line(a) |
|||
ViewProviderLine(a.ViewObject) |
|||
App.ActiveDocument.recompute() |
|||
}} |
|||
return obj |
|||
<translate> |
|||
class box(): |
|||
== Further information == <!--T:18--> |
|||
There are a few very interesting forum threads about scripted objects: |
|||
def __init__(self, obj): |
|||
<!--T:22--> |
|||
""" |
|||
- http://forum.freecadweb.org/viewtopic.php?f=22&t=13740 |
|||
Default constructor |
|||
""" |
|||
self.Type = 'box' |
|||
<!--T:23--> |
|||
- http://forum.freecadweb.org/viewtopic.php?t=12139 |
|||
obj.Proxy = self |
|||
<!--T:26--> |
|||
- https://forum.freecadweb.org/viewtopic.php?f=18&t=13460&start=20#p109709 |
|||
obj.addProperty('App::PropertyString', 'Description', 'Base', 'Box description').Description = "" |
|||
<!--T:27--> |
|||
obj.addProperty('App::PropertyLength', 'Length', 'Dimensions', 'Box length').Length = 10.0 |
|||
- https://forum.freecadweb.org/viewtopic.php?f=22&t=21330 |
|||
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 |
|||
""" |
|||
print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type)) |
|||
<!--T:24--> |
|||
}} |
|||
In addition to the examples presented here have a look at FreeCAD source code [https://github.com/FreeCAD/FreeCAD/blob/master/src/Mod/TemplatePyMod/FeaturePython.py src/Mod/TemplatePyMod/FeaturePython.py] for more examples. |
|||
<translate> |
|||
<!--T: |
<!--T:112--> |
||
[[#top|top]] |
|||
{{docnav|PySide|Embedding FreeCAD}} |
|||
<!--T: |
<!--T:78--> |
||
{{Docnav |
|||
{{Userdocnavi}} |
|||
| |
|||
|[[Create_a_FeaturePython_object_part_II|Create a FeaturePython object part II]] |
|||
<!--T:19--> |
|||
|IconL= |
|||
[[Category:Poweruser Documentation]] |
|||
|IconR= |
|||
}} |
|||
<!--T:30--> |
|||
[[Category:Python Code]] |
|||
</translate> |
</translate> |
||
{{Powerdocnavi{{#translation:}}}} |
|||
[[Category:Developer Documentation{{#translation:}}]] |
|||
[[Category:Python Code{{#translation:}}]] |
|||
{{clear}} |
{{clear}} |
Latest revision as of 17:25, 16 February 2023
Introduction
FeaturePython objects (also referred to as Scripted objects) provide the ability to extend FreeCAD with objects that integrate seamlessly into the FreeCAD framework.
This encourages:
- Rapid prototyping of new objects and tools with custom Python classes.
- Saving and restoring data (also known as serialization) through
App::Property
objects, without embedding any script in the FreeCAD document file. - Creative freedom to adapt FreeCAD for any task.
On this page we are going to construct a working example of a FeaturePython custom class, identifying all the major components and gaining an understanding of how everything works as we go along.
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 customization with a user defined Python class.
This custom Python class takes a reference to one of these objects and modifies it. For example, the Python class may add properties to the object or link it to other objects. In addition the Python class may implement certain methods to enable the object to respond to document events, making it possible to trap object property changes and document recomputes.
When working with custom classes and FeaturePython objects it is important to know that the custom class and its state are not saved in the document as this would require embedding a script in a FreeCAD document file, which would pose a significant security risk. Only the FeaturePython object itself is saved (serialized). But since the script module path is stored in the document, a user need only install the custom Python class code as an importable module, following the same folder structure, to regain the lost functionality.
Setting things up
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. But if you have another idea in mind, feel free to use that instead.
If you don't know where the FreeCAD Macro folder is type FreeCAD.getUserMacroDir(True)
in FreeCAD's Python console:
- On Linux it is usually /home/<username>/.local/share/FreeCAD/Macro/ (version 0.20 and above) or /home/<username>/.FreeCAD/Macro/ (version 0.19 and below).
- On Windows it is %APPDATA%\FreeCAD\Macro\, which is usually C:\Users\<username>\Appdata\Roaming\FreeCAD\Macro\.
- On macOS it is usually /Users/<username>/Library/Application Support/FreeCAD/Macro/.
Now we need to create some folders and files:
- In the Macro folder create a new folder called fpo.
- In the fpo folder create an empty file: __init__.py.
- 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).
Your folder structure should look like this:
Macro/ |--> fpo/ |--> __init__.py |--> box/ |--> __init__.py |--> box.py
The fpo folder provides a nice place to play with new FeaturePython objects and the box folder is the module we will be working in. __init__.py tells Python that there is an importable module in the folder, and box.py will be the class file for our new FeaturePython Object.
With our module paths and files created, let's make sure FreeCAD is set up properly:
- Start FreeCAD (if you haven't done so already).
- Enable the Report view (View → Panels → Report view).
- Enable the Python console (View → Panels → Python console) see FreeCAD Scripting Basics.
Finally, navigate to the Macro/fpo/box folder and open box.py in your favorite code editor. We will only edit that file.
A FeaturePython object
Let's get started by writing our class and its constructor:
class box():
def __init__(self, obj):
"""
Default constructor
"""
self.Type = 'box'
obj.Proxy = self
The __init__()
method breakdown:
def __init__(self, obj):
|
Parameters refer to the Python class itself and the FeaturePython object that it is attached to. |
self.Type = 'box'
|
String definition of the custom Python type. |
obj.Proxy = self
|
Stores a reference to the Python instance in the FeaturePython object. |
Add the following code at the top of the file:
import FreeCAD as App
def create(obj_name):
"""
Object creation method
"""
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)
box(obj)
return obj
The create()
method breakdown:
import FreeCAD as App
|
Standard import for most Python scripts, the App alias is not required. |
obj = ... addObject(...)
|
Creates a new FreeCAD FeaturePython object with the name passed to the method. If there is no name clash, this will be the label and the name of the created object. Otherwise, a unique name and label will be created based on 'obj_name'. |
box(obj)
|
Creates our custom class instance. |
return obj
|
Returns the FeaturePython object. |
The create()
method is not required, but it provides a nice way to encapsulate the object creation code.
Testing the code
Now we can test our new object. Save your code and return to FreeCAD. Make sure you have 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:
mybox = box.create('my_box')
You should see a new object appear in the Tree view labelled "my_box".
Note that the icon is gray. FreeCAD is telling us that the object is not able to display anything in the 3D view. Click on the object and look at its properties in the Property editor. There is not much there, just the name of the object.
Also note that there is a small blue check mark next to the FeaturePython object in the Tree view. That is because when an object is created or changed it is "touched" and needs to be recomputed. Pressing the Std Refresh button will accomplish this. We will add some code to automate this later.
Let's look at our object's attributes:
dir(mybox)
This will return:
['Content', 'Document', 'ExpressionEngine', 'FullName', 'ID', 'InList',
...
'setPropertyStatus', 'supportedProperties', 'touch']
There are a lot of attributes because we're accessing the native FreeCAD FeaturePyton object created in the first line of our create()
method. The Proxy
property we added in our __init__()
method is there too.
Let's inspect it with the dir()
method:
dir(mybox.Proxy)
This will return:
['Type', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__',
...
'__str__', '__subclasshook__', '__weakref__']
We can see our Type
property. Let's check it:
mybox.Proxy.Type
This will return:
'box'
This is indeed the assigned value, so we know we're accessing the custom class through the FeaturePython object.
Now let's see if we can make our class a little more interesting, and maybe more useful as well.
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 and are fully serialized when the file is saved. To avoid having to serialize data yourself, it is advisable to only use these property types.
Adding properties is done using the add_property()
method. The syntax for the method is:
add_property(type, name, section, description)
You can view the list of supported properties by typing:
mybox.supportedProperties()
Let's try adding a property to our box class. Switch to your code editor, move to the __init__()
method, and 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 instance self
.
Once you're done, save the changes and switch back to FreeCAD. Before we can observe the changes made to our code, we need to reload the module. This can be accomplished by restarting FreeCAD, but restarting FreeCAD every time we edit the code would be inconvenient. To make things easier type the following in the Python console:
from importlib import reload
reload(box)
With the module reloaded, let's see what we get when we create an object:
box.create('box_property_test')
You should see the new box object appear in the Tree view:
- Select it and look at the Property editor. There, you should see the Description property.
- Hover over the property name on the left and the tooltip should appear with the description 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.
Let's add some more properties. Return to your source code and add the following properties to the __init__()
method:
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'
And let's also add some code to recompute the document automatically. Add the following line above the return()
statement in the create()
method :
App.ActiveDocument.recompute()
Be careful where you recompute a FeaturePython object. Recomputing should be handled by a method external to its class.
Now, test your changes as follows:
- Save your changes and reload your module.
- Delete all objects in the Tree view.
- Create a new box object from the Python console by calling
box.create('myBox')
.
Once the box is created and you've checked to make sure it has been recomputed, select the object and look at its properties. You should note two things:
- A new property group: Dimensions.
- Three new properties: Height, Length and Width.
Note also how the properties have units. More specifically, they have taken on the linear units set in the user preferences (Edit → Preference... → General → Units).
No doubt you noticed that three different values were entered for the dimensions: a floating-point value (10.0
) and two different strings ('10 mm'
and '1 cm'
). The App::PropertyLength
type assumes floating-point values are in millimeters, string values are parsed according to the units specified, and in the GUI all values are converted to the units specified in the user preferences (mm
in the image). This built-in behavior makes the App::PropertyLength
type ideal for dimensions.
Trapping events
The last element required for a basic FeaturePython object is event trapping. A FeaturePython object can react to events with callback functions. In our case we want the object to react whenever it is recomputed. In other words we want to trap recomputes. To accomplish this we need to add a function with a specific name, execute()
, to the object class. There are several other events that can be trapped, both in the FeaturePython object itself and in the ViewProvider, which we'll cover in Create a FeaturePython object part II.
For a complete reference of methods available to implement on FeautrePython classes, see FeaturePython methods.
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 by again following these steps:
- Save and reload the module.
- Delete all objects.
- Create a new 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.
That's it, you now know how to build a basic, functional FeaturePython object!
Complete code
import FreeCAD as App
def create(obj_name):
"""
Object creation method
"""
obj = App.ActiveDocument.addObject('App::FeaturePython', obj_name)
box(obj)
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
"""
print('Recomputing {0:s} ({1:s})'.format(obj.Name, self.Type))
- FreeCAD scripting: Python, Introduction to Python, Python scripting tutorial, FreeCAD Scripting Basics
- Modules: Builtin modules, Units, Quantity
- Workbenches: Workbench creation, Gui Commands, Commands, Installing more workbenches
- Meshes and Parts: Mesh Scripting, Topological data scripting, Mesh to Part, PythonOCC
- Parametric objects: Scripted objects, Viewproviders (Custom icon in tree view)
- Scenegraph: Coin (Inventor) scenegraph, Pivy
- Graphical interface: Interface creation, Interface creation completely in Python (1, 2, 3, 4, 5), PySide, PySide examples beginner, intermediate, advanced
- Macros: Macros, How to install macros
- Embedding: Embedding FreeCAD, Embedding FreeCADGui
- Other: Expressions, Code snippets, Line drawing function, FreeCAD vector math library (deprecated)
- Hubs: User hub, Power users hub, Developer hub