library and bit import improvements
This commit is contained in:
@@ -74,21 +74,79 @@ class AssetOpenDialog(QFileDialog):
|
||||
try:
|
||||
raw_data = file_path.read_bytes()
|
||||
dependencies = serializer_class.extract_dependencies(raw_data)
|
||||
external_toolbits = [] # Track toolbits found externally
|
||||
|
||||
for dependency_uri in dependencies:
|
||||
if not self.asset_manager.exists(dependency_uri, store=["local", "builtin"]):
|
||||
# First check if dependency exists in asset manager stores
|
||||
if self.asset_manager.exists(dependency_uri, store=["local", "builtin"]):
|
||||
continue
|
||||
|
||||
# If not in stores, check if it exists relative to the library file
|
||||
dependency_found = False
|
||||
if dependency_uri.asset_type == "toolbit":
|
||||
# Look for toolbit files in parallel Bit directory
|
||||
# Library is in Library/, toolbits are in parallel Bit/
|
||||
library_dir = file_path.parent # e.g., /path/to/Library/
|
||||
tools_dir = library_dir.parent # e.g., /path/to/
|
||||
bit_dir = tools_dir / "Bit" # e.g., /path/to/Bit/
|
||||
|
||||
if bit_dir.exists():
|
||||
possible_extensions = [".fctb", ".json", ".yaml", ".yml"]
|
||||
for ext in possible_extensions:
|
||||
toolbit_file = bit_dir / f"{dependency_uri.asset_id}{ext}"
|
||||
if toolbit_file.exists():
|
||||
dependency_found = True
|
||||
external_toolbits.append((dependency_uri, toolbit_file))
|
||||
break
|
||||
|
||||
if not dependency_found:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
f"Failed to import {file_path}: required dependency {dependency_uri} not found",
|
||||
f"Failed to import {file_path}: required dependency {dependency_uri} not found in stores or in parallel Bit directory",
|
||||
)
|
||||
return None
|
||||
|
||||
# If we found external toolbits, ask user if they want to import them
|
||||
if external_toolbits:
|
||||
from PySide.QtGui import QMessageBox
|
||||
toolbit_names = [uri.asset_id for uri, _ in external_toolbits]
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"Import External Toolbits",
|
||||
f"This library references {len(external_toolbits)} toolbit(s) that are not in your local store:\n\n" +
|
||||
"\n".join(f"• {name}" for name in toolbit_names) +
|
||||
f"\n\nWould you like to import these toolbits into your local store?\n" +
|
||||
"This will make them permanently available for use in other libraries.",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.Yes
|
||||
)
|
||||
|
||||
if reply == QMessageBox.Yes:
|
||||
# Import the external toolbits into local store
|
||||
self._import_external_toolbits(external_toolbits)
|
||||
# After importing, use regular deserialization since toolbits are now in local store
|
||||
use_context_deserialize = False
|
||||
else:
|
||||
# User declined import, use context-aware deserialization for external loading
|
||||
use_context_deserialize = True
|
||||
else:
|
||||
# No external toolbits found, use regular deserialization
|
||||
use_context_deserialize = False
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Error", f"{file_path}: Failed to check dependencies: {e}")
|
||||
return None
|
||||
|
||||
# Load and return the asset.
|
||||
try:
|
||||
asset = serializer_class.deep_deserialize(raw_data)
|
||||
# Choose deserialization method based on whether toolbits were imported
|
||||
if use_context_deserialize and hasattr(serializer_class, 'deep_deserialize_with_context'):
|
||||
# Pass file path context for external dependency resolution
|
||||
asset = serializer_class.deep_deserialize_with_context(raw_data, file_path)
|
||||
else:
|
||||
# Use regular deserialization - toolbits should be in stores now
|
||||
asset = serializer_class.deep_deserialize(raw_data)
|
||||
|
||||
if not isinstance(asset, self.asset_class):
|
||||
raise TypeError(f"Deserialized asset is not of type {self.asset_class.__name__}")
|
||||
return asset
|
||||
@@ -124,6 +182,51 @@ class AssetOpenDialog(QFileDialog):
|
||||
# Fallback to home directory if anything goes wrong
|
||||
return pathlib.Path.home()
|
||||
|
||||
def _import_external_toolbits(self, external_toolbits):
|
||||
"""Import external toolbits into the local asset store."""
|
||||
from ...toolbit.serializers import all_serializers as toolbit_serializers
|
||||
from .util import get_serializer_from_extension
|
||||
|
||||
imported_count = 0
|
||||
failed_imports = []
|
||||
|
||||
for dependency_uri, toolbit_file in external_toolbits:
|
||||
try:
|
||||
# Find appropriate serializer for the file
|
||||
file_extension = toolbit_file.suffix.lower()
|
||||
serializer_class = get_serializer_from_extension(
|
||||
toolbit_serializers, file_extension, for_import=True
|
||||
)
|
||||
|
||||
if not serializer_class:
|
||||
failed_imports.append(f"{dependency_uri.asset_id}: No serializer for {file_extension}")
|
||||
continue
|
||||
|
||||
# Load and deserialize the toolbit
|
||||
raw_toolbit_data = toolbit_file.read_bytes()
|
||||
toolbit = serializer_class.deep_deserialize(raw_toolbit_data)
|
||||
|
||||
# Ensure the toolbit ID matches what the library expects
|
||||
if toolbit.id != dependency_uri.asset_id:
|
||||
toolbit.id = dependency_uri.asset_id
|
||||
|
||||
# Import the toolbit into local store
|
||||
imported_uri = self.asset_manager.add(toolbit, store="local")
|
||||
imported_count += 1
|
||||
|
||||
except Exception as e:
|
||||
failed_imports.append(f"{dependency_uri.asset_id}: {str(e)}")
|
||||
|
||||
# Show results to user
|
||||
if imported_count > 0:
|
||||
message = f"Successfully imported {imported_count} toolbit(s) into your local store."
|
||||
if failed_imports:
|
||||
message += f"\n\nFailed to import {len(failed_imports)} toolbit(s):\n" + "\n".join(failed_imports)
|
||||
QMessageBox.information(self, "Import Results", message)
|
||||
elif failed_imports:
|
||||
message = f"Failed to import all {len(failed_imports)} toolbit(s):\n" + "\n".join(failed_imports)
|
||||
QMessageBox.warning(self, "Import Failed", message)
|
||||
|
||||
|
||||
class AssetSaveDialog(QFileDialog):
|
||||
def __init__(
|
||||
|
||||
@@ -181,3 +181,88 @@ class FCTLSerializer(AssetSerializer):
|
||||
|
||||
# Now deserialize with the resolved dependencies
|
||||
return cls.deserialize(data, library_id, resolved_dependencies)
|
||||
|
||||
@classmethod
|
||||
def deep_deserialize_with_context(cls, data: bytes, file_path: "pathlib.Path"):
|
||||
"""Deep deserialize a library with file path context for external dependencies."""
|
||||
import uuid
|
||||
import pathlib
|
||||
from ...camassets import cam_assets
|
||||
from ...toolbit.serializers import all_serializers as toolbit_serializers
|
||||
|
||||
# Generate a unique ID for this library instance
|
||||
library_id = str(uuid.uuid4())
|
||||
|
||||
Path.Log.info(
|
||||
f"FCTL DEEP_DESERIALIZE_WITH_CONTEXT: Starting deep deserialization for library from {file_path}"
|
||||
)
|
||||
|
||||
# Extract dependency URIs from the library data
|
||||
dependency_uris = cls.extract_dependencies(data)
|
||||
Path.Log.info(
|
||||
f"FCTL DEEP_DESERIALIZE_WITH_CONTEXT: Found {len(dependency_uris)} toolbit dependencies: {[uri.asset_id for uri in dependency_uris]}"
|
||||
)
|
||||
|
||||
# Fetch all toolbit dependencies
|
||||
resolved_dependencies = {}
|
||||
for dep_uri in dependency_uris:
|
||||
try:
|
||||
# First try to get from asset manager stores
|
||||
Path.Log.info(
|
||||
f"FCTL EXTERNAL: Trying to fetch toolbit '{dep_uri.asset_id}' from stores ['local', 'builtin']"
|
||||
)
|
||||
toolbit = cam_assets.get(dep_uri, store=["local", "builtin"], depth=0)
|
||||
resolved_dependencies[dep_uri] = toolbit
|
||||
Path.Log.info(
|
||||
f"FCTL EXTERNAL: Successfully fetched toolbit '{dep_uri.asset_id}' from stores"
|
||||
)
|
||||
except Exception as e:
|
||||
# If not in stores, try to load from parallel Bit directory
|
||||
Path.Log.info(
|
||||
f"FCTL EXTERNAL: Toolbit '{dep_uri.asset_id}' not in stores, trying external file: {e}"
|
||||
)
|
||||
|
||||
# Look for toolbit files in parallel Bit directory
|
||||
library_dir = file_path.parent # e.g., /path/to/Library/
|
||||
tools_dir = library_dir.parent # e.g., /path/to/
|
||||
bit_dir = tools_dir / "Bit" # e.g., /path/to/Bit/
|
||||
|
||||
toolbit_loaded = False
|
||||
if bit_dir.exists():
|
||||
possible_extensions = [".fctb", ".json", ".yaml", ".yml"]
|
||||
for ext in possible_extensions:
|
||||
toolbit_file = bit_dir / f"{dep_uri.asset_id}{ext}"
|
||||
if toolbit_file.exists():
|
||||
try:
|
||||
# Find appropriate serializer for the file
|
||||
from ...assets.ui.util import get_serializer_from_extension
|
||||
serializer_class = get_serializer_from_extension(
|
||||
toolbit_serializers, ext, for_import=True
|
||||
)
|
||||
if serializer_class:
|
||||
# Load and deserialize the toolbit
|
||||
raw_toolbit_data = toolbit_file.read_bytes()
|
||||
toolbit = serializer_class.deep_deserialize(raw_toolbit_data)
|
||||
resolved_dependencies[dep_uri] = toolbit
|
||||
toolbit_loaded = True
|
||||
Path.Log.info(
|
||||
f"FCTL EXTERNAL: Successfully loaded toolbit '{dep_uri.asset_id}' from {toolbit_file}"
|
||||
)
|
||||
break
|
||||
except Exception as load_error:
|
||||
Path.Log.warning(
|
||||
f"FCTL EXTERNAL: Failed to load toolbit from {toolbit_file}: {load_error}"
|
||||
)
|
||||
continue
|
||||
|
||||
if not toolbit_loaded:
|
||||
Path.Log.warning(
|
||||
f"FCTL EXTERNAL: Could not load toolbit '{dep_uri.asset_id}' from external files"
|
||||
)
|
||||
|
||||
Path.Log.info(
|
||||
f"FCTL EXTERNAL: Resolved {len(resolved_dependencies)} of {len(dependency_uris)} dependencies"
|
||||
)
|
||||
|
||||
# Now deserialize with the resolved dependencies
|
||||
return cls.deserialize(data, library_id, resolved_dependencies)
|
||||
|
||||
@@ -267,9 +267,27 @@ class ToolBitBrowserWidget(QtGui.QWidget):
|
||||
uri_string = item.data(ToolBitUriRole)
|
||||
if not uri_string:
|
||||
return
|
||||
toolbit = self._asset_manager.get(AssetUri(uri_string))
|
||||
if toolbit:
|
||||
self.itemDoubleClicked.emit(toolbit)
|
||||
try:
|
||||
toolbit = self._asset_manager.get(AssetUri(uri_string))
|
||||
if toolbit:
|
||||
self.itemDoubleClicked.emit(toolbit)
|
||||
except FileNotFoundError:
|
||||
# Handle missing/placeholder toolbits gracefully
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
FreeCAD.Qt.translate("CAM", "Missing Toolbit"),
|
||||
FreeCAD.Qt.translate(
|
||||
"CAM",
|
||||
"This toolbit is missing from your local store. It may be a placeholder for a toolbit that was not found during library import."
|
||||
),
|
||||
)
|
||||
except Exception as e:
|
||||
# Handle other errors
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
FreeCAD.Qt.translate("CAM", "Error"),
|
||||
FreeCAD.Qt.translate("CAM", f"Failed to load toolbit: {e}"),
|
||||
)
|
||||
|
||||
def _on_item_selection_changed(self):
|
||||
"""Emits toolSelected signal and tracks selected URIs."""
|
||||
|
||||
Reference in New Issue
Block a user