diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index 4f21f6e834..2fe48a9a3b 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -30,6 +30,7 @@ from PySide.QtCore import QT_TRANSLATE_NOOP if App.GuiUp: import FreeCADGui as Gui from PySide import QtCore, QtGui, QtWidgets + from PySide.QtGui import QIcon import UtilsAssembly import Preferences @@ -105,8 +106,8 @@ class TaskAssemblyInsertLink(QtCore.QObject): self.form.filterPartList.textChanged.connect(self.onFilterChange) self.form.CheckBox_ShowOnlyParts.stateChanged.connect(self.buildPartList) - self.allParts = [] - self.partsDoc = [] + self.form.partList.header().hide() + self.translation = 0 self.partMoving = False self.totalTranslation = App.Vector() @@ -142,54 +143,91 @@ class TaskAssemblyInsertLink(QtCore.QObject): Gui.Selection.clearSelection() def buildPartList(self): - self.allParts.clear() - self.partsDoc.clear() + self.form.partList.clear() docList = App.listDocuments().values() for doc in docList: - if UtilsAssembly.isDocTemporary(doc): - continue + # Create a new tree item for the document + docItem = QtGui.QTreeWidgetItem() + docItem.setText(0, doc.Name + ".FCStd") + docItem.setIcon(0, QIcon.fromTheme("add", QIcon(":/icons/Document.svg"))) - # Build list of current assembly's parents, including the current assembly itself - parents = self.assembly.Parents - if parents: - root_parent, sub = parents[0] - parents_names, _ = UtilsAssembly.getObjsNamesAndElement(root_parent.Name, sub) - else: - parents_names = [self.assembly.Name] + if not any( + (child.isDerivedFrom("Part::Feature") or child.isDerivedFrom("App::Part")) + for child in doc.Objects + ): + continue # Skip this doc if no relevant objects - for obj in doc.findObjects("App::Part"): - # we don't want to link to itself or parents. - if obj.Name not in parents_names: - self.allParts.append(obj) - self.partsDoc.append(doc) + self.form.partList.addTopLevelItem(docItem) - if not self.form.CheckBox_ShowOnlyParts.isChecked(): - for obj in doc.findObjects("Part::Feature"): - # but only those at top level (not nested inside other containers) - if obj.getParentGeoFeatureGroup() is None: - self.allParts.append(obj) - self.partsDoc.append(doc) + def process_objects(objs, item): + onlyParts = self.form.CheckBox_ShowOnlyParts.isChecked() + for obj in objs: + if obj.Name == self.assembly.Name: + continue # Skip current assembly - self.form.partList.clear() - for part in self.allParts: - newItem = QtGui.QListWidgetItem() - newItem.setText(part.Label + " (" + part.Document.Name + ".FCStd)") - newItem.setIcon(part.ViewObject.Icon) - self.form.partList.addItem(newItem) + if ( + obj.isDerivedFrom("Part::Feature") + or obj.isDerivedFrom("App::Part") + or obj.isDerivedFrom("App::DocumentObjectGroup") + ): + # Special handling for DocumentObjectGroup: only add if it contains relevant child objects + if obj.isDerivedFrom("App::DocumentObjectGroup"): + if not any( + ( + (not onlyParts and child.isDerivedFrom("Part::Feature")) + or child.isDerivedFrom("App::Part") + ) + for child in obj.OutListRecursive + ): + continue # Skip this object if no relevant children + + if obj.isDerivedFrom("Part::Feature"): + if onlyParts: + continue # Ignore solids if we show only Parts + + # Now add the object under the document item + objItem = QtGui.QTreeWidgetItem(item) + objItem.setText(0, obj.Label) + objItem.setIcon( + 0, obj.ViewObject.Icon if hasattr(obj, "ViewObject") else QtGui.QIcon() + ) # Use object's icon if available + + if not obj.isDerivedFrom("App::DocumentObjectGroup"): + objItem.setData(0, QtCore.Qt.UserRole, obj) + + if obj.isDerivedFrom("App::Part") or obj.isDerivedFrom( + "App::DocumentObjectGroup" + ): + process_objects(obj.OutList, objItem) + + process_objects(doc.RootObjects, docItem) + self.form.partList.expandAll() def onFilterChange(self): filter_str = self.form.filterPartList.text().strip().lower() - for i in range(self.form.partList.count()): - item = self.form.partList.item(i) - item_text = item.text().lower() - - # Check if the item's text contains the filter string + def filter_tree_item(item): + # This function recursively filters items based on the filter string. + item_text = item.text(0).lower() # Assuming the relevant text is in the first column is_visible = filter_str in item_text if filter_str else True + child_count = item.childCount() + for i in range(child_count): + child = item.child(i) + child_is_visible = filter_tree_item(child) # Recursively filter children + is_visible = ( + is_visible or child_is_visible + ) # Parent is visible if any child matches + item.setHidden(not is_visible) + return is_visible + + root_count = self.form.partList.topLevelItemCount() + for i in range(root_count): + root_item = self.form.partList.topLevelItem(i) + filter_tree_item(root_item) # Filter from each root item def openFiles(self): selected_files, _ = QtGui.QFileDialog.getOpenFileNames( @@ -213,27 +251,56 @@ class TaskAssemblyInsertLink(QtCore.QObject): self.buildPartList() def onItemClicked(self, item): - for selected in self.form.partList.selectedIndexes(): - selectedPart = self.allParts[selected.row()] + selectedPart = item.data(0, QtCore.Qt.UserRole) if not selectedPart: + # If there's no part associated, toggle the expanded state + item.setExpanded(not item.isExpanded()) return if self.partMoving: self.endMove() # check that the current document had been saved or that it's the same document as that of the selected part - if not self.doc.FileName != "" and not self.doc == selectedPart.Document: - msgBox = QtWidgets.QMessageBox() - msgBox.setIcon(QtWidgets.QMessageBox.Warning) - msgBox.setText("The current document must be saved before inserting external parts.") - msgBox.setWindowTitle("Save Document") - saveButton = msgBox.addButton("Save", QtWidgets.QMessageBox.AcceptRole) - cancelButton = msgBox.addButton("Cancel", QtWidgets.QMessageBox.RejectRole) + if not self.doc == selectedPart.Document: + if self.doc.FileName == "": + msgBox = QtWidgets.QMessageBox() + msgBox.setIcon(QtWidgets.QMessageBox.Warning) + msgBox.setText( + "The current document must be saved before inserting external parts." + ) + msgBox.setWindowTitle("Save Document") + saveButton = msgBox.addButton("Save", QtWidgets.QMessageBox.AcceptRole) + cancelButton = msgBox.addButton("Cancel", QtWidgets.QMessageBox.RejectRole) - msgBox.exec_() + msgBox.exec_() - if not (msgBox.clickedButton() == saveButton and Gui.ActiveDocument.saveAs()): - return + if not (msgBox.clickedButton() == saveButton and Gui.ActiveDocument.saveAs()): + return + + # check that the selectedPart document is saved. + if selectedPart.Document.FileName == "": + msgBox = QtWidgets.QMessageBox() + msgBox.setIcon(QtWidgets.QMessageBox.Warning) + msgBox.setText("The selected object's document must be saved before inserting it.") + msgBox.setWindowTitle("Save Document") + saveButton = msgBox.addButton("Save", QtWidgets.QMessageBox.AcceptRole) + cancelButton = msgBox.addButton("Cancel", QtWidgets.QMessageBox.RejectRole) + + msgBox.exec_() + + if not ( + msgBox.clickedButton() == saveButton + and selectedPart.ViewObject.Document.saveAs() + ): + return + + # Update the document item text - useless because Document.Name still return 'Unnamed' + """documentItem = item + while documentItem.parent() is not None: + documentItem = documentItem.parent() + newDocName = selectedPart.Document.Name + print(selectedPart.Document.Name) + documentItem.setText(0, f"{newDocName}.FCStd")""" createdLink = self.assembly.newObject("App::Link", selectedPart.Label) createdLink.LinkedObject = selectedPart @@ -260,7 +327,8 @@ class TaskAssemblyInsertLink(QtCore.QObject): # Start moving the part if user brings mouse on view self.initMove() - self.form.partList.setItemSelected(item, False) + item.setSelected(False) + # self.form.partList.setItemSelected(item, False) if len(self.insertionStack) == 1 and not UtilsAssembly.isAssemblyGrounded(): self.handleFirstInsertion() @@ -306,7 +374,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): self.endMove() def increment_counter(self, item): - text = item.text() + text = item.text(0) match = re.search(r"(\d+) inserted$", text) if match: @@ -317,10 +385,10 @@ class TaskAssemblyInsertLink(QtCore.QObject): # Counter does not exist, add it new_text = f"{text} : 1 inserted" - item.setText(new_text) + item.setText(0, new_text) def decrement_counter(self, item): - text = item.text() + text = item.text(0) match = re.search(r"(\d+) inserted$", text) if match: @@ -334,7 +402,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): else: return - item.setText(new_text) + item.setText(0, new_text) def initMove(self): self.callbackMove = self.view.addEventCallback("SoLocation2Event", self.moveMouse) diff --git a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui index 1cc4a41ac1..bc5953f6de 100644 --- a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui +++ b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui @@ -22,7 +22,7 @@ - +