From 02ad9a00db13198371322cb81fcf88f42d41de94 Mon Sep 17 00:00:00 2001 From: Zoe Forbes Date: Sat, 7 Feb 2026 14:30:37 -0600 Subject: [PATCH 1/3] fix(silo): fix auth crashes, menu redundancy, and origin connect - Add missing _get_auth_token() function that caused NameError in Settings - Replace _client.auth_username/role/source() calls with _get_auth_*() helpers (methods don't exist on SiloClient, crashed auth dock widget) - Fix connect() in silo_origin.py to show login dialog instead of just revealing the dock panel (was using non-existent Command.get() API) - Separate toolbar (file ops) from menu (admin/management commands) - Remove DEBUG logging from Silo_Save command - Fix long line formatting in silo_origin.py --- freecad/InitGui.py | 15 ++++++++++++-- freecad/silo_commands.py | 45 +++++++++------------------------------- freecad/silo_origin.py | 36 ++++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/freecad/InitGui.py b/freecad/InitGui.py index 0a6d1b9..662e559 100644 --- a/freecad/InitGui.py +++ b/freecad/InitGui.py @@ -44,14 +44,23 @@ class SiloWorkbench(FreeCADGui.Workbench): "Silo_Commit", "Silo_Pull", "Silo_Push", + ] + + # Menu has management/admin commands (file commands are in File menu + # via the Create module's SiloMenuManipulator) + self.menu_commands = [ "Silo_Info", "Silo_BOM", + "Silo_TagProjects", + "Silo_SetStatus", + "Silo_Rollback", + "Separator", "Silo_Settings", "Silo_Auth", ] self.appendToolbar("Silo", self.toolbar_commands) - self.appendMenu("Silo", self.toolbar_commands) + self.appendMenu("Silo", self.menu_commands) def Activated(self): """Called when workbench is activated.""" @@ -67,7 +76,9 @@ class SiloWorkbench(FreeCADGui.Workbench): def _show_shortcut_recommendations(self): """Show keyboard shortcut recommendations dialog on first activation.""" try: - param_group = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/KindredSilo") + param_group = FreeCAD.ParamGet( + "User parameter:BaseApp/Preferences/Mod/KindredSilo" + ) if param_group.GetBool("ShortcutsShown", False): return param_group.SetBool("ShortcutsShown", True) diff --git a/freecad/silo_commands.py b/freecad/silo_commands.py index 01ba57f..b162c87 100644 --- a/freecad/silo_commands.py +++ b/freecad/silo_commands.py @@ -106,6 +106,10 @@ def _get_auth_source() -> str: return param.GetString("AuthSource", "") +def _get_auth_token() -> str: + return _fc_settings.get_api_token() + + # Thin wrappers so command classes can call these without refactoring. # They delegate to the settings adapter or the shared client. @@ -848,9 +852,6 @@ class Silo_Save: # Check if document has unsaved changes gui_doc = FreeCADGui.getDocument(doc.Name) is_modified = gui_doc.Modified if gui_doc else True - FreeCAD.Console.PrintMessage( - f"[DEBUG] Modified={is_modified}, FileName={doc.FileName}\n" - ) if gui_doc and not is_modified and doc.FileName: FreeCAD.Console.PrintMessage("No changes to save.\n") @@ -858,17 +859,9 @@ class Silo_Save: # Collect properties BEFORE saving to avoid dirtying the document # (accessing Shape properties can trigger recompute) - FreeCAD.Console.PrintMessage("[DEBUG] Collecting properties...\n") properties = collect_document_properties(doc) - # Check modified state after collecting properties - is_modified_after_props = gui_doc.Modified if gui_doc else True - FreeCAD.Console.PrintMessage( - f"[DEBUG] After collect_properties: Modified={is_modified_after_props}\n" - ) - # Save locally - FreeCAD.Console.PrintMessage("[DEBUG] Saving to canonical path...\n") file_path = _sync.save_to_canonical_path(doc, force_rename=True) if not file_path: # Fallback to regular save if canonical path fails @@ -879,24 +872,12 @@ class Silo_Save: FreeCAD.Console.PrintError("Could not determine save path\n") return - # Check modified state after save - is_modified_after_save = gui_doc.Modified if gui_doc else True - FreeCAD.Console.PrintMessage( - f"[DEBUG] After save: Modified={is_modified_after_save}\n" - ) - # Force clear modified flag if save succeeded (needed for assemblies) - if is_modified_after_save and gui_doc: - FreeCAD.Console.PrintMessage( - "[DEBUG] Attempting to clear Modified flag...\n" - ) + if gui_doc and gui_doc.Modified: try: gui_doc.Modified = False - FreeCAD.Console.PrintMessage( - f"[DEBUG] After force clear: Modified={gui_doc.Modified}\n" - ) - except Exception as e: - FreeCAD.Console.PrintMessage(f"[DEBUG] Could not clear Modified: {e}\n") + except Exception: + pass FreeCAD.Console.PrintMessage(f"Saved: {file_path}\n") @@ -909,12 +890,6 @@ class Silo_Save: new_rev = result["revision_number"] FreeCAD.Console.PrintMessage(f"Uploaded as revision {new_rev}\n") - # Check modified state after upload - is_modified_after_upload = gui_doc.Modified if gui_doc else True - FreeCAD.Console.PrintMessage( - f"[DEBUG] After upload: Modified={is_modified_after_upload}\n" - ) - except Exception as e: FreeCAD.Console.PrintWarning(f"Upload failed: {e}\n") FreeCAD.Console.PrintMessage("File saved locally but not uploaded.\n") @@ -2675,9 +2650,9 @@ class SiloAuthDockWidget: self._url_label.setText(_get_api_url()) has_token = _client.is_authenticated() - username = _client.auth_username() - role = _client.auth_role() - source = _client.auth_source() + username = _get_auth_username() + role = _get_auth_role() + source = _get_auth_source() # Check server connectivity try: diff --git a/freecad/silo_origin.py b/freecad/silo_origin.py index d78600c..fd186e5 100644 --- a/freecad/silo_origin.py +++ b/freecad/silo_origin.py @@ -15,10 +15,10 @@ import FreeCADGui from .silo_commands import ( _client, _sync, + collect_document_properties, + find_file_by_part_number, get_tracked_object, set_silo_properties, - find_file_by_part_number, - collect_document_properties, ) @@ -126,19 +126,35 @@ class SiloOrigin: def connect(self) -> bool: """Trigger authentication if needed. - Shows the Silo authentication dialog if not already authenticated. + Shows the Silo login dialog if not already authenticated. Returns: True if authenticated after this call """ if _client.is_authenticated(): - return True + try: + ok, _ = _client.check_connection() + if ok: + return True + except Exception: + pass - # Show auth dialog via existing Silo_Auth command + # Show login dialog directly try: - cmd = FreeCADGui.Command.get("Silo_Auth") - if cmd: - cmd.Activated() + from PySide import QtWidgets + + mw = FreeCADGui.getMainWindow() + if mw is None: + return False + + # Find or create the auth dock widget and trigger its login dialog + panel = mw.findChild(QtWidgets.QDockWidget, "SiloDatabaseAuth") + if panel and hasattr(panel, "_auth"): + panel._auth._show_login_dialog() + else: + # Fallback: run the Settings command so the user can configure + FreeCADGui.runCommand("Silo_Settings") + return _client.is_authenticated() except Exception as e: FreeCAD.Console.PrintError(f"Silo connect failed: {e}\n") @@ -379,7 +395,9 @@ class SiloOrigin: # Upload to Silo properties = collect_document_properties(doc) - _client._upload_file(obj.SiloPartNumber, str(file_path), properties, comment="") + _client._upload_file( + obj.SiloPartNumber, str(file_path), properties, comment="" + ) # Clear modified flag doc.Modified = False From fcb0a214e22cfe1730507d6756aa275f0b81f2f5 Mon Sep 17 00:00:00 2001 From: Zoe Forbes Date: Sat, 7 Feb 2026 21:28:55 -0600 Subject: [PATCH 2/3] fix(gui): remove Silo toolbar and ToggleMode in favor of unified origin system Remove the separate Silo workbench toolbar and Silo_ToggleMode command. File operations (New/Open/Save) are now handled by the standard File toolbar via the origin system. The Silo menu retains admin commands (Settings, Auth, Info, BOM, TagProjects, SetStatus, Rollback). Closes #65 --- freecad/InitGui.py | 53 ++------------------------ freecad/silo_commands.py | 81 +--------------------------------------- 2 files changed, 4 insertions(+), 130 deletions(-) diff --git a/freecad/InitGui.py b/freecad/InitGui.py index 662e559..f07896d 100644 --- a/freecad/InitGui.py +++ b/freecad/InitGui.py @@ -35,19 +35,9 @@ class SiloWorkbench(FreeCADGui.Workbench): except Exception as e: FreeCAD.Console.PrintWarning(f"Could not register Silo origin: {e}\n") - self.toolbar_commands = [ - "Silo_ToggleMode", - "Separator", - "Silo_Open", - "Silo_New", - "Silo_Save", - "Silo_Commit", - "Silo_Pull", - "Silo_Push", - ] - - # Menu has management/admin commands (file commands are in File menu - # via the Create module's SiloMenuManipulator) + # Silo menu provides admin/management commands. + # File operations (New/Open/Save) are handled by the standard File + # toolbar via the origin system -- no separate Silo toolbar needed. self.menu_commands = [ "Silo_Info", "Silo_BOM", @@ -59,13 +49,11 @@ class SiloWorkbench(FreeCADGui.Workbench): "Silo_Auth", ] - self.appendToolbar("Silo", self.toolbar_commands) self.appendMenu("Silo", self.menu_commands) def Activated(self): """Called when workbench is activated.""" FreeCAD.Console.PrintMessage("Kindred Silo workbench activated\n") - self._show_shortcut_recommendations() def Deactivated(self): pass @@ -73,41 +61,6 @@ class SiloWorkbench(FreeCADGui.Workbench): def GetClassName(self): return "Gui::PythonWorkbench" - def _show_shortcut_recommendations(self): - """Show keyboard shortcut recommendations dialog on first activation.""" - try: - param_group = FreeCAD.ParamGet( - "User parameter:BaseApp/Preferences/Mod/KindredSilo" - ) - if param_group.GetBool("ShortcutsShown", False): - return - param_group.SetBool("ShortcutsShown", True) - - from PySide import QtGui - - msg = """

