Store edges or faces as properties for scripted objects.

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
josegegas
Posts: 241
Joined: Sat Feb 11, 2017 12:54 am
Location: New Zealand

Store edges or faces as properties for scripted objects.

Post by josegegas »

Hi.

I have the user to select certain objects, then I get the selected objects as follows:

Code: Select all

obj = FreeCADGui.Selection.getSelectionEx()[0].SubObjects[0]
so that obj can be an edge or a face.

My question is, is it possible to store obj as a property of a scripted object?
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: Store edges or faces as properties for scripted objects.

Post by openBrain »

Sure. This is roughly how is achieved a parametric workflow.
josegegas
Posts: 241
Joined: Sat Feb 11, 2017 12:54 am
Location: New Zealand

Re: Store edges or faces as properties for scripted objects.

Post by josegegas »

Can you please provide an example of a scripted object storing obj? Would be much appreciated... :D
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Store edges or faces as properties for scripted objects.

Post by TheMarkster »

Did you try using Part::PropertyPartShape property types?
josegegas
Posts: 241
Joined: Sat Feb 11, 2017 12:54 am
Location: New Zealand

Re: Store edges or faces as properties for scripted objects.

Post by josegegas »

TheMarkster wrote: Tue Nov 30, 2021 4:34 pm Did you try using Part::PropertyPartShape property types?
Yes I did. The thing is that in this case the selected objects are faces or edges, and these do not have a shape property...
josegegas
Posts: 241
Joined: Sat Feb 11, 2017 12:54 am
Location: New Zealand

Re: Store edges or faces as properties for scripted objects.

Post by josegegas »

I´m trying to do something like the fillet tool in the part design workbench, in which the user selects a set of edges or faces before applying the fillet, and the list of faces or edges is stored as part of the fillet and can be later changed, to remove/include edges or faces.
Attachments
select edges.png
select edges.png (82.82 KiB) Viewed 2110 times
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Store edges or faces as properties for scripted objects.

Post by TheMarkster »

Those are shapes, but I see now you don't want to store the actual shape. You could use a property of type App::PropertyLinkSubList. Here, LSL is the property and an example of how to set it. It takes a list of tuples:

Code: Select all

[(obj,subobjectname),(obj2,subobjectname)]

LSL = [(FreeCAD.getDocument('fingerjoint_test').getObject('Sketch003'),u'Edge1'),(FreeCAD.getDocument('fingerjoint_test').getObject('Sketch003'),u'Edge2'),(FreeCAD.getDocument('fingerjoint_test').getObject('Body003'),u'Sketch003.Wire1'),]
josegegas
Posts: 241
Joined: Sat Feb 11, 2017 12:54 am
Location: New Zealand

Re: Store edges or faces as properties for scripted objects.

Post by josegegas »

Hi again.

I´m having some trouble with this. Let me explain what I want to achieve. I am working on the "dynamics" workbench, which aims at providing MBD (Multi Body Dynamics) capabilities to FreeCAD, using MBDyn as solver. So far the project goes well, and the user can pretty much model any mechanism using rigid bodies. For this, I am implementing the joints MBDyn has to offer as Scripted objects for FreeCAD. This is the class to create objects for a "revolute pin" joint.

Code: Select all

import FreeCAD,FreeCADGui
import Draft
from sympy import Point3D, Line3D

