Another approach to assembly solver (A2plus)

Hello everyone, I got some time to write fex lines on this topic. Please find the code below. I still have some issue with the transparency and color recovery after surface modification. The principle is the following:

  • you select a link created by a2plus
  • the run the macro
  • it should hide every object except the 2 ones concerned by the link, make half transparent the parts and highlignt the selected surfaces which are linked.
  • a window should pop-up and let you choose another surface. There is a warning if you try to work on the wrong part.
  • if you close the window, it does not save the change, otherwise there is a button to close and exit.

if you have any feedback feel free to share.

Have a good weekend,
Hubert

# -*- coding: utf-8 -*-

# Macro Begin
+++++++++++++++++++++++++++++++++++++++++++++++++
import FreeCAD as App
import FreeCADGui as Gui
import PySide
from PySide import QtCore, QtGui
+++++++++++++++++++++++++++++++++++++++++++++++++

##########################################
# Global function
##########################################

try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

##########################################
# Gui definition
##########################################

class GuiSelectSubElemWindows(PySide.QtGui.QWidget):
    # automagically called when the window is created
    closed = QtCore.Signal()
    def __init__(self,app=None):
        super(GuiSelectSubElemWindows, self).__init__()
        self.setWindowFlags(PySide.QtCore.Qt.WindowStaysOnTopHint)
        self.setWindowTitle(_translate(
            "MainWindow", "SelectionWindows", None))
        self.app = app

        # UI
        self.setGeometry(14,200, 40, 50)
        grid = PySide.QtGui.QGridLayout()
        self.setLayout(grid)
        self.setLayout(grid)   
        self.setObjectName(_fromUtf8("SelectionWindows"))

        # Labels
        self.label_0 = QtGui.QLabel(self)
        self.label_0.setText("Constraint name:")
        self.UpdateConstraintName()
        self.label_0.setToolTip("Current constraint evaluated")
        self.label_0.adjustSize()
    
        self.label_1 = QtGui.QLabel(self)
        self.label_1.setText("Elem 1:")
        self.UpdateSubElem1Name()
        self.label_1.setToolTip("First Element of the constraint")
        self.label_1.adjustSize()
       
        self.label_2 = QtGui.QLabel(self)
        self.label_2.setText("Elem 2:")
        self.UpdateSubElem2Name()
        self.label_2.setToolTip("Second Element of the constraint")
        self.label_2.adjustSize()

        self.label_3 = QtGui.QLabel(self)
        self.label_3.setText("Status: ")
        self.label_3.adjustSize()
            
        # Update on request
        self.PushButton_1 = QtGui.QPushButton(self)
        self.PushButton_1.Label = 'Change_SubElem2'
        self.PushButton_1.setText("Change E1")
        self.PushButton_1.clicked.connect(self.UpdateSubElem1)
        self.PushButton_1.setToolTip("Validate First Sub Element")
        self.PushButton_1.adjustSize()

        self.PushButton_2 = QtGui.QPushButton(self)
        self.PushButton_2.Label = 'Change_SubElem2'
        self.PushButton_2.setText("Change E2")
        self.PushButton_2.clicked.connect(self.UpdateSubElem2)
        self.PushButton_2.setToolTip("Validate Second Sub Element")
        self.PushButton_2.adjustSize()


        self.PushButton_3 = QtGui.QPushButton(self)
        self.PushButton_3.Label = 'Save_and_Close'
        self.PushButton_3.setText("Save changes and Close")
        self.PushButton_3.clicked.connect(self.SaveModificationAndClose)
        self.PushButton_3.setToolTip("Save modifications and close macro")
        self.PushButton_3.adjustSize()

        grid.addWidget(self.label_0,0,0,1,2)
        grid.addWidget(self.label_1,1,0,1,1)        
        grid.addWidget(self.label_2,2,0,1,1)
        grid.addWidget(self.PushButton_1,1,1,1,1)
        grid.addWidget(self.PushButton_2,2,1,1,1)
        grid.addWidget(self.label_3,3,0,1,2)
        grid.addWidget(self.PushButton_3,4,0,1,2)

        self.show()

    def UpdateConstraintName(self):
        self.label_0.setText("Constraint name: %s" % self.app.a2pConstraintName)

    def UpdateSubElem1(self):
        self.app.UpdateSubElem1() # ??????
        self.UpdateSubElem1Name()
        self.label_3.setText('Status: %s' % self.app.status)

    def UpdateSubElem1Name(self):
        self.label_1.setText("Sub-Element 1: %s" % self.app.a2pSubElem1)

    def UpdateSubElem2(self):
        self.app.UpdateSubElem2() # ?????
        self.UpdateSubElem2Name()
        self.label_3.setText('Status: %s' % self.app.status)
        
    def refreshScreen(self):
        Gui.Selection.clearSelection()
        for obj in App.ActiveDocument.Objects:
            if obj.ViewObject.isVisible():
                Gui.Selection.addSelection(obj)
        Gui.Selection.clearSelection()

    def UpdateSubElem2Name(self):
        self.label_2.setText("Sub-Element 2: %s" % self.app.a2pSubElem2)

    def SaveModificationAndClose(self):
        self.app.Selection.SubElement1 = self.app.a2pSubElem1
        self.app.Selection.SubElement2 = self.app.a2pSubElem2
        self.close()

    def closeEvent(self, event):
        self.app.resetVisibility()
        self.refreshScreen()
        self.closed.emit()