Welcome to Kindred Silo!

-

For the best experience, set up these keyboard shortcuts:

- - - - - -
Ctrl+O - Silo_Open (Search & Open)
Ctrl+N - Silo_New (Register new item)
Ctrl+S - Silo_Save (Save & upload)
Ctrl+Shift+S - Silo_Commit (Save with comment)
-

To set shortcuts: Tools > Customize > Keyboard

-

This message appears once.

""" - - dialog = QtGui.QMessageBox() - dialog.setWindowTitle("Silo Keyboard Shortcuts") - dialog.setTextFormat(QtGui.Qt.RichText) - dialog.setText(msg) - dialog.setIcon(QtGui.QMessageBox.Information) - dialog.addButton("Set Up Now", QtGui.QMessageBox.AcceptRole) - dialog.addButton("Later", QtGui.QMessageBox.RejectRole) - if dialog.exec_() == 0: - FreeCADGui.runCommand("Std_DlgCustomize", 0) - except Exception as e: - FreeCAD.Console.PrintWarning("Silo shortcuts dialog: " + str(e) + "\n") - FreeCADGui.addWorkbench(SiloWorkbench()) FreeCAD.Console.PrintMessage("Silo workbench registered\n") diff --git a/freecad/silo_commands.py b/freecad/silo_commands.py index b162c87..42c02ea 100644 --- a/freecad/silo_commands.py +++ b/freecad/silo_commands.py @@ -2316,85 +2316,6 @@ class Silo_BOM: return FreeCAD.ActiveDocument is not None -# --------------------------------------------------------------------------- -# Silo Mode toggle - swap Ctrl+O/S/N between standard and Silo commands -# --------------------------------------------------------------------------- - -# Stored original shortcuts so they can be restored on toggle-off -_original_shortcuts: Dict[str, Any] = {} - - -def _swap_shortcuts(mapping, enable_silo): - """Swap keyboard shortcuts between standard and Silo commands. - - mapping: list of (std_cmd, silo_cmd, shortcut) tuples - enable_silo: True to assign shortcuts to Silo commands, False to restore. - """ - from PySide import QtGui - - mw = FreeCADGui.getMainWindow() - if mw is None: - return - - for std_cmd, silo_cmd, shortcut in mapping: - if enable_silo: - # Save and clear the standard command's shortcut - std_action = mw.findChild(QtGui.QAction, std_cmd) - if std_action: - _original_shortcuts[std_cmd] = std_action.shortcut().toString() - std_action.setShortcut("") - # Assign the shortcut to the Silo command - silo_action = mw.findChild(QtGui.QAction, silo_cmd) - if silo_action: - silo_action.setShortcut(shortcut) - else: - # Clear the Silo command's shortcut - silo_action = mw.findChild(QtGui.QAction, silo_cmd) - if silo_action: - silo_action.setShortcut("") - # Restore the standard command's original shortcut - std_action = mw.findChild(QtGui.QAction, std_cmd) - if std_action and std_cmd in _original_shortcuts: - std_action.setShortcut(_original_shortcuts.pop(std_cmd)) - - -_SHORTCUT_MAP = [ - ("Std_Open", "Silo_Open", "Ctrl+O"), - ("Std_Save", "Silo_Save", "Ctrl+S"), - ("Std_New", "Silo_New", "Ctrl+N"), -] - - -class Silo_ToggleMode: - """Toggle between standard file operations and Silo equivalents.""" - - def GetResources(self): - return { - "MenuText": "Silo Mode", - "ToolTip": ( - "Toggle between standard file operations and Silo equivalents.\n" - "When ON: Ctrl+O/S/N use Silo Open/Save/New.\n" - "When OFF: Standard FreeCAD file operations." - ), - "Pixmap": _icon("silo"), - "Checkable": True, - } - - def Activated(self, checked): - param = FreeCAD.ParamGet(_PREF_GROUP) - if checked: - _swap_shortcuts(_SHORTCUT_MAP, enable_silo=True) - param.SetBool("SiloMode", True) - FreeCAD.Console.PrintMessage("Silo mode enabled\n") - else: - _swap_shortcuts(_SHORTCUT_MAP, enable_silo=False) - param.SetBool("SiloMode", False) - FreeCAD.Console.PrintMessage("Silo mode disabled\n") - - def IsActive(self): - return True - - # --------------------------------------------------------------------------- # SSE live-update listener # --------------------------------------------------------------------------- @@ -2947,5 +2868,5 @@ FreeCADGui.addCommand("Silo_TagProjects", Silo_TagProjects()) FreeCADGui.addCommand("Silo_Rollback", Silo_Rollback()) FreeCADGui.addCommand("Silo_SetStatus", Silo_SetStatus()) FreeCADGui.addCommand("Silo_Settings", Silo_Settings()) -FreeCADGui.addCommand("Silo_ToggleMode", Silo_ToggleMode()) + FreeCADGui.addCommand("Silo_Auth", Silo_Auth()) From 45e803402d401016c732e3f1de5ba74aa1944a72 Mon Sep 17 00:00:00 2001 From: Zoe Forbes Date: Sun, 8 Feb 2026 10:35:54 -0600 Subject: [PATCH 3/3] fix: use absolute import in silo_origin.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The freecad/ directory is not a Python package (no __init__.py) — it is added directly to sys.path by FreeCAD. The relative import 'from .silo_commands import ...' fails when silo_origin is imported as a top-level module, causing Silo origin registration to silently fail. Change to absolute import 'from silo_commands import ...' to match the import style used everywhere else in the directory. --- freecad/silo_origin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/freecad/silo_origin.py b/freecad/silo_origin.py index fd186e5..071c772 100644 --- a/freecad/silo_origin.py +++ b/freecad/silo_origin.py @@ -11,8 +11,7 @@ providing the standardized origin interface. import FreeCAD import FreeCADGui - -from .silo_commands import ( +from silo_commands import ( _client, _sync, collect_document_properties,