class Revolutepin:
    def __init__(self, obj, label, node, reference1, reference2):       
             
        #Get the center of mass or the center of the two references
        try:
            x2 = FreeCAD.Units.Quantity(reference1.Curve.Center[0],FreeCAD.Units.Unit('mm'))  
            y2 = FreeCAD.Units.Quantity(reference1.Curve.Center[1],FreeCAD.Units.Unit('mm'))  
            z2 = FreeCAD.Units.Quantity(reference1.Curve.Center[2],FreeCAD.Units.Unit('mm'))  
        except:    
            x2 = FreeCAD.Units.Quantity(reference1.CenterOfMass[0],FreeCAD.Units.Unit('mm'))  
            y2 = FreeCAD.Units.Quantity(reference1.CenterOfMass[1],FreeCAD.Units.Unit('mm'))  
            z2 = FreeCAD.Units.Quantity(reference1.CenterOfMass[2],FreeCAD.Units.Unit('mm')) 
       
        try:
            x3 = FreeCAD.Units.Quantity(reference2.Curve.Center[0],FreeCAD.Units.Unit('mm'))  
            y3 = FreeCAD.Units.Quantity(reference2.Curve.Center[1],FreeCAD.Units.Unit('mm'))  
            z3 = FreeCAD.Units.Quantity(reference2.Curve.Center[2],FreeCAD.Units.Unit('mm')) 
        except:
            x3 = FreeCAD.Units.Quantity(reference2.CenterOfMass[0],FreeCAD.Units.Unit('mm'))  
            y3 = FreeCAD.Units.Quantity(reference2.CenterOfMass[1],FreeCAD.Units.Unit('mm'))  
            z3 = FreeCAD.Units.Quantity(reference2.CenterOfMass[2],FreeCAD.Units.Unit('mm')) 
        

        #Calculate the joint´s absolute position:
        x = (x2+x3)/2
        y = (y2+y3)/2
        z = (z2+z3)/2
            
        #Calculate the joint possition relative to it's node (relative offset):        
        x1 = x-node.absolute_position_X
        y1 = y-node.absolute_position_Y
        z1 = z-node.absolute_position_Z
                
        obj.addExtension("App::GroupExtensionPython") 
        
        #Create scripted object:
        obj.addProperty("App::PropertyString","label","Revolute pin","label",1).label = label
        obj.addProperty("App::PropertyString","node_label","Revolute pin","node_label",1).node_label = node.label
        obj.addProperty("App::PropertyString","joint","Revolute pin","joint",1).joint = 'revolute pin'
        obj.addProperty("App::PropertyString","plugin_variables","Revolute pin","plugin_variables",1).plugin_variables = "none"
        
        #pin possition relative to it's node:
        obj.addProperty("App::PropertyDistance","relative_offset_X","Relative offset","relative_offset_X",1).relative_offset_X = x1
        obj.addProperty("App::PropertyDistance","relative_offset_Y","Relative offset","relative_offset_Y",1).relative_offset_Y = y1
        obj.addProperty("App::PropertyDistance","relative_offset_Z","Relative offset","relative_offset_Z",1).relative_offset_Z = z1
        
        #Absolute pin position:                
        obj.addProperty("App::PropertyDistance","absolute_pin_position_X","Absolute pin position","absolute_pin_position_X",1).absolute_pin_position_X = x
        obj.addProperty("App::PropertyDistance","absolute_pin_position_Y","Absolute pin position","absolute_pin_position_Y",1).absolute_pin_position_Y = y
        obj.addProperty("App::PropertyDistance","absolute_pin_position_Z","Absolute pin position","absolute_pin_position_Z",1).absolute_pin_position_Z = z                        
        
        #Animation parameters:
        obj.addProperty("App::PropertyEnumeration","animate","Animation","animate")
        obj.animate=['false','true']

        obj.addProperty("App::PropertyEnumeration","frame","Animation","frame")
        obj.frame=['global','local']        
        
        obj.addProperty("App::PropertyFloat","force vector multiplier","Animation","force vector multiplier").force_vector_multiplier = 1.0
        
        obj.Proxy = self
        
        #Add joint´s rotation axis. This axis determines the "absolute_pin_orientation_matrix":
        p1 = FreeCAD.Vector(x2, y2, z2)
        p2 = FreeCAD.Vector(x3, y3, z3)
        #Create the rotation axis:
        l = Draft.makeLine(p1, p2)
        l.Label = 'z: joint: '+ label          
        l.ViewObject.LineColor = (0.00,0.00,1.00)
        l.ViewObject.PointColor = (0.00,0.00,1.00)
        l.ViewObject.DrawStyle = u"Dashed"   
        l.ViewObject.LineWidth = 1.00
        l.ViewObject.PointSize = 1.00
        
             
        #Add the vector to visualize reaction forces
        Llength = FreeCAD.Units.Quantity(FreeCAD.ActiveDocument.getObjectsByLabel("X")[0].End[0]/4,FreeCAD.Units.Unit('mm'))
        p1 = FreeCAD.Vector(x, y, z)
        p2 = FreeCAD.Vector(x+Llength, y+Llength, z+Llength)    
        d = Draft.makeLine(p1, p2)
        d.ViewObject.LineColor = (1.00,0.00,0.00)
        d.ViewObject.PointColor = (1.00,0.00,0.00)  
        d.ViewObject.LineWidth = 1.00
        d.ViewObject.PointSize = 1.00
        d.ViewObject.EndArrow = True
        d.ViewObject.ArrowType = u"Arrow"
        d.ViewObject.ArrowSize = str(Llength/75)#+' mm'
        d.Label = "jf: "+ label         

        #absolute orientation:            
        obj.addProperty("App::PropertyString","absolute_pin_orientation_matrix","Orientation","absolute_pin_orientation_matrix",1).absolute_pin_orientation_matrix = ""#"3, " + str(l1.direction[0]) + ", "+ str(l1.direction[1]) + ", " + str(l1.direction[2]) + ", " +"2, guess"                     
        
        #relative orientation:   
        obj.addProperty("App::PropertyString","relative_orientation_matrix","Orientation","relative_orientation_matrix",1).relative_orientation_matrix = ""#"3, " + str(l1.direction[0]) +", "+ str(l1.direction[1]) + ", " + str(l1.direction[2]) + ", " +"2, guess"                     


    def execute(self, fp):
        #precission = int(FreeCAD.ActiveDocument.getObjectsByLabel('MBDyn')[0].precision)#Max number of decimal places        
        ##############################################################################Calculate the new absolute orientation:            
        ZZ = FreeCAD.ActiveDocument.getObjectsByLabel("z: joint: "+fp.label)[0]#get the joint´s rotation axis
        
        #Two 3D points that define the joint´s line:
        p1, p2 = Point3D(ZZ.Start[0], ZZ.Start[1], ZZ.Start[2]), Point3D(ZZ.End[0], ZZ.End[1], ZZ.End[2]) 
        l1 = Line3D(p1, p2)#Line that defines the joint
        
        magnitude = (l1.direction[0]**2+l1.direction[1]**2+l1.direction[2]**2)**0.5#Calculate the vector´s magnitude
        
        #generate the orientation matrix:
        fp.absolute_pin_orientation_matrix = "3, "+ str(l1.direction[0]/magnitude) +", "+ str(l1.direction[1]/magnitude) + ", " + str(l1.direction[2]/magnitude) + ", " +"2, guess"                
                        
        ##############################################################################Recalculate the offset, in case the node was moved: 
        #get and update the absolute pin position:
        x = FreeCAD.Units.Quantity((ZZ.Start[0] + ZZ.End[0])/2,FreeCAD.Units.Unit('mm')) 
        y = FreeCAD.Units.Quantity((ZZ.Start[1] + ZZ.End[1])/2,FreeCAD.Units.Unit('mm')) 
        z = FreeCAD.Units.Quantity((ZZ.Start[2] + ZZ.End[2])/2,FreeCAD.Units.Unit('mm')) 
        
        fp.absolute_pin_position_X = x
        fp.absolute_pin_position_Y = y
        fp.absolute_pin_position_Z = z
        
        #get the node:
        node = FreeCAD.ActiveDocument.getObjectsByLabel("structural: "+fp.node_label)[0]
        
        #Re-calculate the joint possition relative to it's node (relative offset)        
        x1 = x - node.absolute_position_X
        y1 = y - node.absolute_position_Y
        z1 = z - node.absolute_position_Z

        #Update the offset:
        fp.relative_offset_X = x1
        fp.relative_offset_Y = y1
        fp.relative_offset_Z = z1           
        
        ##############################################################################Calculate the new relative orientation:            
        zz = FreeCAD.ActiveDocument.getObjectsByLabel("z: joint: "+fp.label)[0]
        p11, p22 = Point3D(zz.Start[0], zz.Start[1], zz.Start[2]), Point3D(zz.End[0], zz.End[1], zz.End[2]) 
        l11 = Line3D(p11, p22)
        zzmagnitude1 = (l11.direction[0]**2+l11.direction[1]**2+l11.direction[2]**2)**0.5
                
        #generate the relative orientation matrix:
        #fp.relative_orientation_matrix = "3, "+ str(round(l1.direction[0]/zzmagnitude,precission)) +", "+ str(round(l1.direction[1]/zzmagnitude,precission)) + ", " + str(round(l1.direction[2]/zzmagnitude,precission)) + ", " +"2, "+ str(round(l2.direction[0]/yymagnitude,precission)) +", "+ str(round(l2.direction[1]/yymagnitude,precission)) + ", " + str(round(l2.direction[2]/yymagnitude,precission))                
        fp.relative_orientation_matrix = "3, "+ str(l11.direction[0]/zzmagnitude1) +", "+ str(l11.direction[1]/zzmagnitude1) + ", " + str(l11.direction[2]/zzmagnitude1) + ", " +"2, guess"#+ str(round(l2.direction[0]/yymagnitude,precission)) +", "+ str(round(l2.direction[1]/yymagnitude,precission)) + ", " + str(round(l2.direction[2]/yymagnitude,precission))                
        
        FreeCAD.Console.PrintMessage("REVOLUTE PIN JOINT: " +fp.label+" successful recomputation...\n")