##########################################
# Model definition
##########################################

class getRelatedConstraintObject():
    def __init__(self,Selection=None):
        self.ObjInView = []
        self.OriginalVisibilities = []
        self.OriginalTransparencies = []
        self.OriginalSubElemColors = [None,None]
        self.ProListCheck = ['Object1', 'Object2', 'Proxy', 'SubElement1', 'SubElement2', 'Suppressed', 'Toponame1', 'Toponame2', 'Type', 'Visibility']
        self.a2pObject1 = None
        self.a2pObject2= None
        self.a2pSubElem1 = None
        self.a2pSubElem2 = None
        self.a2pConstraintName= None
        self.Selection = Selection
        self.isSelectionValid = False
        self.status = ''
        self.checkSelection()
        if self.isSelectionValid:
            self.getObjectConstraintDetails()
            self.getObjInView()
            self.getOriginalVisibilities()
   
    def checkSelection(self):
        if self.Selection.TypeId == 'App::FeaturePython' and  all([item in self.Selection.PropertiesList for item in self.ProListCheck]):
            self.isSelectionValid = True
  
    def getObjInView(self):
        self.ObjInView.clear()
        for obj in App.ActiveDocument.Objects:
            try:
                obj.getPropertyByName("Shape")
                self.ObjInView.append(obj)
            except AttributeError:
                pass

    def getOriginalVisibilities(self):
        self.OriginalVisibilities.clear()
        for obj in self.ObjInView:
            vis =obj.ViewObject.Visibility
            transp = obj.ViewObject.Transparency
            self.OriginalVisibilities.append(vis)
            self.OriginalTransparencies.append(transp)
            if vis:
                if obj.TypeId != str("Drawing::FeatureViewPython") and not self.isRelatedObject(obj):
                    obj.ViewObject.Visibility = False
                elif self.isRelatedObject(obj):
                    pass
        self.setRelatedFacesHighlighted()
        

    def setRelatedFacesHighlighted(self):
        # Sub Elem 1
        self.OriginalSubElemColors[0] = self.setColorOnFace(self.a2pObject1, self.Selection.SubElement1)
        # Sub Elem 2
        self.OriginalSubElemColors[1] = self.setColorOnFace(self.a2pObject2, self.Selection.SubElement2)
      
    def getObjectConstraintDetails(self):
        self.a2pObject1 = self.Selection.Object1
        self.a2pObject2 = self.Selection.Object2
        self.a2pSubElem1 = self.Selection.SubElement1
        self.a2pSubElem2 = self.Selection.SubElement2
        self.a2pConstraintName = self.Selection.Label

    def UpdateSubElem1(self):
        try:
            SelectedObj = Gui.Selection.getSelectionEx()[0]
            SelectedObjName = SelectedObj.ObjectName
            if SelectedObjName == self.a2pObject1:
                self.a2pSubElem1 = SelectedObj.SubElementNames[0]
                SelectedObj.Object.ViewObject.DiffuseColor = self.OriginalSubElemColors[0]
                _ = self.setColorOnFace(self.a2pObject1, self.a2pSubElem1)
                self.status = 'SubElem1 selected successfully'
            else:
                self.status = 'Wrong part selected'
        except:
            pass
        
    def UpdateSubElem2(self):
        try:
            SelectedObj = Gui.Selection.getSelectionEx()[0]
            SelectedObjName = SelectedObj.ObjectName
            if SelectedObjName == self.a2pObject2:
                self.a2pSubElem2 = SelectedObj.SubElementNames[0]
                SelectedObj.Object.ViewObject.DiffuseColor = self.OriginalSubElemColors[1]
                _ = self.setColorOnFace(self.a2pObject2, self.a2pSubElem2)
                self.status = 'SubElem2 selected successfully'
            else:
                self.status = 'Wrong part selected'
        except:
            pass
        
    def isRelatedObject(self,CADObj):
        return CADObj.Name == self.a2pObject1 or CADObj.Name == self.a2pObject2
    
    def resetVisibility(self):
        obj =App.activeDocument().getObject(self.a2pObject1)
        obj.ViewObject.DiffuseColor = self.OriginalSubElemColors[0]
        obj =App.activeDocument().getObject(self.a2pObject2)
        obj.ViewObject.DiffuseColor = self.OriginalSubElemColors[1]
        for obj,vis,transp in zip(self.ObjInView,self.OriginalVisibilities,self.OriginalTransparencies):
            obj.ViewObject.Visibility = vis
            obj.ViewObject.Transparency = 1 #  workaroud for the display bug with reseting transparency
            obj.ViewObject.Transparency =  transp
        
        
    def ChangeTransparency(self,colors,value):
        colors = [(s[0],s[1],s[2],value) for s in colors]
        return colors

    def setColorOnFace(self, MyObject, MyFace,MyColor=None):
        obj =App.activeDocument().getObject(MyObject)
        NFaces = len(obj.Shape.Faces)
        if len(obj.ViewObject.DiffuseColor) == 1:
           colorlist = obj.ViewObject.DiffuseColor*NFaces
        else:
            colorlist = obj.ViewObject.DiffuseColor
        colorlist = self.ChangeTransparency(colorlist,0.7)
        SubElemIndex = int(MyFace[4:])-1
        if MyColor is None:
            c1, c2, c3, _ = colorlist[SubElemIndex]
            MyColor = (float((1.-c1)), float((1.-c2)), float((1.-c3)), 0.)
            # MyColor = (1.,0.,1.,0.)
        colorlist[SubElemIndex] = MyColor # (0.,1.,0.,0.)
        output = obj.ViewObject.DiffuseColor
        obj.ViewObject.DiffuseColor = colorlist
        return output
     
