Compare commits

...

2 Commits

Author SHA1 Message Date
forbes
f6222a5181 chore: add .mailmap to normalize git identity 2026-03-03 14:17:26 -06:00
forbes-0023
9187622239 chore: add root package.xml, Init.py, and InitGui.py (#373)
Move package.xml from freecad/ to mod root following the standard
addon layout used by sdk, gears, datums, and solver.

Add root Init.py (sys.path setup for silo-client) and InitGui.py
that consolidates all Silo GUI initialization:
- SiloWorkbench class and registration
- Silo overlay context registration
- Document observer for .kc tree building
- Auth and activity dock panels via kindred_sdk
- First-start settings check
- Start page override
- kindred:// URL handler

This decouples Silo initialization from src/Mod/Create/InitGui.py,
which previously held five Silo-specific deferred setup functions.
2026-03-03 08:19:42 -06:00
4 changed files with 310 additions and 0 deletions

7
.mailmap Normal file
View File

@@ -0,0 +1,7 @@
forbes <contact@kindred-systems.com> forbes <joseph.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> forbes <zoe.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> forbes-0023 <joseph.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> forbes-0023 <zoe.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> josephforbes23 <joseph.forbes@kindred-systems.com>
forbes <contact@kindred-systems.com> Zoe Forbes <forbes@copernicus-9.kindred.internal>
forbes <contact@kindred-systems.com> admin <admin@kindred-systems.com>

13
Init.py Normal file
View File

@@ -0,0 +1,13 @@
"""Silo addon — console initialization.
Adds the shared silo-client package to sys.path so that
``import silo_client`` works from silo_commands.py and other modules.
"""
import os
import sys
_mod_dir = os.path.dirname(os.path.abspath(__file__))
_client_dir = os.path.join(_mod_dir, "silo-client")
if os.path.isdir(_client_dir) and _client_dir not in sys.path:
sys.path.insert(0, _client_dir)

265
InitGui.py Normal file
View File

@@ -0,0 +1,265 @@
"""Kindred Silo addon — GUI initialization.
Registers the SiloWorkbench, Silo file origin, overlay context,
dock panels (auth + activity), document observer, and start page override.
"""
import os
import FreeCAD
import FreeCADGui
FreeCAD.Console.PrintMessage("Kindred Silo InitGui.py loading...\n")
# ---------------------------------------------------------------------------
# Workbench
# ---------------------------------------------------------------------------
class SiloWorkbench(FreeCADGui.Workbench):
"""Kindred Silo workbench for item database integration."""
MenuText = "Kindred Silo"
ToolTip = "Item database and part management for Kindred Create"
Icon = ""
def __init__(self):
icon_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"freecad",
"resources",
"icons",
"silo.svg",
)
if os.path.exists(icon_path):
self.__class__.Icon = icon_path
def Initialize(self):
"""Called when workbench is first activated."""
import silo_commands
# Register Silo as a file origin in the unified origin system
try:
import silo_origin
silo_origin.register_silo_origin()
except Exception as e:
FreeCAD.Console.PrintWarning(f"Could not register Silo origin: {e}\n")
# Silo origin toolbar — shown as an overlay on any context when the
# active document is Silo-tracked. Registered as Unavailable so
# EditingContextResolver controls visibility via the overlay system.
self.silo_toolbar_commands = [
"Silo_Commit",
"Silo_Pull",
"Silo_Push",
"Separator",
"Silo_Info",
"Silo_BOM",
"Silo_Jobs",
]
self.appendToolbar("Silo Origin", self.silo_toolbar_commands, "Unavailable")
# Silo menu provides admin/management commands.
self.menu_commands = [
"Silo_Info",
"Silo_BOM",
"Silo_Jobs",
"Silo_TagProjects",
"Silo_SetStatus",
"Silo_Rollback",
"Silo_SaveAsTemplate",
"Separator",
"Silo_Settings",
"Silo_Auth",
"Silo_Runners",
"Silo_StartPanel",
"Silo_Diag",
]
self.appendMenu("Silo", self.menu_commands)
def Activated(self):
"""Called when workbench is activated."""
FreeCAD.Console.PrintMessage("Kindred Silo workbench activated\n")
FreeCADGui.runCommand("Silo_StartPanel", 0)
def Deactivated(self):
pass
def GetClassName(self):
return "Gui::PythonWorkbench"
FreeCADGui.addWorkbench(SiloWorkbench())
FreeCAD.Console.PrintMessage("Silo workbench registered\n")
# ---------------------------------------------------------------------------
# Silo overlay context — adds "Silo Origin" toolbar to any active context
# when the current document is Silo-tracked.
# ---------------------------------------------------------------------------
def _register_silo_overlay():
"""Register the Silo overlay after the Silo workbench has initialised."""
def _silo_overlay_match():
"""Return True if the active document is Silo-tracked."""
try:
doc = FreeCAD.ActiveDocument
if not doc:
return False
from silo_origin import get_silo_origin
origin = get_silo_origin()
return origin.ownsDocument(doc)
except Exception:
return False
try:
from kindred_sdk import register_overlay
register_overlay(
"silo", # overlay id
["Silo Origin"], # toolbar names to append
_silo_overlay_match, # match function
)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Silo overlay registration failed: {e}\n")
# ---------------------------------------------------------------------------
# Document observer — builds the Silo metadata tree when .kc files open.
# ---------------------------------------------------------------------------
def _register_silo_document_observer():
"""Register the Silo document observer for .kc tree building."""
try:
import silo_document
silo_document.register()
except Exception as e:
FreeCAD.Console.PrintLog(f"Silo: document observer registration skipped: {e}\n")
# ---------------------------------------------------------------------------
# Dock panels — auth and activity widgets via SDK
# ---------------------------------------------------------------------------
def _setup_silo_auth_panel():
"""Dock the Silo authentication panel in the right-hand side panel."""
try:
from kindred_sdk import register_dock_panel
def _factory():
import silo_commands
auth = silo_commands.SiloAuthDockWidget()
# Prevent GC of the auth timer by stashing on the widget
auth.widget._auth = auth
return auth.widget
register_dock_panel("SiloDatabaseAuth", "Database Auth", _factory)
except Exception as e:
FreeCAD.Console.PrintLog(f"Silo: auth panel skipped: {e}\n")
def _setup_silo_activity_panel():
"""Show a dock widget with recent Silo database activity."""
try:
from kindred_sdk import register_dock_panel
def _factory():
from PySide import QtWidgets
widget = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout(widget)
activity_list = QtWidgets.QListWidget()
layout.addWidget(activity_list)
try:
import silo_commands
items = silo_commands._client.list_items()
if isinstance(items, list):
for item in items[:20]:
pn = item.get("part_number", "")
desc = item.get("description", "")
updated = item.get("updated_at", "")
if updated:
updated = updated[:10]
activity_list.addItem(f"{pn} - {desc} - {updated}")
if activity_list.count() == 0:
activity_list.addItem("(No items in database)")
except Exception:
activity_list.addItem("(Unable to connect to Silo database)")
return widget
register_dock_panel("SiloDatabaseActivity", "Database Activity", _factory)
except Exception as e:
FreeCAD.Console.PrintLog(f"Silo: activity panel skipped: {e}\n")
# ---------------------------------------------------------------------------
# First-start check
# ---------------------------------------------------------------------------
def _check_silo_first_start():
"""Show Silo settings dialog on first startup if not yet configured."""
try:
param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/KindredSilo")
if not param.GetBool("FirstStartChecked", False):
param.SetBool("FirstStartChecked", True)
if not param.GetString("ApiUrl", ""):
FreeCADGui.runCommand("Silo_Settings")
except Exception as e:
FreeCAD.Console.PrintLog(f"Silo: first-start check skipped: {e}\n")
# ---------------------------------------------------------------------------
# Start page override — must happen before the C++ StartLauncher fires
# at ~100ms after GUI init.
# ---------------------------------------------------------------------------
try:
import silo_start
silo_start.register()
except Exception as e:
FreeCAD.Console.PrintWarning(f"Silo Start page override failed: {e}\n")
# ---------------------------------------------------------------------------
# Handle kindred:// URLs passed as command-line arguments on cold start.
# ---------------------------------------------------------------------------
def _handle_startup_urls():
"""Process any kindred:// URLs passed as command-line arguments."""
import sys
from silo_commands import handle_kindred_url
for arg in sys.argv[1:]:
if arg.startswith("kindred://"):
handle_kindred_url(arg)
# ---------------------------------------------------------------------------
# Deferred setup — staggered timers for non-blocking startup
# ---------------------------------------------------------------------------
from PySide import QtCore as _QtCore
_QtCore.QTimer.singleShot(500, _handle_startup_urls)
_QtCore.QTimer.singleShot(600, _register_silo_document_observer)
_QtCore.QTimer.singleShot(2000, _setup_silo_auth_panel)
_QtCore.QTimer.singleShot(2500, _register_silo_overlay)
_QtCore.QTimer.singleShot(3000, _check_silo_first_start)
_QtCore.QTimer.singleShot(4000, _setup_silo_activity_panel)

25
package.xml Normal file
View File

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
<name>silo</name>
<description>PLM workbench for Kindred Create</description>
<version>0.1.0</version>
<maintainer email="development@kindred-systems.com">Kindred Systems</maintainer>
<license file="LICENSE">MIT</license>
<url type="repository">https://git.kindred-systems.com/kindred/silo-mod</url>
<content>
<workbench>
<classname>SiloWorkbench</classname>
<subdirectory>freecad</subdirectory>
</workbench>
</content>
<kindred>
<min_create_version>0.1.0</min_create_version>
<load_priority>60</load_priority>
<pure_python>true</pure_python>
<dependencies>
<dependency>sdk</dependency>
</dependencies>
</kindred>
</package>