The main idea is quite simple. The constructor gets two references (reference1 and reference2), which are shapes the user selects in the GUI. The line passing through the center of mass of these two references determines the joint´s position and orientation. The position of the joint is the center point of this line, and the orientation of the joint is the same as the orientation of the line. (This is how other programs, such as Adams and Ansys motion, define joints too...)

So far it works perfectly, but the problem is that it i not parametric, so that if the CAD model is changed, the joint does not change accordingly, and the user is forced to delete and create the joint again. I am working now on making the joints parametric, and so I need to store, as a property for the scripted object, the two shapes provided as references. I have tried:

Code: Select all

        obj.addProperty("Part::PropertyPartShape","reference_1","References","reference_1").reference_1 = reference1
        obj.addProperty("Part::PropertyPartShape","reference_2","References","reference_2").reference_2 = reference2


But the thing is that this seems to store a copy of the reference shape, and not a link to the reference. So that this does not help me to make the joint parametric.

I have tried using App::PropertyLinkSubList, as TheMarkester suggested, but I do not quite understand how this works and I have not been able to find any complete example either...

Any ideas will be very welcome...
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: Store edges or faces as properties for scripted objects.

Post by TheMarkster »

If you know the edges will all be from the same object, then you could use App::PropertyLinkSub.

Code: Select all