for selection in Gui.Selection.getSelection():
    app = getRelatedConstraintObject(selection) # first in selection, no test)
    if app.isSelectionValid:
        view = GuiSelectSubElemWindows(app)
        loop = QtCore.QEventLoop()
        view.closed.connect(loop.quit)
        loop.exec_()

Hi HubertTO. It sounds like you want to view a constraint and then move the attachment surface to another surface if it is not the correct one. I wrote this workbench a while back but haven’t used FreeCAD since I finished building my house. Most of the workbench is out dated but the constraint viewer still seems to work. You can select a part and the constraints show in the dialog box. You can also select the constraints from the tree. You can then change the attachment surface.
I have attached some directions and an assembly that breaks the constraints when the assembly is updated.
Dan.
A2plusmore.zip (872 KB)
Sample for updated part.zip (142 KB)
Constraint Viewer instructions.pdf (64.9 KB)

I took a fast look at the part if this workbench that should find broken constraints. A2plus has been rewritten since I wrote this. The broken constraint finder is working on the one file I tried it on so I’m putting it up here without checking it very well. When the program is ran, it finds broken constraints in the a2plus assembly and creates a list of them. You can then open the constraint viewer to see what is broken and perhaps fix them.
I hope someone has success with one of these programs.
Dan
A2plusmore Ver12.zip (970 KB)
Assy with broken constraints.zip (8.71 KB)
Directions for finding broken constraints.pdf (48.8 KB)

