Automatic update of a part when another part changes

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
MarkkuTM
Posts: 33
Joined: Sun Dec 31, 2017 6:57 pm

Re: Automatic update of a part when another part changes

Post by MarkkuTM »

I have tried additional experiments with Expressions ( but not yet with the 'App::PropertyLink' method).

I have got everything to work for me when I use my script (below) to create only one carcass with 2 doors. I can move the carcass anywhere and turn it around the z axis to any angle and the doors will follow, but they are still independent so that I can make them invisible while still seeing the carcass. Exactly what I want.

The problem arises when I use the macro a second time, or more times in the same document. I get new new carcasses as expected (Carcass001, Carcass002, . . .), and I also get new doors packaged in compounds (Compound 001, Compound002, ...). The problem is that all the doors take their size and orientation information only from the original Carcass. So all sets of doors are stacked on top of each other in front of Carcass, and they all change size or orientation only with changes made to Carcass, not Carcass001, or Carcass002 etc.

This is obviously an issue about what is referred to by the door Expression script lines for size and placement. It is always the same object in the Active Document regardless of how many new carcasses are added. Any ideas on how to make the doors refer to the correct carcass? (I tried a direct reference along the lines of leftDoorObj.width = carcassObj.width/2 which works to create doors with proper size and orientation, but which are not parametric and therefore do not change size and orientation with the carcass)

Here is my code.

Code: Select all

# This calls Carcass.py and DoorTest.py
# uses Expressions to set door size and orientation to that of carcass.

import Part,FreeCAD

# Define variables

Thickness = 0.75*25.4
Width = 20*25.4
Height = 30.25*25.4
Depth = 23.75*25.4
DoorThickness = 0.75*25.4

# Carcass Maker
import Carcass # contains Class CarcassShape, produces a solid called Carcass
carcassObj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","Carcass")
Carcass.CarcassShape(carcassObj,Thickness,Width,Height,Depth)
carcassObj.ViewObject.Proxy = 0 # this is mandatory unless we code the ViewProvider


# Left Door Maker
import DoorTest # contains Class TestDoorShape, produces a solid called TestDoor
leftDoorObj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","TestDoor")
# pass cabinet width and height to DoorTest.py
DoorTest.TestDoorShape(leftDoorObj,DoorThickness,Width,Height) 
leftDoorObj.setExpression("width", "Carcass.width*0.5") # comment in for 2 doors
# leftDoorObj.setExpression("width", "Carcass.width") #comment in for one door
leftDoorObj.setExpression("height", "Carcass.height")
leftDoorObj.ViewObject.Proxy = 0

# Right Door Maker / BLOCK COMMENT OUT FOR ONE DOOR
import DoorTest
rightDoorObj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython","TestDoor")
DoorTest.TestDoorShape(rightDoorObj,DoorThickness,Width,Height) # passes cabinet width and height
# to DoorTest.py
rightDoorObj.setExpression('width', 'Carcass.width*0.5') # comment out for one door
rightDoorObj.setExpression("height", "Carcass.height")
rightDoorObj.setExpression('Placement.Base.x', u'Carcass.width/2 ')
rightDoorObj.ViewObject.Proxy = 0

# Make 2 Doors into a Compound 

Doors = App.activeDocument().addObject("Part::Compound","Compound")
Doors.Links = (leftDoorObj,rightDoorObj)
App.ActiveDocument.recompute()

# Make Doors follow cabinet when it is moved. Works properly in first use of Macro.

# ***There is still a problem on additional uses of Macro: Applies first Carcass size 
# and position to Compound001, Compound002, etc which then all follow size and moves
# of Carcass instead of Carcass001, Carcass002, etc  ***

Doors.setExpression('Placement.Base.x', u'Carcass.Placement.Base.x')
Doors.setExpression('Placement.Base.y', u'Carcass.Placement.Base.y')
Doors.setExpression('Placement.Base.z', u'Carcass.Placement.Base.z')
Doors.setExpression('Placement.Rotation.Angle', u'Carcass.Placement.Rotation.Angle')


