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
This commit is contained in:
@@ -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
|
||||
@@ -1908,6 +1978,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)
|
||||
|
||||
Reference in New Issue
Block a user