Assembly: Allow editing joint references

As the title says - root cause of this problem was imprecise reference
matching by moving_part and TNP string processing incosistencies, so the
solution is to give enhanced matching and TNP string mapping for
reliable deletion.

Also added context menu with keyboard shortucs so user is able to delete
stuff with right click.
This commit is contained in:
tetektoza
2025-08-20 00:35:21 +02:00
committed by Chris Hennes
parent 59b01f6802
commit 59d4eec796

View File

@@ -32,7 +32,7 @@ from collections.abc import Sequence
if App.GuiUp:
import FreeCADGui as Gui
from PySide import QtWidgets
from PySide import QtGui, QtWidgets
__title__ = "Assembly Joint object"
__author__ = "Ondsel"
@@ -1386,6 +1386,8 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.jForm.featureList.installEventFilter(self)
self.createDeleteAction()
self.addition_rejected = False
def accept(self):
@@ -1784,34 +1786,80 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
if info["State"] == "UP" and info["Key"] == "RETURN":
self.accept()
def _removeSelectedItems(self, selected_indexes):
for index in selected_indexes:
row = index.row()
if row < len(self.refs):
ref = self.refs[row]
ref_id = id(ref)
if hasattr(self, "_original_tnp_map") and ref_id in self._original_tnp_map:
# use original TNP string for newly added references
removal_string = self._original_tnp_map[ref_id]
else:
# use processed element name for reloaded references
removal_string = ref[1][0]
Gui.Selection.removeSelection(ref[0], removal_string)
else:
print(f"Row {row} is out of bounds for refs (length: {len(self.refs)})")
def eventFilter(self, watched, event):
if self.jForm is not None and watched == self.jForm.featureList:
if event.type() == QtCore.QEvent.ShortcutOverride:
if event.key() == QtCore.Qt.Key_Delete:
event.accept() # Accept the event only if the key is Delete
return True # Indicate that the event has been handled
if (
hasattr(self, "deleteAction")
and self.deleteAction.shortcut().matches(event.key())
!= QtGui.QKeySequence.NoMatch
):
event.accept()
return True
return False
elif event.type() == QtCore.QEvent.KeyPress:
if event.key() == QtCore.Qt.Key_Delete:
selected_indexes = self.jForm.featureList.selectedIndexes()
for index in selected_indexes:
row = index.row()
if row < len(self.refs):
ref = self.refs[row]
Gui.Selection.removeSelection(ref[0], ref[1][0])
if (
hasattr(self, "deleteAction")
and self.deleteAction.shortcut().matches(event.key())
!= QtGui.QKeySequence.NoMatch
):
self.deleteAction.trigger()
return True # Consume the event
return super().eventFilter(watched, event)
def createDeleteAction(self):
"""Create delete action with shortcut"""
try:
delete_sequence = Gui.QtTools.deleteKeySequence()
except AttributeError:
# fallback to standard key if there is no sequence defined
delete_sequence = QtCore.Qt.Key_Delete
self.deleteAction = QtGui.QAction("Remove", self.jForm)
self.deleteAction.setShortcut(delete_sequence)
self.deleteAction.setIcon(
QtWidgets.QApplication.style().standardIcon(QtWidgets.QStyle.SP_DialogCancelButton)
)
self.deleteAction.setShortcutVisibleInContextMenu(True)
self.jForm.featureList.addAction(self.deleteAction)
self.jForm.featureList.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.deleteAction.triggered.connect(self.deleteSelectedItems)
def deleteSelectedItems(self):
"""Delete selected items from the feature list - same logic as Delete key in ev filter"""
selected_indexes = self.jForm.featureList.selectedIndexes()
self._removeSelectedItems(selected_indexes)
def getMovingPart(self, ref):
return UtilsAssembly.getMovingPart(self.assembly, ref)
# selectionObserver stuff
def addSelection(self, doc_name, obj_name, sub_name, mousePos):
original_sub_name = sub_name
rootObj = App.getDocument(doc_name).getObject(obj_name)
# We do not need the full TNP string like :"Part.Body.Pad.;#a:1;:G0;XTR;:Hc94:8,F.Face6"
@@ -1849,6 +1897,12 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
# add the vertex name to the reference
ref = UtilsAssembly.addVertexToReference(ref, vertex_name)
# store the original TNP string for deletion purposes
if hasattr(self, "_original_tnp_map"):
self._original_tnp_map[id(ref)] = original_sub_name
else:
self._original_tnp_map = {id(ref): original_sub_name}
self.refs.append(ref)
self.updateJoint()
@@ -1860,15 +1914,24 @@ class TaskAssemblyCreateJoint(QtCore.QObject):
self.addition_rejected = False
return
ref = [App.getDocument(doc_name).getObject(obj_name), [sub_name]]
moving_part = self.getMovingPart(ref)
rootObj = App.getDocument(doc_name).getObject(obj_name)
# Find and remove the corresponding dictionary from the combined list
for reference in self.refs:
sel_moving_part = self.getMovingPart(reference)
if sel_moving_part == moving_part:
# Apply the same processing as in addSelection to ensure consistent comparison
resolved = rootObj.resolveSubElement(sub_name, True)
sub_name = resolved[2]
sub_name = UtilsAssembly.fixBodyExtraFeatureInSub(doc_name, sub_name)
for reference in self.refs[:]:
ref_obj = reference[0]
ref_element_name = reference[1][0] if len(reference[1]) > 0 else ""
# match both object and processed element name for precise identification
if ref_obj == rootObj and ref_element_name == sub_name:
self.refs.remove(reference)
break
else:
print("No matching ref found for removal!")
self.updateJoint()