Open dulouie opened 5 years ago
if idrop the body object to binder:
<ShapeBinder> ViewProviderShapeBinder.cpp(319): cannot resolve relative link
This may help: https://github.com/realthunder/FreeCAD_assembly3/issues/89
Thanks
Okay these function update the binder:
def updateBinder(binder):
Gui.Selection.clearSelection()
Gui.Selection.addSelection(binder)
binder.ViewObject.doubleClicked()
Besides an binder.addGeometry() methode would be nice.
The addSelection
call is very important to provide the necessary context information. Imaging you have two links to the same body containing a binder. In the tree view, the same binder appears in two branches, so the update result after double click will be different depending on which item you click. If there is only one binder, then your code above works as expected. If the binder appears in multiple places, say there is another Link
to the body containing a Binder001, then below is how to establish the correct context.
Gui.Selection.addSelection(Link, 'Binder001.')
The same principle applies for drag and drop. Depending on where you drop the object, the relative link is calculated differently. You can experiment with various operations with your mouse. All the Gui.Selection, drag & drop, and double click calls are logged in Python console just in case you want to script it.
I have some trouble to get the arguments for addSelection().
With myobject.Parents
i can get these arguments but sometimes it is strange. With linked object you get a second list witch is switching. And there is no documentation, so i have to find the logic behind this behavior by myself.
class SelectionPath:
def resolve(self, obj):
self.obj = obj
self.doc = App.ActiveDocument
if len(obj.Parents)== 1:
self.linked = False
self.root = obj.Parents[0][0]
self.path = obj.Parents[0][1]
print("[0]rootObject= " + self.root.Name)
print("[0]SelectionPath= " + self.path)
else:
self.linked = True
self.root = obj.Parents[0][0]
self.path = obj.Parents[0][1]
self.root2 = obj.Parents[1][0]
self.path2 = obj.Parents[1][1]
print("[0]rootObject= " + self.root.Name)
print("[0]SelectionPath= " + self.path)
print("[1]rootObject= " + self.root2.Name)
print("[1]SelectionPath= " + self.path2 )
def select(self):
Gui.Selection.clearSelection()
if self.linked:
Gui.Selection.addSelection(doc.Name,self.root2.Name,self.path2)
else:
Gui.Selection.addSelection(doc.Name,self.root.Name,self.path)
Is there any simpler plan to get it? Actually i want to shapebinder all last features of bodys in the document expect the activated body. I wish a function to simply add geometry to the binder like binder.addGeometry(obj) so i've set binder.Support mutable.
support = [(self.root,(self.path))]
binder.Support= support
The path
is supposed to be a string representing the object hierarchy in the tree view. You can check the Python console to see how it is expressed. For example,
Gui.Selection.addSelection('Unnamed','Assembly','Parts.Box.')
So, all three arguments are supposed to be string, addSelection(docName, objName, subName)
.
In case you have the document object, then there is a second shorter form, addSelection(obj, subName)
.
You can directly set binder.Support
if you know what you are doing. To turn off read only, run
binder.setPropertyStatus('Support', '-Immutable')
binder.Support = [ (self.root, (self.path)) ]
Assuming the self.path
above is a correct subname.
Okay but i don't understand the order of myobj.Parents output. Sometimes is the App::Link and sometimes the Partdesign::Body object the first list entry. Both feature objects are actually App::Link to an external file. Look at my example assembly. The output from the pad of the baselink is:
[0]rootObject= Body
[0]SelectionPath= Pad.
[1]rootObject= Link
[1]SelectionPath= Pad.
And the pad from link1:
[0]rootObject= Link001
[0]SelectionPath= Pad.
[1]rootObject= Body
[1]SelectionPath= Pad.
As you can see the order has switched. Can you explain me why? assembly.zip
To reproduce it, you have to open the assembly.fcstd, then close it and all linked documents and open it again. Or do you know an other function to get the subName?
Oh, now I see. You are using the Parents
attribute. Internally it is using a map on the object pointer, so of course the order is going to change on document re-open. If you want a consistent order, just store the result in a dictionary. And since the returned object may come from different documents, you may want to include the document name to resolve duplication.
{ o.Document.Name + '#' + o.Name : (o, sub) for o, sub in obj.Parents }
But I don't think Parents
is what you want. Can you elaborate on your intention?
Basically i want to shapebinder all bodies of the document (the last feature) to my new created body.
I can get those body's with App.ActiveDocument.Objects
then filter for <body object>
or <App::Link object>
. And to get the last feature, the first entry of OutListRecursive
is the last feature of the body.
And now i have a list with features from different bodys. To add it sequential to shapebinder support the subname
is needed. Parents
is the only function i found for that. So i have to write a function that resolve the structure of the given objects and translate it into subname
strings for the correct binder format.
Or simpler if possible: binder.Support=[referenceobj1, referenceobj2, referenceobj3...]
If you want the Tip of the body, then you should bind to that body directly without any subname, just in case you want to add feature later on. But if you insist, here is the code,
# This gives all top level (i.e. without parent) bodies and link to bodies
bodies = [ o for o in App.ActiveDocument.Objects
if o.getLinkedObject(True).isDerivedFrom('PartDesign::Body') and not o.Parents ]
# prepare the property link info
links = [ (o, (o.getLinkedObject(True).Tip.Name+'.')) for o in bodies ]
# add a new body
body = App.ActiveDocument.addObject('PartDesign::Body', 'Body')
# add binder
binder = body.newObject('PartDesign::SubShapeBinder','Binder')
# Set support
# First, turn off readonly
binder.setPropertyStatus('Support','-Immutable')
binder.Support = links
# Turn on readonly again, optionally
binder.setPropertyStatus('Support','Immutable')
I've written a small testscript for different cases. And only case three is fully working with relative placement. I guess the context in case two is missing?
import FreeCAD
case = 2
doc = App.ActiveDocument
# This gives all top level (i.e. without parent) bodies and link to bodies
bodies = [ o for o in App.ActiveDocument.Objects
if o.getLinkedObject(True).isDerivedFrom('PartDesign::Body') and not o.Parents ]
# add a new body
body = App.ActiveDocument.addObject('PartDesign::Body', 'Body')
#set new placement
pl = FreeCAD.Placement()
pl.move(FreeCAD.Vector(-34,0,29.3))
body.Placement = pl
if case==1:
# preselection of bodies and then create the binder
for obj in bodies:
Gui.Selection.addSelection(obj)
#create new binder
binder = body.newObject('PartDesign::SubShapeBinder','Binder')
if case==2:
#set support via script
#create new binder
binder = body.newObject('PartDesign::SubShapeBinder','Binder')
#set support
binder.setPropertyStatus('Support','-Immutable')
binder.Support = bodies
if case==3:
# select the binder and drop objects
#create new binder
binder = body.newObject('PartDesign::SubShapeBinder','Binder')
# select the binder
Gui.Selection.clearSelection()
Gui.Selection.addSelection(doc.Name,body.Name,binder.Name+'.')
#drop objects
for obj in bodies:
binder.ViewObject.dropObject(obj,None,'',[])
I see. Right now SubShapeBinder only support placement adjustment when binding to one parent object. You can bind to multiple sub-objects inside one group parent, e.g. put all those bodies in a App::Part. I'll see if I can lift that restriction.
I have implemented placement adjustment of multiple support. I have also enhanced SubShapeBinder to store the context, so that it can now auto update itself when its parent has moved (@ceremcem you probably would be interested in this too).
The first screencast here shows that the binder auto update itself when its parent body is moved.
The screencast shows a new feature. The Placement
of the binder is now user changeable to set an offset from the bound objects. This is only available for newly created binder. Existing binder's Placement
is still readonly for compatibility reason.
This screencast shows when you need to manually adjust context. The parent body of the binder is now put into a new group. This changes the context of the binder. It cannot update itself when the parent group is moved. To establish a new context, just double click the binder as before. You can do that using script with addSelection
and doubleClick
, but I think it is not really needed since it can now remember the context and auto update itself.
This screencast shows another way to change context by moving the bound object to a group. For this, you'll have to manually reset the binding. Just remember to hold CTRL
key before dropping the first object to reset instead of adding more bindings.
Thanks for your effort, i have just refactored my script and all is working fine! My goal is to implement a more matured design-in-context feature, based on subshapebinder, App::Link and mutiple document support. At the current state it shows very clear the real potential of App::Link.
@realthunder Auto-updating the subshape binder works very well! Thanks! (Wow, I'm a little bit late)
Any progress @dulouie ?
How can i add geometry and shapes to subshapebinder via script? I have the last feature of the body
and then create the binder
feature = body.OutList[0]
feature isbinder = body.newObject('PartDesign::SubShapeBinder',binder') binder.ViewObject.dropObject(feature)
RoportView show me:<ShapeBinder> ViewProviderShapeBinder.cpp(325): invalid selection
and the binder has a wrong placement, when i double click the binder he solve the placement.