PySide - Detecting change in an object (a body)

Need help, or want to share a macro? Post here!
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
comlich
Posts: 13
Joined: Tue Aug 24, 2021 8:18 am

PySide - Detecting change in an object (a body)

Post by comlich »

Hi there,
I am trying to write a macro that analyses a body object while it is being edited by an user. I assume everything takes place in the PartDesign workbench, where a body has been created to model an object. An initial geometry is created (e.g. by extruding a sketch) then others features are added (by the user) sequentially until the design is complete. This is basically the Feature Editing Methodology (https://wiki.freecadweb.org/Feature_editing) for modeling a part. Throughout the modeling, the body changes as a result of the features used by the designer (see image below)
Body.png
Body.png (11.37 KiB) Viewed 2339 times
What I am trying to do is a macro that triggers some instructions whenever a new feature is added, in other words whenever the body changes (e.g. compute the volume of the body and possibly raise a warning if it is larger than a certain threshold). My question is: how to track this change in the body object?
The macro takes the form of a window as shown below
Analysis.PNG
Analysis.PNG (62.68 KiB) Viewed 2339 times


Here is the code for that macro:

Code: Select all

import FreeCAD
from PySide import QtGui, QtCore

class Analysis(QtGui.QWidget):
    
    def __init__(self):
        super(Analysis, self).__init__()
        
        self.initUI()
    
    def initUI(self):
        self.maxVolumeLbl = QtGui.QLabel("Threshold:",self)
        self.maxVolumeLbl.move(30,20)
        
        self.MaxVolume = 10000
        qle = QtGui.QLineEdit(self)
        qle.move(100, 15)
        qle.textChanged[str].connect(self.onChanged)
        
        self.BodyVolumelbl1 = QtGui.QLabel("Body volume:",self)
        self.BodyVolumelbl1.move(30,50)
        self.BodyVolumelbl2 = QtGui.QLabel("                            ", self)
        self.BodyVolumelbl2.move(150, 50)
        
        pushButtonAnalysis = QtGui.QPushButton("   Launch Analysis!   ",self)
        pushButtonAnalysis.clicked.connect(self.onAnalysis)
        pushButtonAnalysis.move(40, 85)
        
        self.setGeometry(300, 300, 300, 120)
        self.setWindowTitle('Body Volume Analysis')
        self.show()
    
    def onChanged(self,value):
        self.MaxVolume = float(value)
        
    def onAnalysis(self):
        v = FreeCAD.ActiveDocument.Body.Shape.Volume
        self.BodyVolumelbl2.setText("{:.2f}".format(v))
        if v>self.MaxVolume:
            warn = QtGui.QMessageBox(QtGui.QMessageBox.Warning,'Volume issue!','Volume is higher than maximum volume')
            warn.exec_()
        
test = Analysis()
I assume the body's name is (always) 'Body', so I get the volume with "FreeCAD.ActiveDocument.Body.Shape.Volume". So far, I can have the user run the analysis only on-demand by pressing a button, as shown in the image above, but I would like the analysis to run whenever FreeCAD.ActiveDocument.Body (or FreeCAD.ActiveDocument.Body.Volume) changes.

Any help would be greatly appreciated.

Thanks in advance.
TheMarkster
Veteran
Posts: 5505
Joined: Thu Apr 05, 2018 1:53 am

Re: PySide - Detecting change in an object (a body)

Post by TheMarkster »

Another approach idea:

Code: Select all

class WeightWatcher:
    def __init__(self,obj):
        obj.addProperty("App::PropertyFloat","MaxVolume").MaxVolume = 1000
        obj.addProperty("App::PropertyFloat","CurrentVolume").CurrentVolume = 0
        obj.addProperty("App::PropertyLink","Body")
        obj.Proxy = self

    def execute(self,fp):
        fp.CurrentVolume = fp.Body.Shape.Volume
        if fp.Body.Shape.Volume > fp.MaxVolume:
            FreeCAD.Console.PrintWarning("Max Volume has been exceeded\n")

#assumes there is a document and that there is a body named "Body" in it
ww = FreeCAD.ActiveDocument.addObject("App::FeaturePython", "WeightWatcher")
WeightWatcher(ww)

ww.Body  = App.ActiveDocument.getObject("Body")
Note: the object does not retain its parametric nature after saving and reloading the file or restarting FreeCAD. For that you need to place the class definition in another file with .py extension and import it from another file that contains the initialization code. The Link set to the Body makes the FP object dependent on the Body and thus gets recomputed when the Body changes.
comlich
Posts: 13
Joined: Tue Aug 24, 2021 8:18 am

Re: PySide - Detecting change in an object (a body)

Post by comlich »

Thank you very much to you both for your help.

@TheMarkster
I just saw your reply, so I haven't experimented your solution yet. Though many thanks.

@openBrain
I have been trying to understand/tweaking your solution for the past few days. Especially I tried the example you provided here: https://github.com/0penBrain/FreeCAD-ma ... og.FCMacro. Everything runs fine, expect when I try to add a new slot to the MyDocObs class. I first tried to compute the volume in the slotCreatedObject slot, but the volume get computed before the new object is added. Then I noticed in the header you sent (https://github.com/FreeCAD/FreeCAD/blob ... h#L55-L113), the slotRecomputedDocument slot whose description reads: "Called when an observed document is recomputed". Whenever a new object is added, the document get recomputed (I can see it in the Python console: e.g. "App.getDocument('testMacro').recompute()"). So I tried the slotRecomputedDocument slot by adding it to your example as follows:

Code: Select all

def slotRecomputedDocument(self, doc):
	self.callb(f'Document recomputed : {doc.Document.Name}')
You can see an excerpt in the image at the bottom. The problem is that nothing happens whenever I add a new object despite the document being recomputed.
Any idea on where I am wrong?
Thank you in advance.
Attachments
addedSlot.PNG
addedSlot.PNG (35 KiB) Viewed 2053 times
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: PySide - Detecting change in an object (a body)

Post by openBrain »

comlich wrote: Mon Sep 27, 2021 4:24 pm You can see an excerpt in the image at the bottom. The problem is that nothing happens whenever I add a new object despite the document being recomputed.
Any idea on where I am wrong?
Thank you in advance.
Do you see the 'Document recomputed' line you added to be displayed in the 'Document Observer' log window?
Basically my example does nothing more than printing events in the boxes of the dialog. :)
I'm with a mobile now but can find a try later.
comlich
Posts: 13
Joined: Tue Aug 24, 2021 8:18 am