I see that this workbench. “A2plusmore”, from the previous message has been downloaded over 50 times. Has anyone had success using it or have a suggestion to make it more useful?
Dan

Hi,

when clicking onto edit a part imported by a2plus, it will open the part separately.
So far so good.

My object is a little bit bigger, now when clicking it it will stall the popup menu on the screen for a minute.
Could you refactor this option to close the popup menu first and then load the object into freecad?

Hello Dan, many thanks for your great contribution!
In order that users can easily install your addon, can you please make your addon available via GitHub so that one can install it using the Addon manager?

Wouldn’t it be better as a PR to A2+ instead?

To all:
This post is about a program I called Constraint viewer. It is the program above, A2plusmore, which I stripped down to two icons, the viewer and updater, which are really diagnostic tools for constraints. Many of the other functions are broken since the last major update of A2plus.

Uwestoehr and Kunda1:
I put the A2Plusmore program above to see if I could get some feedback on whether or not I should spend any time on separating out the viewer program. Why there are over one hundred downloads, I don’t know. I’m still not sure if the program runs on any machine but mine, but I separated it anyway. Is Klaus is working on A2plus at the moment?
On another note, as a newbie I could use a link on how to submit it as an addon or PR. Plus it needs a lot of cleanup on the code and how to documentation.
Dan
A2pConstraintViewer ver2.zip (934 KB)

Open a new thread to discuss this so this thread doesn’t get derailed is the first place to start. Make a clear ask on said thread and we’ll get the ball rolling on this :wink:

Done. I renamed ConstraintViewer to ConstraintDiagnostics but it is the same workbench.
Thanks.
Dan

URL for the split off topic ?