class FP:
    def __init__(self,obj):
        obj.addProperty("App::PropertyLinkSub","Edges")
        obj.Proxy = self

    def execute(self,fp):
        if not fp.Edges:
            return
        object = fp.Edges[0]
        edges = fp.Edges[1]
        FreeCAD.Console.PrintMessage(f"Object name: {object.Name}, edges: {edges}\n")
        subs = [object.getSubObject(sub) for sub in edges]
        comp = FreeCAD.ActiveDocument.getObject("Shape")
        comp.Shape = Part.makeCompound(subs)
        


if not FreeCAD.ActiveDocument:
    FreeCAD.newDocument()
fp = FreeCAD.ActiveDocument.addObject("App::FeaturePython","FP")
FP(fp)
box = FreeCAD.ActiveDocument.addObject("Part::Box","box")
box.ViewObject.Visibility = False
fp.Edges = (box,["Edge1","Edge2","Face2"])
comp = FreeCAD.ActiveDocument.addObject("Part::Feature","Shape")
FreeCAD.ActiveDocument.recompute()
josegegas
Posts: 241
Joined: Sat Feb 11, 2017 12:54 am
Location: New Zealand

Re: Store edges or faces as properties for scripted objects.

Post by josegegas »

Thanks.

Now, how can I recover the objects?

Say I have a simple class:

Code: Select all

class FP:
    def __init__(self, obj):
        obj.addProperty("App::PropertyLinkSub","reference_1","References","reference_1")
        obj.addProperty("App::PropertyLinkSub","reference_2","References","reference_2")        
        obj.Proxy = self

and create the object:

Code: Select all

fp = FreeCAD.ActiveDocument.addObject("App::FeaturePython","FP")
FP(fp)
Then, the user selects two edges or faces belonging to the same object, and I do:

Code: Select all

fp.reference_1 = (FreeCADGui.Selection.getSelection()[0],FreeCADGui.Selection.getSelectionEx()[0].SubElementNames[0])
fp.reference_2 = (FreeCADGui.Selection.getSelection()[0],FreeCADGui.Selection.getSelectionEx()[0].SubElementNames[1])
This works well so far, but now I want to recover the center of mass of reference_1 and of reference_2 (which can be edges or faces), and I'm not sure how to do so. Any ideas?
Post Reply