From 8f02c0d6f2638a4102c3d6d694d895de4f3388b7 Mon Sep 17 00:00:00 2001 From: forbes Date: Sat, 14 Feb 2026 15:03:13 -0600 Subject: [PATCH] fix: Insert Component uses Silo browser when available When the silo workbench is loaded, Insert Component now opens the Silo part browser (OpenItemWidget) instead of the OS file dialog. Users can search both the Silo database and local files to find components to insert. Falls back to the OS file dialog if silo-mod is not installed. Also adds .kc file support to the fallback file dialog filter. Closes #202 --- src/Mod/Assembly/CommandInsertLink.py | 145 ++++++++++++++++++++------ src/Mod/CMakeLists.txt | 2 - src/Mod/Create/CMakeLists.txt | 1 - 3 files changed, 116 insertions(+), 32 deletions(-) diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index 2c0d4afaa3..33fa94c934 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -21,10 +21,10 @@ # * # **************************************************************************/ -import re import os -import FreeCAD as App +import re +import FreeCAD as App from PySide.QtCore import QT_TRANSLATE_NOOP if App.GuiUp: @@ -32,10 +32,9 @@ if App.GuiUp: from PySide import QtCore, QtGui, QtWidgets from PySide.QtGui import QIcon -import UtilsAssembly -import Preferences import CommandCreateJoint - +import Preferences +import UtilsAssembly __title__ = "Assembly Command Insert Component" __author__ = "Ondsel" @@ -118,8 +117,12 @@ class TaskAssemblyInsertLink(QtCore.QObject): self.form.partList.installEventFilter(self) pref = Preferences.preferences() - self.form.CheckBox_ShowOnlyParts.setChecked(pref.GetBool("InsertShowOnlyParts", False)) - self.form.CheckBox_RigidSubAsm.setChecked(pref.GetBool("InsertRigidSubAssemblies", True)) + self.form.CheckBox_ShowOnlyParts.setChecked( + pref.GetBool("InsertShowOnlyParts", False) + ) + self.form.CheckBox_RigidSubAsm.setChecked( + pref.GetBool("InsertRigidSubAssemblies", True) + ) # Actions self.form.openFileButton.clicked.connect(self.openFiles) @@ -199,8 +202,12 @@ class TaskAssemblyInsertLink(QtCore.QObject): self.docObserver = None pref = Preferences.preferences() - pref.SetBool("InsertShowOnlyParts", self.form.CheckBox_ShowOnlyParts.isChecked()) - pref.SetBool("InsertRigidSubAssemblies", self.form.CheckBox_RigidSubAsm.isChecked()) + pref.SetBool( + "InsertShowOnlyParts", self.form.CheckBox_ShowOnlyParts.isChecked() + ) + pref.SetBool( + "InsertRigidSubAssemblies", self.form.CheckBox_RigidSubAsm.isChecked() + ) Gui.Selection.clearSelection() def buildPartList(self): @@ -216,7 +223,10 @@ class TaskAssemblyInsertLink(QtCore.QObject): icon = QIcon.fromTheme("add", QIcon(":/icons/Document.svg")) if doc.Partial: itemName = ( - itemName + " (" + QT_TRANSLATE_NOOP("Assembly_Insert", "Partially loaded") + ")" + itemName + + " (" + + QT_TRANSLATE_NOOP("Assembly_Insert", "Partially loaded") + + ")" ) icon = self.createDisabledIcon(icon) docItem.setText(0, itemName) @@ -224,7 +234,10 @@ class TaskAssemblyInsertLink(QtCore.QObject): self.doc_item_map[docItem] = doc if not any( - (child.isDerivedFrom("Part::Feature") or child.isDerivedFrom("App::Part")) + ( + child.isDerivedFrom("Part::Feature") + or child.isDerivedFrom("App::Part") + ) for child in doc.Objects ): continue # Skip this doc if no relevant objects @@ -253,7 +266,10 @@ class TaskAssemblyInsertLink(QtCore.QObject): if obj.isDerivedFrom("App::DocumentObjectGroup"): if not any( ( - (not onlyParts and child.isDerivedFrom("Part::Feature")) + ( + not onlyParts + and child.isDerivedFrom("Part::Feature") + ) or child.isDerivedFrom("App::Part") ) for child in obj.ViewObject.claimChildrenRecursive() @@ -268,7 +284,10 @@ class TaskAssemblyInsertLink(QtCore.QObject): objItem = QtGui.QTreeWidgetItem(item) objItem.setText(0, obj.Label) objItem.setIcon( - 0, obj.ViewObject.Icon if hasattr(obj, "ViewObject") else QtGui.QIcon() + 0, + obj.ViewObject.Icon + if hasattr(obj, "ViewObject") + else QtGui.QIcon(), ) # Use object's icon if available if not obj.isDerivedFrom("App::DocumentObjectGroup"): @@ -309,13 +328,17 @@ class TaskAssemblyInsertLink(QtCore.QObject): 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 + 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 + 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 @@ -329,11 +352,50 @@ class TaskAssemblyInsertLink(QtCore.QObject): filter_tree_item(root_item) # Filter from each root item def openFiles(self): + try: + from open_search import OpenItemWidget + from silo_commands import _client, _sync, search_local_files + + self._openSiloSearch(_client, _sync, search_local_files, OpenItemWidget) + except Exception: + self._openFileDialog() + + def _openSiloSearch(self, client, sync, search_local_fn, WidgetClass): + """Show the Silo part browser to select components.""" + mw = Gui.getMainWindow() + mdi = mw.findChild(QtWidgets.QMdiArea) + if not mdi: + self._openFileDialog() + return + + widget = WidgetClass(client, search_local_fn) + + sw = mdi.addSubWindow(widget) + sw.setWindowTitle("Insert Component — Search") + sw.show() + mdi.setActiveSubWindow(sw) + + def _on_selected(data): + sw.close() + path = data.get("path") + part_number = data.get("part_number") + if path: + App.openDocument(path, True) + elif part_number: + sync.open_item(part_number) + App.setActiveDocument(self.doc.Name) + self.buildPartList() + + widget.item_selected.connect(_on_selected) + widget.cancelled.connect(sw.close) + + def _openFileDialog(self): + """Fall back to the OS file dialog.""" selected_files, _ = QtGui.QFileDialog.getOpenFileNames( None, "Select FreeCAD documents to import parts from", "", - "Supported Formats (*.FCStd *.fcstd);;All files (*)", + "Supported Formats (*.FCStd *.fcstd *.kc);;All files (*)", ) for filename in selected_files: @@ -344,7 +406,8 @@ class TaskAssemblyInsertLink(QtCore.QObject): ) if not import_doc_is_open: - if filename.lower().endswith(".fcstd"): + ext = os.path.splitext(filename)[1].lower() + if ext in (".fcstd", ".kc"): App.openDocument(filename, True) App.setActiveDocument(self.doc.Name) self.buildPartList() @@ -366,21 +429,29 @@ class TaskAssemblyInsertLink(QtCore.QObject): ) msgBox.setWindowTitle("Save Document") saveButton = msgBox.addButton("Save", QtWidgets.QMessageBox.AcceptRole) - cancelButton = msgBox.addButton("Cancel", QtWidgets.QMessageBox.RejectRole) + cancelButton = msgBox.addButton( + "Cancel", QtWidgets.QMessageBox.RejectRole + ) msgBox.exec_() - if not (msgBox.clickedButton() == saveButton and Gui.ActiveDocument.saveAs()): + 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.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) + cancelButton = msgBox.addButton( + "Cancel", QtWidgets.QMessageBox.RejectRole + ) msgBox.exec_() @@ -412,7 +483,9 @@ class TaskAssemblyInsertLink(QtCore.QObject): screenCorner = view.getPointOnFocalPlane(x, y) addedObject.LinkedObject = selectedPart - addedObject.Label = selectedPart.Label # non-ASCII characters fails with newObject. #12164 + addedObject.Label = ( + selectedPart.Label + ) # non-ASCII characters fails with newObject. #12164 addedObject.recompute() insertionDict = {} @@ -441,7 +514,9 @@ class TaskAssemblyInsertLink(QtCore.QObject): else: # bboxCenter = addedObject.ViewObject.getBoundingBox().Center - addedObject.Placement.Base = screenCenter - bboxCenter + self.totalTranslation + addedObject.Placement.Base = ( + screenCenter - bboxCenter + self.totalTranslation + ) self.prevScreenCenter = screenCenter @@ -498,7 +573,10 @@ class TaskAssemblyInsertLink(QtCore.QObject): targetObj = self.insertionStack[0]["addedObject"] # If the object is a flexible AssemblyLink, we should ground its internal 'base' part - if targetObj.isDerivedFrom("Assembly::AssemblyLink") and not targetObj.Rigid: + if ( + targetObj.isDerivedFrom("Assembly::AssemblyLink") + and not targetObj.Rigid + ): linkedAsm = targetObj.LinkedObject if linkedAsm and hasattr(linkedAsm, "Group"): srcGrounded = None @@ -514,7 +592,8 @@ class TaskAssemblyInsertLink(QtCore.QObject): candidate = None for child in targetObj.Group: if not candidate and ( - child.isDerivedFrom("App::Link") or child.isDerivedFrom("Part::Feature") + child.isDerivedFrom("App::Link") + or child.isDerivedFrom("Part::Feature") ): candidate = child @@ -532,7 +611,9 @@ class TaskAssemblyInsertLink(QtCore.QObject): targetObj = candidate self.groundedObj = targetObj - self.groundedJoint = CommandCreateJoint.createGroundedJoint(self.groundedObj) + self.groundedJoint = CommandCreateJoint.createGroundedJoint( + self.groundedObj + ) def increment_counter(self, item): text = item.text(0) @@ -607,7 +688,9 @@ class TaskAssemblyInsertLink(QtCore.QObject): "Assembly_Insert", "Fully load document" ) load_action = menu.addAction(load_action_text) - load_action.triggered.connect(lambda: self.fullyLoadDocument(doc)) + load_action.triggered.connect( + lambda: self.fullyLoadDocument(doc) + ) menu.exec_(event.globalPos()) return True # Event was handled @@ -628,7 +711,9 @@ class TaskAssemblyInsertLink(QtCore.QObject): menu = QtWidgets.QMenu() # Add the checkbox action - showHiddenAction = QtWidgets.QAction("Show objects hidden in tree view", menu) + showHiddenAction = QtWidgets.QAction( + "Show objects hidden in tree view", menu + ) showHiddenAction.setCheckable(True) showHiddenAction.setChecked(self.showHidden) @@ -708,7 +793,9 @@ class TaskAssemblyInsertLink(QtCore.QObject): try: # Remove the joint if it still exists if self.groundedJoint.Document: - self.groundedJoint.Document.removeObject(self.groundedJoint.Name) + self.groundedJoint.Document.removeObject( + self.groundedJoint.Name + ) except Exception: pass self.groundedObj = None diff --git a/src/Mod/CMakeLists.txt b/src/Mod/CMakeLists.txt index c7757ea424..e07921e12a 100644 --- a/src/Mod/CMakeLists.txt +++ b/src/Mod/CMakeLists.txt @@ -143,5 +143,3 @@ endif(BUILD_TUX) if(BUILD_WEB) add_subdirectory(Web) endif(BUILD_WEB) - -add_subdirectory(Create) diff --git a/src/Mod/Create/CMakeLists.txt b/src/Mod/Create/CMakeLists.txt index 209a548ec2..de033db073 100644 --- a/src/Mod/Create/CMakeLists.txt +++ b/src/Mod/Create/CMakeLists.txt @@ -13,7 +13,6 @@ install( FILES Init.py InitGui.py - kc_format.py update_checker.py ${CMAKE_CURRENT_BINARY_DIR}/version.py DESTINATION -- 2.49.1