fix: Insert Component uses Silo browser when available (#202) #222

Merged
forbes merged 1 commits from fix/insert-component-silo into main 2026-02-14 21:04:53 +00:00
3 changed files with 116 additions and 32 deletions

View File

@@ -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

View File

@@ -143,5 +143,3 @@ endif(BUILD_TUX)
if(BUILD_WEB)
add_subdirectory(Web)
endif(BUILD_WEB)
add_subdirectory(Create)

View File

@@ -13,7 +13,6 @@ install(
FILES
Init.py
InitGui.py
kc_format.py
update_checker.py
${CMAKE_CURRENT_BINARY_DIR}/version.py
DESTINATION