From 4a54e9b7cdefa3095dcf584e1479907ae636d220 Mon Sep 17 00:00:00 2001 From: forbes Date: Sat, 14 Feb 2026 15:06:40 -0600 Subject: [PATCH 1/2] =?UTF-8?q?feat(silo):=20update=20silo=20submodule=20?= =?UTF-8?q?=E2=80=94=20DAG=20API=20methods=20(#215)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds push_dag() and get_dag() to SiloClient: - push_dag: PUT /api/items/{pn}/dag with nodes + edges - get_dag: GET /api/items/{pn}/dag with optional revision filter Closes #215 --- mods/silo | 2 +- src/Mod/Assembly/CommandInsertLink.py | 145 ++++++-------------------- 2 files changed, 30 insertions(+), 117 deletions(-) diff --git a/mods/silo b/mods/silo index 3a9fe6aed8..4921095296 160000 --- a/mods/silo +++ b/mods/silo @@ -1 +1 @@ -Subproject commit 3a9fe6aed89cc36f68999feae8e327e64b8c1885 +Subproject commit 492109529632fedc9be90e1a0eb91fbe665b5d97 diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index 33fa94c934..2c0d4afaa3 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -21,10 +21,10 @@ # * # **************************************************************************/ -import os import re - +import os import FreeCAD as App + from PySide.QtCore import QT_TRANSLATE_NOOP if App.GuiUp: @@ -32,9 +32,10 @@ if App.GuiUp: from PySide import QtCore, QtGui, QtWidgets from PySide.QtGui import QIcon -import CommandCreateJoint -import Preferences import UtilsAssembly +import Preferences +import CommandCreateJoint + __title__ = "Assembly Command Insert Component" __author__ = "Ondsel" @@ -117,12 +118,8 @@ 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) @@ -202,12 +199,8 @@ 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): @@ -223,10 +216,7 @@ 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) @@ -234,10 +224,7 @@ 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 @@ -266,10 +253,7 @@ 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() @@ -284,10 +268,7 @@ 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"): @@ -328,17 +309,13 @@ 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 @@ -352,50 +329,11 @@ 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 *.kc);;All files (*)", + "Supported Formats (*.FCStd *.fcstd);;All files (*)", ) for filename in selected_files: @@ -406,8 +344,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): ) if not import_doc_is_open: - ext = os.path.splitext(filename)[1].lower() - if ext in (".fcstd", ".kc"): + if filename.lower().endswith(".fcstd"): App.openDocument(filename, True) App.setActiveDocument(self.doc.Name) self.buildPartList() @@ -429,29 +366,21 @@ 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_() @@ -483,9 +412,7 @@ 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 = {} @@ -514,9 +441,7 @@ 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 @@ -573,10 +498,7 @@ 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 @@ -592,8 +514,7 @@ 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 @@ -611,9 +532,7 @@ 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) @@ -688,9 +607,7 @@ 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 @@ -711,9 +628,7 @@ 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) @@ -793,9 +708,7 @@ 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 -- 2.49.1 From fa92f5a4d9ea20587e4396ba8c35b92a8dea211e Mon Sep 17 00:00:00 2001 From: forbes Date: Sat, 14 Feb 2026 15:11:00 -0600 Subject: [PATCH 2/2] =?UTF-8?q?feat(silo):=20update=20silo=20submodule=20?= =?UTF-8?q?=E2=80=94=20push=20DAG=20on=20save/commit=20(#216)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wires DAG extraction into Silo_Save and Silo_Commit commands. After successful file upload, extracts the feature DAG and pushes it to the server. Failures warn but never block the save. Closes #216 --- mods/silo | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/silo b/mods/silo index 4921095296..3dd0da3964 160000 --- a/mods/silo +++ b/mods/silo @@ -1 +1 @@ -Subproject commit 492109529632fedc9be90e1a0eb91fbe665b5d97 +Subproject commit 3dd0da39648d756cc5df0fab757a85d01c3bae6e -- 2.49.1