|
|
|
|
@@ -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)
|
|
|
|
|
|