Files
silo/pkg/freecad/InitGui.py
Forbes 1478514b13 feat(origin): implement SiloOrigin adapter for unified origin system
Implements Issue #11: Silo origin adapter

This commit creates the SiloOrigin class that implements the FileOrigin
interface introduced in Issue #9, enabling Silo to be used as a document
origin in the unified file origin system.

## SiloOrigin Class (silo_origin.py)

New Python module providing the FileOrigin implementation for Silo PLM:

### Identity Methods
- id(): Returns 'silo' as unique identifier
- name(): Returns 'Kindred Silo' for UI display
- nickname(): Returns 'Silo' for compact UI elements
- icon(): Returns 'silo' icon name
- type(): Returns OriginType.PLM (1)

### Workflow Characteristics
- tracksExternally(): True - Silo tracks documents in database
- requiresAuthentication(): True - Silo requires login

### Capabilities
- supportsRevisions(): True
- supportsBOM(): True
- supportsPartNumbers(): True
- supportsAssemblies(): True

### Connection State
- connectionState(): Checks auth status and API connectivity
- connect(): Triggers Silo_Auth dialog if needed
- disconnect(): Calls _client.logout()

### Document Identity (UUID-based tracking)
- documentIdentity(): Returns SiloItemId (UUID) as primary identity
- documentDisplayId(): Returns SiloPartNumber for human display
- ownsDocument(): True if document has SiloItemId or SiloPartNumber

### Core Operations (delegate to existing commands)
- newDocument(): Delegates to Silo_New command
- openDocument(): Uses find_file_by_part_number or _sync.open_item
- saveDocument(): Saves locally + uploads via _client._upload_file
- saveDocumentAs(): Triggers migration workflow for local docs

### Extended Operations
- commitDocument(): Delegates to Silo_Commit
- pullDocument(): Delegates to Silo_Pull
- pushDocument(): Delegates to Silo_Push
- showInfo(): Delegates to Silo_Info
- showBOM(): Delegates to Silo_BOM

### Module Functions
- get_silo_origin(): Returns singleton instance
- register_silo_origin(): Registers with FreeCADGui.addOrigin()
- unregister_silo_origin(): Cleanup function

## UUID Tracking (silo_commands.py)

Added SiloItemId property to all locations where Silo properties are set:

1. create_document_for_item() - Assembly objects (line 1115)
2. create_document_for_item() - Fallback Part objects (line 1131)
3. create_document_for_item() - Part objects (line 1145)
4. Silo_New.Activated() - Tagged existing objects (line 1471)

The SiloItemId stores the database UUID (Item.ID) which is immutable,
while SiloPartNumber remains the human-readable identifier that could
theoretically change.

Property structure on tracked objects:
- SiloItemId: UUID from database (primary tracking key)
- SiloPartNumber: Human-readable part number
- SiloRevision: Current revision number
- SiloItemType: 'part' or 'assembly'

## Workbench Integration (InitGui.py)

SiloOrigin is automatically registered when the Silo workbench
initializes:

    def Initialize(self):
        import silo_commands
        try:
            import silo_origin
            silo_origin.register_silo_origin()
        except Exception as e:
            FreeCAD.Console.PrintWarning(...)

This makes Silo available as a file origin via:
- FreeCADGui.listOrigins() -> includes 'silo'
- FreeCADGui.getOrigin('silo') -> returns origin info dict
- FreeCADGui.setActiveOrigin('silo') -> sets Silo as active

## Design Decisions

1. **Delegation Pattern**: SiloOrigin delegates to existing Silo
   commands rather than reimplementing logic, ensuring consistency
   and easier maintenance.

2. **UUID as Primary Identity**: documentIdentity() returns UUID
   (SiloItemId) for immutable tracking, while documentDisplayId()
   returns part number for user display.

3. **Graceful Fallback**: If SiloItemId is not present (legacy docs),
   falls back to SiloPartNumber for identity/ownership checks.

4. **Exception Handling**: All operations wrapped in try/except to
   prevent origin system failures from breaking FreeCAD.

Refs: #11
2026-02-05 13:29:45 -06:00

103 lines
3.5 KiB
Python

"""Kindred Silo Workbench - Item database integration for Kindred Create."""
import os
import FreeCAD
import FreeCADGui
FreeCAD.Console.PrintMessage("Kindred Silo InitGui.py loading...\n")
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):
# Resolve icon relative to this file so it works regardless of install location
icon_path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), "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")
self.toolbar_commands = [
"Silo_ToggleMode",
"Separator",
"Silo_Open",
"Silo_New",
"Silo_Save",
"Silo_Commit",
"Silo_Pull",
"Silo_Push",
"Silo_Info",
"Silo_BOM",
"Silo_Settings",
"Silo_Auth",
]
self.appendToolbar("Silo", self.toolbar_commands)
self.appendMenu("Silo", self.toolbar_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
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 = """<h3>Welcome to Kindred Silo!</h3>
<p>For the best experience, set up these keyboard shortcuts:</p>
<table style="margin: 10px 0;">
<tr><td><b>Ctrl+O</b></td><td> - </td><td>Silo_Open (Search & Open)</td></tr>
<tr><td><b>Ctrl+N</b></td><td> - </td><td>Silo_New (Register new item)</td></tr>
<tr><td><b>Ctrl+S</b></td><td> - </td><td>Silo_Save (Save & upload)</td></tr>
<tr><td><b>Ctrl+Shift+S</b></td><td> - </td><td>Silo_Commit (Save with comment)</td></tr>
</table>
<p><b>To set shortcuts:</b> Tools > Customize > Keyboard</p>
<p style="color: #888;">This message appears once.</p>"""
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")