I will make an effort to try the 'App::PropertyLink' method soon. Could you point out, or annotate, in your example code what is an example of saving a reference to a scripted object, and assigning an object to it. I can't tell by looking at bare code. No amount of doing searches of documentation or forum entries has yielded anything of value. Your explanation of what it is supposed to do, however, makes complete sense to me.
Joel_graff wrote: Sun Mar 17, 2019 2:59 am 1. Add a property to the furniture parts that need to be updated when the carcass changes. The property type needs to be 'App::PropertyLink'
2. After the scripted object is created, save a reference to it and assign the carcass object to the PropertyLink property

That's it.

Whenever the carcass gets recomputed, the execute() method of any furntire part scripted objects that are linked to it will be called.

A sample of code to help get you started:
User avatar
Joel_graff
Veteran
Posts: 1949
Joined: Fri Apr 28, 2017 4:23 pm
Contact:

Re: Automatic update of a part when another part changes

Post by Joel_graff »

MarkkuTM wrote: Wed Mar 20, 2019 3:42 am I will make an effort to try the 'App::PropertyLink' method soon. Could you point out, or annotate, in your example code what is an example of saving a reference to a scripted object, and assigning an object to it.
Not sure quite what you mean by 'saving a reference to a scripted object'. I've annotated what I think you might be referring to below.

Code: Select all


#call the create function, passing a reference to the carcass object
my_furntiure_object = create(my_carcass_object.Object)

def create(carcass_object):
    '''
    Class construction method
    '''
     #Creates a FeaturePython Object which is added directly to the FreeCAD document
     _obj = App.ActiveDocument.addObject('Part::FeaturePython', 'my_part')
     
     #Applies or attaches the custom class to that object 
    _fpo = _FurniturePart(_obj)

    #Creates the view provider for the 3D visualization
    _FurniturePartViewProvider(_obj.ViewObject)

    #Assigns the passed reference to the carcass object
    obj.CarcassLink = carcass_object

    App.ActiveDocument.recompute()
    
    return 

class _FurniturePart():

    def __init__(self, obj):
        '''
        Default Constructor
        '''
        #attribute to prevent execution during object construction
        self.no_execute = True

	#'Proxy' allows access to the custom class's properties outside the class itself (like self.Type)
        obj.Proxy = self
        
        #String description of the custom object class
        self.Type = 'FurniturePart'
        
        #Reference to the App::FeaturePython object that was added to the Document 
        #This is necessary for accessing serializable properties
        self.Object = obj

        #add serializable class properties directly to the actual FeaturePython Object
        obj.addProperty('App::PropertyLink', 'CarcassLink', 'Link to Carcass Object').CarcassLink = None

        #done initializing, remove no_execute block
        delattr(self, 'no_execute')

    def __getstate__(self):
        return self.Type

    def __setstate__(self, state):
        if state:
            self.Type = state

    def onDocumentRestored(self, fp):
        '''
        Restore object references on reload
        '''
	#Required to ensure access to serializable properties after document loading / object copying
       self.Object = fp
       
   def execute(self, fp):
       '''
       Execute method
       '''
       
       if hasattr(self, 'no_execute'):
           return
           
class _FurniturePartViewProvider():

    [VIEWPROVIDER CODE HERE]
FreeCAD Trails workbench for transportation engineering: https://www.github.com/joelgraff/freecad.trails

pivy_trackers 2D coin3D library: https://www.github.com/joelgraff/pivy_trackers
MarkkuTM
Posts: 33
Joined: Sun Dec 31, 2017 6:57 pm

Re: Automatic update of a part when another part changes

Post by MarkkuTM »

Re: saving a reference to a scripted object: just feeding back your explanation
Joel_graff wrote: Wed Mar 20, 2019 11:44 am 1. Add a property to the furniture parts that need to be updated when the carcass changes. The property type needs to be 'App::PropertyLink'
2. After the scripted object is created, save a reference to it and assign the carcass object to the PropertyLink property
User avatar
Joel_graff
Veteran
Posts: 1949
Joined: Fri Apr 28, 2017 4:23 pm
Contact:

Re: Automatic update of a part when another part changes

Post by Joel_graff »

MarkkuTM wrote: Wed Mar 20, 2019 2:54 pm Re: saving a reference to a scripted object: just feeding back your explanation
Oh. Basically that's what the create() function does. Once the object and FPO are created and you've assigned the PropertyLink property, you don't need to retain references.
FreeCAD Trails workbench for transportation engineering: https://www.github.com/joelgraff/freecad.trails

pivy_trackers 2D coin3D library: https://www.github.com/joelgraff/pivy_trackers
Post Reply