I’m trying to create my own objects in python. I have followed the information on the wiki. I still have several questions and I wanted to do a reality check with what I have.
If I understood correctly, the process involves creating an object and a handler class that would take care of the view (ViewProviderObject). I’m a little lost in what should be on each one. From what I have seen, I have to recreate the shape ob the object when a geometrical property change, there is no way of attaching those properties to measurements.
I understand that the system is more flexible that way, one can have objects that do not have a geometrical representation. However, what I’m trying to do could be quite simple. I just want to have an object that can have more than one shape (short of like a group, but I have experimented with it and it does not seem to do what I need) and relations between those shapes that are not necessarily simple geometrical measurements. It would be king of an extended part object with more properties.
I think that I may be going at it the wrong way and I wanted to see if someone can give me a hint. Let me give you an example:
I have object A. This object has a face and a line that are used to represent different things. However, the line is the result of some calculations on the face. It also has some properties that are not purely geometrical, that I had managed.
Hi,
It works like this: in freecad everything is separated between core application and gui representation. that extends to all of what you see in the 3D scene. Objects are separated between their geometry definition, and how that geometry must be “drawn” on the 3D viewport. This is actually not really a rule (nothing prevents you to not follow this) but it’s very handy.
In the case of python objects, you would generally store in the Object itself all that is how your object gets constructed, and in its associated ViewObject how that object gets represented. For example, if freecad is run in console mode, you will only have access to the Object counterpart, no ViewObject will be created.
These two are pure python objects (classes), so they can basically hold whatever you want (many shapes, etc). But it is interesting to use the freecad properties because doing so, the contents of those properties will be saved when you save your file.
So, if you want one object to contain several shapes, you would just add several shape properties to your object.
But you should see if it wouldn’t be more interesting to save only one shape, and have several representations of it…
You could even have pure “visual” objects, with no real existence in the scene geometry (for example widgets, manipulators, etc) or objects with no representation on screen… everything is possible.
So, do I have to handle any change in the object? I mean, if there is a change in the shape of an object (let’s say move a vertex) it will not get update on its representation unless I write some code to handle that, right?. I can see how that could be handy but it could also be a pain. I think that using a simple recompute in that scenario would be better. I would assume that most of the time what it is wanted it to keep both representation and object in sync. I know there would be exceptions but I’m thinking about the general case.
Are there any hooks created by default to handle changes?
I’m having problems showing a line. What would have to be included in the viewprovider to show a line? Where could I find information about what goes in the view provider?
actually you have a couple of methods (functions), inside both the object and the viewobject, that get executed on certain occasions (for example when a parameter is changed). The example file is normally well explained.
There, you can do what you want with your shape, the usual behaviour is to recompute a new shape, then a new representation of it…
If you want an example, look into the Mod/Draft/Draft.py file around line 740. Since that one is a dimension, there was no shape at all (it’s a purely visual object, no need to interact in the scene geometry), so everything is done in the view object itself. But you have the same methods in the base object too.
To reply your last question, if you have a Part shape, you must create its Inventor representation. Part shapes have a writeInventor() method that does it automatically, but you can also do it by hand. In case your line is purely indicative, you don’t even need a shape behind, you can directly create your inventor node. See the dimension example above to see how it works…
The shapes in freecad (at least at the moment) cannot be changed (for ex. a vertex moved). You need to recompute a new shape if you want to change a vertex position. That’s basically the advantage of being parametric, objects are defined only by their parameters…
I do need the line in this case so I’ll keep both representation and object.
My example with a vertex was probably not well chosen. I mean the end point of a line. If I understand you correctly, unless I want to do some move, rotate, scale, I would have to repeat the process of creating the line to move that point right?
Same deal with a planar same I guess. I mean if that line becomes an edge of a polygon, I would have to recreate the polygon once I move the shape.
I have looked at the example but I’m still trying to understand what the coin stuff does.
I will take a look at draft.py to see if I can get a clearer picture.
exactly.
Think to the coin stuff like this: the 3d view is a coin (or inventor, same thing) scenegraph. it’s like a tree, containing nodes. You can add nodes, remove nodes, transform nodes, change their properties, etc. Everything gets reflected in realtime. It’s very powerful. Our Part (or opencascade) geometry, on the other hand, is just the definition of 3d geometry. where the vertices are in the 3d space, where are the edges, etc. It has no idea of how it must be rendered on the screen. Normally, in a “commercial” app, the programmers took care of that for you, they “convert” your data to a format that can be rendered in a 3D view (direct3D, opengl, etc). Here, we have complete access to the pipeline, and can define exactly what is rendered and how. You can use the writeInventor() method to just skip that step and get the “default” inventor node from your shape, and simply add it to the main scenegraph (which, in a python feature, is also taken care automatically, you must just provide the node).
But if you want to can do very powerful things like specifying a linewidth, color, displaying symbols at the endpoints, text info, make things selectable/draggable, etc…), all based on your simple line.
Hi,
I reworked our python feature framework some time ago a bit where it is now possible to use more advanced feature classes written in C++ like our Part::Feature directly in python. If you don’t need to visualize special custom properties of your feature class a view provider is almost obsolete, then. This means for standard tasks you don’t need the coin stuff at all on python side. You’ll find some examples in the file src/Mod/TemplatePyMod/FeaturePython.py.
However, what I’m trying to do could be quite simple. I just want to have an object that can have more than one shape (short of like a group, but I have experimented with it and it does not seem to do what I need) and relations between those shapes that are not necessarily simple geometrical measurements. It would be king of an extended part object with more properties.
In your python feature class you can of course have further shape properties where you can e.g. implement in its execute() method the final shape (and assign to the property “Shape”) which is automatically displayed by the standard view provider.
But if you want to behave the relations you’re talking parametric then I think it’s better to have not one “fat” feature with all the different shapes inside but one shape (i.e. already the Shape property and no further custom shape property) per feature and add a link property (of type App::PropertyLink) as a custom property. Then you simply have to set the correct linking to all your objects and you don’t have to care about anything else. Now, if you change one shape (or a property a shape depends on) and do a recompute of the document everything is done automagically.
So, do I have to handle any change in the object? I mean, if there is a change in the shape of an object (let’s say move a vertex) it will not get update on its representation unless I write some code to handle that, right?
This was true with our first approach. But now if using e.g. Part::Feature you don’t need to care about this anymore.
Are there any hooks created by default to handle changes?
Yes. In our C++ feature classes the notification chain works this way:
- you change a property
- this calls the onChaged() method of its parent feature
- the feature calls onChanged() of its parent class until the grand(-grand,…)-parent class is reached
- this notifies the App document
- the App document notifies the GUI document (in case the GUI is up)
- the GUI document searches for the associated view provider and notifies it which property has changed. If e.g. it’s the shape it rebuilds the Inventor structure in the scenegraph
So, this means if you have changed a property and want to adjust another property immediately without waiting for a recompute of the document you can simply rewrite the onChanged() method and do the task there instead of the execute() method. But be careful: you must not change the property again which caused the notifications otherwise you’ll run into an endless loop.
I’m having problems showing a line. What would have to be included in the viewprovider to show a line? Where could I find information about what goes in the view provider?
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
def execute(self, fp):
''' Print a short message when doing a recomputation, this method is mandatory '''
fp.Shape = Part.makeLine(fp.p1,fp.p2)
class ViewProviderLine:
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 getDefaultDisplayMode(self):
''' Return the name of the default display mode. It must be defined in getDisplayModes. '''
return "Flat Lines"
def __getstate__(self):
''' When saving the document this object gets stored using Python's cPickle module.
Since we have some un-pickable here -- the Coin stuff -- we must define this method
to return a tuple of all pickable objects or None.
'''
return None
def __setstate__(self,state):
''' When restoring the pickled object from document we have the chance to set some
internals here. Since no data were pickled nothing needs to be done here.
'''
return None
def makeLine():
FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
ViewProviderLine(a.ViewObject)
I have just checked-in a slightly reworked python feature framework. Now the view provider for usual python features is fully optional. The above example script can be reduced to the following few lines:
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
def execute(self, fp):
''' Print a short message when doing a recomputation, this method is mandatory '''
fp.Shape = Part.makeLine(fp.p1,fp.p2)
In client code to create such a line do:
FreeCAD.newDocument()
a=FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Line")
Line(a)
a.ViewObject.Proxy=0 # just set it to something different from None (this assignment is needed to run an internal notification)
Great, I’ll try to compile this weekend (there is no night build right?) and give it a try.
I have tried a bunch of different options including the line example but I cannot seem to get it to work, not even the line example.
I have downloaded and compiled from svn (rev. 3142)
The object show and no error is given but it does not appear in the viewport.
I’m posting the code here. Maybe someone can shed some light on it:
Class keel in its own file Keel.py :
import FreeCAD as App
from math import sin, cos
from pivy import coin
import Part
######################################New object type for freecad ##############
#Keel would contain geometry and properties of any apendage of type keel
################################################################################
class Keel:
'''Object to keep keel characteristics and geometry'''
def __init__(self, obj):
"Properties that define the keel"
#Temporary variables to size with dummy values
rc = 1
tc = 1
d = 1
xLE = 1
#Check for a hull to use, preliminary sizing of keel
try:
hull = App.ActiveDocument.hull
rc = hull.Shape.BoundBox.XLength / 20.0
tc = hull.Shape.BoundBox.XLength / 25.0
d = hull.Shape.BoundBox.YLength / 1.5
xLE = hull.Shape.BoundBox.XLength / 2.0
except:
App.Console.PrintMessage('A hull could not be found, check size of keel\n')
obj.addProperty("App::PropertyLength","rootChord","Root","Chord at waterline").rootChord=rc
obj.addProperty("App::PropertyLength","tipChord","Tip","Chord at maximun draft").tipChord=tc
obj.addProperty("App::PropertyLength","draft","Keel", "Draft").draft=d
obj.addProperty("App::PropertyAngle","sweepback","Keel", "Draft").sweepback=1.0
obj.addProperty("App::PropertyLength","xPosLE","Root", "X position of leading edge").xPosLE=xLE
obj.addProperty("Part::PropertyPartShape","Shape","Keel", "Lateral shape of keel")
obj.Proxy = self
def execute(self,fp):
"Do something when doing a recomputation, this method is mandatory"
App.Console.PrintMessage("Recomputing Python Keel feature\n")
#Define the quater chord line
self.qc1 = App.Vector(fp.xPosLE + 0.25* fp.rootChord,0,0)
self.qc2 = App.Vector(fp.xPosLE + sin((fp.sweepback)*fp.draft) - 0.25* fp.tipChord ,0,-fp.draft)
#Define the 4 vertices that represent the keel shape
r1 = App.Vector(fp.xPosLE,0,0)
r2 = App.Vector(r1.x+fp.rootChord,0,r1.z)
t1 = App.Vector(fp.xPosLE + sin((fp.sweepback)*fp.draft),0,-fp.draft)
t2 = App.Vector(t1.x + fp.tipChord,0,t1.z)
wire=Part.makePolygon([r1,r2,t2,t1,r1])
fp.Shape=Part.Face(wire)
Code associated with a menu to create it:
class MakeKeel():
def Activated(self):
doc = App.ActiveDocument
App.Console.PrintMessage('Adding a keel to the boat\n')
activeKeel = doc.addObject("App::FeaturePython","keel")
#doc.Hull.addObject(activeKeel)
doc.keel.Label="Keel"
Keel(activeKeel)
activeKeel.ViewObject.Proxy=0
doc.recompute()
def GetResources(self):
return {'Pixmap' : 'path_to_an_icon/myicon.png', 'MenuText': 'Add Keel', 'ToolTip': 'Create a keel for the model'}
Sorry for posting all that, I do not know if this is appropriated but it the only way I know
That’s quite easy. Just replace the line in you command
activeKeel = doc.addObject("App::FeaturePython","keel")
with
activeKeel = doc.addObject("Part::FeaturePython","keel")
(The difference is that prefix App is replaced with Part)
The point is that the view provider for App::FeaturePython is quite dumb and doesn’t know of shapes and how to render them. Btw, this view provider doesn’t provide any “Display modes” either.
I think FreeCAD is really entering the Parametric Era now… ![]()
I have my first FreeCAD object!
![]()
Now I’m really thinking to convert all the draft module object to parametric ones… With Werner’s new Part Python feature, it’ll be pretty easy… And since they have a Shape attribute, just like “dumb” shapes, almost all the internal working should work (almost) exactly as before… For example:
- Rectangles can become parmetric rectangles, with width and height parameters
- Circles can become parametric circles, with radius
- Lines can become parametric lines, with start and endpoints
- Wires can become parametric, with a list of points and a Closed attribute
- Arcs can have radius, start and end parameters
- Unions and Subtractions can remember their original shapes (same way as Part unions & subtractions)
Next thing I’m only imagining, imagine a way to set/change parameters graphically… (like, edit a 3D point onscreen, having length and width appearing…) That would be a quite powerful editmode!
Perfect editmode IMHO:
- You draw an object
- It gets automatically dimension annotation: width, height, diameter or radius, distance to reference point (eg. 0,0,0) - “weak” dimensions
- If you modify dimension everything should be recompute
- “Weak” dimension should can be convert to “strong” dimension. Difference: you have
|<--12--->|<5->|
__________
| |_____
|______________|
|<-----17----->|
12 is weak, 5 is strong. Modify 17 to 15
|<-10-->|<5->|
________
| |____
|____________|
|<-----15--->|
Aha, there you are talking constraints…
I wasn’t so ambitious at first (let’s begin with making things parametrics)
but yeah that’s the plan ![]()
I don’t know if working parametric as you describe is the best way to go. Please, take this as an idea and not as criticism
I think the way to go in the 2d case is to work directly with constrains, even if in some cases it would be transparent to the user. For example, a rectangle is nothing but a group of four lines perpendicular to each other on their intersections. Trying to implement that as a rectangle defined by it length and width would impose a higher design intent. It would mean that we want that shape to stay as a rectangle. In many cases (at least in my experience) one draws a rectangle just because it is similar to the final shape we want, for example after trimming a corner with another curve. What I’m trying to say is that that road is too constructive solid geometry (CSG) in my opinion. Yes, the rectangle could stay hidden in the code once it is trimmed, but this approach has further compromises that it seem at first (at least for me when I started thinking about it).
Again, do not take this more than it is, my opinion. I think constrains (although also parametric) are more flexible that parametric objects defined in terms of the minimum set of parameters. I find more useful to be able to constrain the points of an spline or arc than to define it by it radius, but maybe it’s just in the kind of work I do.
My 2 cents.
Actually the main reason is that making stuff parametric is easy now, thanks to Werner, while making constraints would probably take me a couple of months ![]()
But eventually we get there! Also, since I’m the less qualified in that matter, it’s probably more efficient to let Jürgen or Werner trace the first path in that jungle, then I can follow more quickly…
Any news about integrating sketchsolve Jürgen?
I’m still having some problems with the custom objects I’m creating:
The shape is not recreated when I open the file. I add a custom object and I can see its shape; however, when I try to open the file from scratch (with the object saved) its shape is nowhere to be seen. I can see the object in the tree, but not its shape in the viewport. Do I have to write some code to manage it when I open a file?
The other issue is probably more a feature request than anything else. I would like to draw a sphere as part of the object to position certain properties. For example, it is very interesting to my application to keep a visual cue of the center of gravity of the submerged part of the hull. This has to be a non editable property (non editable for the user) and I would like to present it graphically. I’m assuming the automatic management of shapes in an object implies that there is only one (I have tried and that is what it seems to me). Therefore, only a shape get drawn for object. Would it be possible to draw more than one without diving into managing the whole view object manually? or Do you have any idea how to do this better?
Thanks