Compare commits

...

2 Commits

Author SHA1 Message Date
65b87ef605 fix(silo): pull assembly dependencies on all open paths (#337)
SiloOrigin.openDocument() bypassed open_item() when a local file
existed, skipping dependency pulling for assemblies. Silo_Pull also
skipped dependency pulling when no revisions had files but a local
file existed.

Changes:
- SiloOrigin.openDocument(): remove local-file shortcut, route all
  opens through open_item() which already handles dependency pulling
- Silo_Pull.Activated(): add _pull_dependencies() call before opening
  existing local assembly files in the no-revisions-have-files path

Closes #337
2026-03-02 13:13:58 -06:00
cc6a79f1b1 fix(silo): pull assembly dependencies on all open paths
Add _pull_dependencies() that fetches BOM children and downloads
missing files to canonical paths before an assembly document is
opened, so PropertyXLink references resolve correctly.

- Create _pull_dependencies(): iterates BOM, fetches canonical item
  description via get_item(), downloads latest file revision for each
  missing child
- Modify open_item(): detect assembly item_type and call
  _pull_dependencies() before opening
- Silo_Open: prefer open_item() when part_number is available so
  assemblies opened from search results also pull dependencies
- Silo_Pull: pull dependencies with progress dialog after main file
  download completes, before reopening the document

Closes #337
2026-02-26 12:50:34 -06:00
2 changed files with 96 additions and 12 deletions

View File

@@ -1048,6 +1048,59 @@ def get_tracked_object(doc):
return None
def _pull_dependencies(part_number: str) -> None:
"""Download BOM children so PropertyXLink references resolve on open.
For each child in the BOM that is not already present locally, fetch
the item metadata (for canonical description) and download the latest
file revision to the canonical path.
"""
try:
bom = _client.get_bom(part_number)
except Exception as e:
FreeCAD.Console.PrintWarning(f"Could not fetch BOM for {part_number}: {e}\n")
return
if not bom:
return
for entry in bom:
child_pn = entry.get("child_part_number", "")
if not child_pn:
continue
# Check if we already have the file locally
existing = find_file_by_part_number(child_pn)
if existing and existing.exists():
continue
# Get canonical item description for the filename
try:
child_item = _client.get_item(child_pn)
child_desc = child_item.get("description", "")
except Exception:
child_desc = entry.get("child_description", "")
dest_path = get_cad_file_path(child_pn, child_desc)
dest_path.parent.mkdir(parents=True, exist_ok=True)
# Find latest revision with a file
rev = _client.latest_file_revision(child_pn)
if not rev:
FreeCAD.Console.PrintWarning(f"No file revision for dependency {child_pn}, skipping.\n")
continue
rev_num = rev["revision_number"]
try:
ok = _client._download_file(child_pn, rev_num, str(dest_path))
if ok:
FreeCAD.Console.PrintMessage(f"Pulled dependency {child_pn} rev {rev_num}\n")
else:
FreeCAD.Console.PrintWarning(f"Failed to download dependency {child_pn}\n")
except Exception as e:
FreeCAD.Console.PrintWarning(f"Error downloading dependency {child_pn}: {e}\n")
class SiloSync:
"""Handles synchronization between FreeCAD and Silo."""
@@ -1165,14 +1218,31 @@ class SiloSync:
return doc
def open_item(self, part_number: str):
"""Open or create item document."""
"""Open or create item document.
For assemblies, BOM dependencies are pulled first so that
PropertyXLink references resolve when the document loads.
"""
# Fetch item metadata to check item_type
try:
item = self.client.get_item(part_number)
except Exception as e:
FreeCAD.Console.PrintError(f"Failed to fetch item: {e}\n")
item = {}
# Pull assembly dependencies before opening so links resolve
if item.get("item_type") == "assembly":
_pull_dependencies(part_number)
existing_path = find_file_by_part_number(part_number)
if existing_path and existing_path.exists():
return FreeCAD.openDocument(str(existing_path))
if not item:
return None
try:
item = self.client.get_item(part_number)
return self.create_document_for_item(item, save=True)
except Exception as e:
FreeCAD.Console.PrintError(f"Failed to open: {e}\n")
@@ -1365,10 +1435,10 @@ class Silo_Open:
# inside the dialog's nested event loop, which can cause crashes.
data = _open_after_close[0]
if data is not None:
if data.get("path"):
FreeCAD.openDocument(data["path"])
else:
if data.get("part_number"):
_sync.open_item(data["part_number"])
elif data.get("path"):
FreeCAD.openDocument(data["path"])
def IsActive(self):
return True
@@ -1824,6 +1894,13 @@ class Silo_Pull:
if not has_any_file:
if existing_local:
FreeCAD.Console.PrintMessage(f"Opening existing local file: {existing_local}\n")
# Pull assembly dependencies before opening (#337)
try:
item = _client.get_item(part_number)
except Exception:
item = {}
if item.get("item_type") == "assembly":
_pull_dependencies(part_number)
FreeCAD.openDocument(str(existing_local))
else:
try:
@@ -1908,6 +1985,18 @@ class Silo_Pull:
FreeCAD.Console.PrintMessage(f"Pulled revision {rev_num} of {part_number}\n")
# Pull assembly dependencies before reopening
if item.get("item_type") == "assembly":
dep_progress = QtGui.QProgressDialog(
f"Pulling dependencies for {part_number}...", None, 0, 0
)
dep_progress.setWindowModality(2) # Qt.WindowModal
dep_progress.setMinimumDuration(0)
dep_progress.setValue(0)
QtGui.QApplication.processEvents()
_pull_dependencies(part_number)
dep_progress.close()
# Close existing document if open, then reopen
if doc and doc.FileName == str(dest_path):
FreeCAD.closeDocument(doc.Name)

View File

@@ -315,13 +315,8 @@ class SiloOrigin:
FreeCAD.Console.PrintError(f"Silo open failed: {e}\n")
return None
# Try to find existing local file by part number
# (UUID lookup would require API enhancement)
local_path = find_file_by_part_number(identity)
if local_path and local_path.exists():
return FreeCAD.openDocument(str(local_path))
# Download from Silo
# Route through open_item() which pulls assembly dependencies
# before opening, ensuring PropertyXLink references resolve (#337)
try:
doc = _sync.open_item(identity)
return doc