From a0749888c4635ce4c705108f87c0b47256431d4b Mon Sep 17 00:00:00 2001 From: Paddle Date: Thu, 11 Jan 2024 11:17:55 +0100 Subject: [PATCH] Assembly: Enable part manipulation in active App::Part with the fixed joint. --- src/Mod/Assembly/CommandCreateJoint.py | 10 ++- src/Mod/Assembly/JointObject.py | 115 +++++++++++++++++-------- src/Mod/Assembly/UtilsAssembly.py | 23 ++++- 3 files changed, 107 insertions(+), 41 deletions(-) diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py index 09ef98048a..297f9392cf 100644 --- a/src/Mod/Assembly/CommandCreateJoint.py +++ b/src/Mod/Assembly/CommandCreateJoint.py @@ -62,7 +62,10 @@ class CommandCreateJointFixed: return { "Pixmap": "Assembly_CreateJointFixed", - "MenuText": QT_TRANSLATE_NOOP("Assembly_CreateJointFixed", "Create Fixed Joint"), + "MenuText": QT_TRANSLATE_NOOP( + "Assembly_CreateJointFixed", + "If an assembly is active : Create a Fixed Joint.\n If a part is active : Position sub parts by matching.", + ), "Accel": "J", "ToolTip": "

" + QT_TRANSLATE_NOOP( @@ -74,7 +77,10 @@ class CommandCreateJointFixed: } def IsActive(self): - return isCreateJointActive() + if UtilsAssembly.activePart: + return UtilsAssembly.assembly_has_at_least_n_parts(2) + + return UtilsAssembly.isAssemblyGrounded() and UtilsAssembly.assembly_has_at_least_n_parts(2) def Activated(self): activateJoint(0) diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index 6a7d31e31b..8e445f7a47 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -75,12 +75,14 @@ JointUsingReverse = [ def solveIfAllowed(assembly, storePrev=False): - if Preferences.preferences().GetBool("SolveInJointCreation", True): + if assembly.Type == "Assembly" and Preferences.preferences().GetBool( + "SolveInJointCreation", True + ): assembly.solve(storePrev) class Joint: - def __init__(self, joint, type_index, assembly): + def __init__(self, joint, type_index): self.Type = "Joint" joint.Proxy = self @@ -257,7 +259,20 @@ class Joint: if hasattr( joint, "Vertex1" ): # during loading the onchanged may be triggered before full init. - solveIfAllowed(self.getAssembly(joint)) + isAssembly = self.getAssembly(joint).Type == "Assembly" + if isAssembly: + solveIfAllowed(self.getAssembly(joint)) + else: + self.updateJCSPlacements(joint) + obj1 = UtilsAssembly.getObjectInPart(joint.Object1, joint.Part1) + obj2 = UtilsAssembly.getObjectInPart(joint.Object2, joint.Part2) + self.preSolve( + joint, + obj1, + joint.Part1, + obj2, + joint.Part2, + ) def execute(self, fp): """Do something when doing a recomputation, this method is mandatory""" @@ -267,10 +282,14 @@ class Joint: def setJointConnectors(self, joint, current_selection): # current selection is a vector of strings like "Assembly.Assembly1.Assembly2.Body.Pad.Edge16" including both what selection return as obj_name and obj_sub assembly = self.getAssembly(joint) + isAssembly = assembly.Type == "Assembly" if len(current_selection) >= 1: joint.Part1 = None - self.part1Connected = assembly.isPartConnected(current_selection[0]["part"]) + if isAssembly: + self.part1Connected = assembly.isPartConnected(current_selection[0]["part"]) + else: + self.part1Connected = True joint.Object1 = current_selection[0]["object"].Name joint.Part1 = current_selection[0]["part"] @@ -289,7 +308,10 @@ class Joint: if len(current_selection) >= 2: joint.Part2 = None - self.part2Connected = assembly.isPartConnected(current_selection[1]["part"]) + if isAssembly: + self.part2Connected = assembly.isPartConnected(current_selection[1]["part"]) + else: + self.part2Connected = False joint.Object2 = current_selection[1]["object"].Name joint.Part2 = current_selection[1]["part"] @@ -305,7 +327,10 @@ class Joint: current_selection[1]["object"], joint.Part2, ) - solveIfAllowed(assembly, True) + if isAssembly: + solveIfAllowed(assembly, True) + else: + self.updateJCSPlacements(joint) else: joint.Object2 = "" @@ -313,7 +338,8 @@ class Joint: joint.Element2 = "" joint.Vertex2 = "" joint.Placement2 = App.Placement() - assembly.undoSolve() + if isAssembly: + assembly.undoSolve() self.undoPreSolve() def updateJCSPlacements(self, joint): @@ -342,7 +368,6 @@ class Joint: return App.Placement() obj = UtilsAssembly.getObjectInPart(objName, part) - assembly = self.getAssembly(joint) plc = App.Placement() if not obj: @@ -437,6 +462,7 @@ class Joint: # plc = global_plc * plc # change plc to be relative to the assembly. + # assembly = self.getAssembly(joint) # plc = assembly.Placement.inverse() * plc # We apply rotation / reverse / offset it necessary, but only to the second JCS. @@ -544,9 +570,7 @@ class Joint: jcsPlc1 = self.flipPlacement(jcsPlc1) joint.Part1.Placement = globalJcsPlc2 * jcsPlc1.inverse() - def undoPreSolve( - self, - ): + def undoPreSolve(self): if self.partMovedByPresolved: self.partMovedByPresolved.Placement = self.presolveBackupPlc self.partMovedByPresolved = None @@ -913,8 +937,9 @@ class MakeJointSelGate: full_obj_name = ".".join(objs_names) full_element_name = full_obj_name + "." + element_name selected_object = UtilsAssembly.getObject(full_element_name) + part_containing_selected_object = UtilsAssembly.getContainingPart( - full_element_name, selected_object + full_element_name, selected_object, self.assembly ) for selection_dict in self.taskbox.current_selection: @@ -936,19 +961,30 @@ class TaskAssemblyCreateJoint(QtCore.QObject): activeTask = self self.assembly = UtilsAssembly.activeAssembly() + if not self.assembly: + self.assembly = UtilsAssembly.activePart() + self.activeType = "Part" + else: + self.activeType = "Assembly" + self.view = Gui.activeDocument().activeView() self.doc = App.ActiveDocument if not self.assembly or not self.view or not self.doc: return - self.assembly.ViewObject.EnableMovement = False + if self.activeType == "Assembly": + self.assembly.ViewObject.EnableMovement = False self.form = Gui.PySideUic.loadUi(":/panels/TaskAssemblyCreateJoint.ui") + if self.activeType == "Part": + self.form.setWindowTitle("Match parts") + self.form.jointType.hide() self.form.jointType.addItems(JointTypes) self.form.jointType.setCurrentIndex(jointTypeIndex) self.form.jointType.currentIndexChanged.connect(self.onJointTypeChanged) + self.form.distanceSpinbox.valueChanged.connect(self.onDistanceChanged) self.form.offsetSpinbox.valueChanged.connect(self.onOffsetChanged) self.form.rotationSpinbox.valueChanged.connect(self.onRotationChanged) @@ -968,7 +1004,10 @@ class TaskAssemblyCreateJoint(QtCore.QObject): else: self.creating = True self.jointName = self.form.jointType.currentText().replace(" ", "") - App.setActiveTransaction("Create " + self.jointName + " Joint") + if self.activeType == "Part": + App.setActiveTransaction("Transform") + else: + App.setActiveTransaction("Create " + self.jointName + " Joint") self.current_selection = [] self.preselection_dict = None @@ -1001,7 +1040,10 @@ class TaskAssemblyCreateJoint(QtCore.QObject): self.deactivate() solveIfAllowed(self.assembly) - self.joint.Visibility = self.visibilityBackup + if self.activeType == "Assembly": + self.joint.Visibility = self.visibilityBackup + else: + self.joint.Document.removeObject(self.joint.Name) App.closeActiveTransaction() return True @@ -1016,9 +1058,11 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def deactivate(self): global activeTask activeTask = None - self.assembly.clearUndo() - self.assembly.ViewObject.EnableMovement = True + if self.activeType == "Assembly": + self.assembly.clearUndo() + self.assembly.ViewObject.EnableMovement = True + Gui.Selection.removeSelectionGate() Gui.Selection.removeObserver(self) Gui.Selection.setSelectionStyle(Gui.Selection.SelectionStyle.NormalSelection) @@ -1057,7 +1101,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) selected_object = UtilsAssembly.getObject(full_element_name) element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = UtilsAssembly.getContainingPart( + part_containing_selected_object = self.getContainingPart( full_element_name, selected_object ) @@ -1090,10 +1134,13 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def createJointObject(self): type_index = self.form.jointType.currentIndex() - joint_group = UtilsAssembly.getJointGroup(self.assembly) + if self.activeType == "Part": + self.joint = self.assembly.newObject("App::FeaturePython", "Temporary joint") + else: + joint_group = UtilsAssembly.getJointGroup(self.assembly) + self.joint = joint_group.newObject("App::FeaturePython", self.jointName) - self.joint = joint_group.newObject("App::FeaturePython", self.jointName) - Joint(self.joint, type_index, self.assembly) + Joint(self.joint, type_index) ViewProviderJoint(self.joint.ViewObject) def onJointTypeChanged(self, index): @@ -1275,15 +1322,16 @@ class TaskAssemblyCreateJoint(QtCore.QObject): if info["State"] == "UP" and info["Key"] == "RETURN": self.accept() + def getContainingPart(self, full_element_name, obj): + return UtilsAssembly.getContainingPart(full_element_name, obj, self.assembly) + # selectionObserver stuff def addSelection(self, doc_name, obj_name, sub_name, mousePos): full_obj_name = UtilsAssembly.getFullObjName(obj_name, sub_name) full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) selected_object = UtilsAssembly.getObject(full_element_name) element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = UtilsAssembly.getContainingPart( - full_element_name, selected_object - ) + part_containing_selected_object = self.getContainingPart(full_element_name, selected_object) selection_dict = { "object": selected_object, @@ -1308,9 +1356,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) selected_object = UtilsAssembly.getObject(full_element_name) element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = UtilsAssembly.getContainingPart( - full_element_name, selected_object - ) + part_containing_selected_object = self.getContainingPart(full_element_name, selected_object) # Find and remove the corresponding dictionary from the combined list selection_dict_to_remove = None @@ -1333,9 +1379,7 @@ class TaskAssemblyCreateJoint(QtCore.QObject): full_element_name = UtilsAssembly.getFullElementName(obj_name, sub_name) selected_object = UtilsAssembly.getObject(full_element_name) element_name = UtilsAssembly.getElementName(full_element_name) - part_containing_selected_object = UtilsAssembly.getContainingPart( - full_element_name, selected_object - ) + part_containing_selected_object = self.getContainingPart(full_element_name, selected_object) self.preselection_dict = { "object": selected_object, @@ -1352,11 +1396,12 @@ class TaskAssemblyCreateJoint(QtCore.QObject): def setJointsPickableState(self, state: bool): """Make all joints in assembly selectable (True) or unselectable (False) in 3D view""" - try: + if self.activeType == "Assembly": jointGroup = UtilsAssembly.getJointGroup(self.assembly) for joint in jointGroup.Group: - if hasattr(joint, "Part1"): + if hasattr(joint, "JointType"): joint.ViewObject.Proxy.setPickableState(state) - except Exception as e: - s = "" if state else "un" - App.Console.PrintWarning(f"Failed to set joints {s}pickable: {e}") + else: + for obj in self.assembly.OutList: + if obj.TypeId == "App::FeaturePython" and hasattr(obj, "JointType"): + obj.ViewObject.Proxy.setPickableState(state) diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 6e6f4a6ea0..4bdaa28b2e 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -79,7 +79,9 @@ def assembly_has_at_least_n_parts(n): assembly = activeAssembly() i = 0 if not assembly: - return False + assembly = activePart() + if not assembly: + return False for obj in assembly.OutList: # note : groundedJoints comes in the outlist so we filter those out. if hasattr(obj, "Placement") and not hasattr(obj, "ObjectToGround"): @@ -168,7 +170,7 @@ def isBodySubObject(typeId): ) -def getContainingPart(full_name, selected_object): +def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None): # full_name is "Assembly.Assembly1.LinkOrPart1.LinkOrBox.Edge16" -> LinkOrPart1 # or "Assembly.Assembly1.LinkOrPart1.LinkOrBody.pad.Edge16" -> LinkOrPart1 # or "Assembly.Assembly1.LinkOrPart1.LinkOrBody.Sketch.Edge1" -> LinkOrBody @@ -200,7 +202,12 @@ def getContainingPart(full_name, selected_object): # Note here we may want to specify a specific behavior for Assembly::AssemblyObject. if obj.TypeId == "App::Part": if obj.hasObject(selected_object, True): - return obj + if not activeAssemblyOrPart: + return obj + elif obj.hasObject(activeAssemblyOrPart, True) or obj == activeAssemblyOrPart: + continue + else: + return obj elif obj.TypeId == "App::Link": linked_obj = obj.getLinkedObject() @@ -211,7 +218,15 @@ def getContainingPart(full_name, selected_object): # linked_obj_doc = linked_obj.Document # selected_obj_in_doc = doc.getObject(selected_object.Name) if linked_obj.hasObject(selected_object, True): - return obj + if not activeAssemblyOrPart: + return obj + elif ( + linked_obj.hasObject(activeAssemblyOrPart, True) + or linked_obj == activeAssemblyOrPart + ): + continue + else: + return obj # no container found so we return the object itself. return selected_object