Re: PySide - Detecting change in an object (a body)

Post by comlich »

No I don't see that line as expected in the 'Document Observer' log window, upon 'recomputation' of the document. The lines sent by the other slots you added show up perfectly every time the corresponding events occur.
I understood your example simply prints the events and, before proceeding further with my project, I would like to do the same with the RecomputedDocument event.

Thanks in advance.
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: PySide - Detecting change in an object (a body)

Post by openBrain »

comlich wrote: Mon Sep 27, 2021 11:22 pm No I don't see that line as expected in the 'Document Observer' log window, upon 'recomputation' of the document. The lines sent by the other slots you added show up perfectly every time the corresponding events occur.
I understood your example simply prints the events and, before proceeding further with my project, I would like to do the same with the RecomputedDocument event.

Thanks in advance.
Simpler with a computer. :) Actually if you carefully read the posts I pointed, you'll see that API is different depending if you set a FreeCAD (App) doc observer or a FreeCADGui (Gui) doc observer.
What you pointed above is the API for App but my example set a Gui doc observer.
The API in Gui is very limited https://github.com/FreeCAD/FreeCAD/blob ... .h#L53-L74
Especially the 'slotRecomputedDocument' doesn't exist. So it will never be called. :)

Considering App API is far more complete I'll update my example dialog to use it and so it should work fine for you too. Let me one hour :)
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: PySide - Detecting change in an object (a body)

Post by openBrain »

Updated. Please download the latest version of dialog example and add again your function, it should work fine : https://github.com/0penBrain/FreeCAD-ma ... og.FCMacro
comlich
Posts: 13
Joined: Tue Aug 24, 2021 8:18 am

Re: PySide - Detecting change in an object (a body)

Post by comlich »

Hi!
I understand now that I wasn't looking at the right documentObserver class. I updated the way you mentioned in your example and indeed everything worked fine now. Thank you very much for your help and for your explanations. My problem is solved.

Please, I have one little question though, it is out of curiosity but it is likely to help me in a near future: is there a way to detect which feature's button is pressed by the user? In the PartDesign workbench for instance, the user can add Pad, Hole, Revolute... features by pressing the corresponding buttons in the toolbar. My question is that, are there slots for these specific events?

Once again thank you very much.
openBrain
Veteran
Posts: 9034
Joined: Fri Nov 09, 2018 5:38 pm
Contact:

Re: PySide - Detecting change in an object (a body)

Post by openBrain »

comlich wrote: Tue Sep 28, 2021 1:30 pm Please, I have one little question though, it is out of curiosity but it is likely to help me in a near future: is there a way to detect which feature's button is pressed by the user? In the PartDesign workbench for instance, the user can add Pad, Hole, Revolute... features by pressing the corresponding buttons in the toolbar. My question is that, are there slots for these specific events?
AFAIK there is no native or/and reliable solution to do this with Python API.
You could envisage fancy tricky things, like parsing the Python console, or adding signal/slot connections to every single toolbar/menu item, but it would all look dirty IMO. :)
Post Reply