[url][https://forum.freecadweb.org/viewtopic.php?f=20&t=67124&sid=99734628119a06ad908db4bcd885094b[url](https://forum.freecadweb.org/viewtopic.php?f=20&t=67124&sid=99734628119a06ad908db4bcd885094b[url)]

Also: I have modified the files so it can be a PR to A2+.

Kunda:
These functions are being added to the A2plus assembly program.
The information is here https://forum.freecadweb.org/viewtopic.php?f=20&t=67258.
We don’t want to sidetrack this thread, so please post any comments about these two features on the new thread.
Dan Miel

Workbench have version 0.4.59d now.
And support 7 languages.
All language files placed on github:
https://github.com/kbwbe/A2plus/tree/master/translations

If you can help - you can:

  1. Download your language file (or download A2plus.ts for new language),
  2. Open in QT 5 Linguist,
  3. Translate file,
  4. Upload translated file to github or on this topic.
OS: Ubuntu 20.04.5 LTS (XFCE/xfce)
Word size of FreeCAD: 64-bit
Version: 0.20.1.29410 (Git) AppImage
Build type: Release
Branch: (HEAD detached at 0.20.1)
Hash: f5d13554ecc7a456fb6e970568ae5c74ba727563
Python 3.10.5, Qt 5.15.4, Coin 4.0.0, Vtk 9.1.0, OCC 7.6.2
Locale: English/United States (en_US)
Installed mods: 
  * A2plus 0.4.59

Does anybody else face such an error when trying to update through the Addon manager?
error.png

It seems a git error about something changed in the destination repo (your Mod dir) when AddOn Manager try to do a “git pull --ff-only” it detect that things have changed.

If you don’t have do modifications, try to simply uninstall the AddOn, delete eventually leftovers, and reinstall.

If you have done modification to the sources decide what to do.

Regards

Carlo D.

Thanks for the tips! I have not modified anything.

Hi Klaus and all,

I have problems to assemble easy things:
Ansicht1.png
Now, i am here:

OS: Linux Mint 19.3 (X-Cinnamon/cinnamon)
Word size of FreeCAD: 64-bit
Version: 0.21.0.31488 (Git) AppImage
Build type: Release
Branch: master
Hash: 3e58513c24e533326906be5c87c82aafe582c936
Python 3.10.8, Qt 5.15.4, Coin 4.0.0, Vtk 9.1.0, OCC 7.6.3
Locale: German/Germany (de_DE)
Installed mods: 
  * sheetmetal.backup1663267330.3384569
  * Lithophane
  * BIM 2021.12.0
  * sheetmetal 0.2.60
  * Glass.backup1663237728.6533988 (Disabled)
  * DynamicData 2.46.0
  * A2plus 0.4.60g
  * Glass (Disabled)
  * TabBar
  * kicadStepUpMod 10.16.8
  * fasteners
  * ExplodedAssembly
  * FeedsAndSpeeds 0.5.0
  * freecad.gears 1.0.0
  * 3D_Printing_Tools

Now the file with the parts:
Rohr_Monitorhalterung_asm.FCStd
Ok, what is the problem?

I can not assemble it as it shouldt…

Sorry, it is an whery easy assembling… but for now A2plus is not to use…

Klaus, to you and your family, when you have not the time to fix anything, it is not a problem.

Hi,

i have found the issue.
The issue was between my two ears… :laughing:

After i builded subassemblys it works fine.
Bild.png
Sorry for the dust I kicked up…

Walter

when checking for constraints sometimes things blow up and then popups seem to come up endlessly…

Can you add something like this:

diff --git a/a2p_searchConstraintConflicts.py b/a2p_searchConstraintConflicts.py
index 544b389..628f0df 100644
--- a/a2p_searchConstraintConflicts.py
+++ b/a2p_searchConstraintConflicts.py
@@ -48,6 +48,8 @@ class a2p_SearchConstraintConflictsCommand:
     '''
     def Activated(self):
         doc = FreeCAD.activeDocument()
+        yesflag = False
+        counter = 0
         
         workList = []
         constraints = [ obj for obj in doc.Objects if 'ConstraintInfo' in obj.Content]
@@ -92,16 +94,25 @@ Do you want to delete this constraint-pair?
     ob1.Label,
     ob2.Label
     )                
     )                
-                flags = QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No
-                response = QtGui.QMessageBox.information(
-                    QtGui.QApplication.activeWindow(), 
-                    translate("A2plus_searchConstraintConflicts",'Searching for conflicting constraints'), 
-                    message, 
-                    flags
-                    )
-                if response == QtGui.QMessageBox.Yes:
+                response = None
+                if yesflag == False:
+                    flags = QtGui.QMessageBox.StandardButton.YesToAll | QtGui.QMessageBox.StandardButton.Yes | QtGui.QMessageBox.StandardButton.No
+                    response = QtGui.QMessageBox.information(
+                        QtGui.QApplication.activeWindow(), 
+                        translate("A2plus_searchConstraintConflicts",'Searching for conflicting constraints'), 
+                        message, 
+                        flags
+                        )
+                if response == QtGui.QMessageBox.YesToAll:
+                    yesflag = True
+                if response == QtGui.QMessageBox.Yes or yesflag == True:
                     a2plib.removeConstraint(c)
+                    counter=counter+1
+                
         a2plib.SHOW_WARNING_FLOATING_PARTS = True
+        if counter > 0:
+            print("removed %d constraints" % counter)
+            
        
     def IsActive(self):
         if FreeCAD.activeDocument() is None: return False