| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | import json |
| | import pathlib |
| | from typing import Optional, Union, Sequence |
| | import Path |
| | from Path import Preferences |
| | from Path.Preferences import addToolPreferenceObserver |
| | from .assets import AssetManager, AssetUri, Asset, FileStore |
| | from .toolbit.migration import ParameterAccessor, migrate_parameters |
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | def ensure_library_assets_initialized(asset_manager: AssetManager, store_name: str = "local"): |
| | """ |
| | Ensures the given store is initialized with built-in library |
| | if it is currently empty. |
| | """ |
| | builtin_library_path = Preferences.getBuiltinLibraryPath() |
| |
|
| | if asset_manager.is_empty("toolbitlibrary", store=store_name): |
| | for path in builtin_library_path.glob("*.fctl"): |
| | asset_manager.add_file("toolbitlibrary", path) |
| |
|
| |
|
| | def ensure_toolbits_have_shape_type(asset_manager: AssetManager, store_name: str = "local"): |
| | from .shape import ToolBitShape |
| |
|
| | toolbit_uris = asset_manager.list_assets( |
| | asset_type="toolbit", |
| | store=store_name, |
| | ) |
| |
|
| | for uri in toolbit_uris: |
| | data = asset_manager.get_raw(uri, store=store_name) |
| | attrs = json.loads(data) |
| | changed = False |
| |
|
| | |
| | if "shape-type" not in attrs: |
| | shape_id = pathlib.Path( |
| | str(attrs.get("shape", "")) |
| | ).stem |
| | if not shape_id: |
| | Path.Log.error(f"ToolBit {uri} missing shape ID") |
| | continue |
| |
|
| | try: |
| | shape_class = ToolBitShape.get_shape_class_from_id(shape_id) |
| | except Exception as e: |
| | Path.Log.error(f"Failed to load toolbit {uri}: {e}. Skipping") |
| | continue |
| | if not shape_class: |
| | Path.Log.error(f"Toolbit {uri} has no shape-type attribute, and failed to infer it") |
| | continue |
| | attrs["shape-type"] = shape_class.name |
| | Path.Log.info( |
| | f"Migrating toolbit {uri}: Adding shape-type attribute '{shape_class.name}'" |
| | ) |
| | changed = True |
| |
|
| | |
| | if "parameter" in attrs and isinstance(attrs["parameter"], dict): |
| | if migrate_parameters(ParameterAccessor(attrs)): |
| | changed = True |
| |
|
| | |
| | if changed: |
| | data = json.dumps(attrs, sort_keys=True, indent=2).encode("utf-8") |
| | asset_manager.add_raw("toolbit", uri.asset_id, data, store=store_name) |
| |
|
| |
|
| | def ensure_toolbit_assets_initialized(asset_manager: AssetManager, store_name: str = "local"): |
| | """ |
| | Ensures the given store is initialized with built-in bits |
| | if it is currently empty. |
| | """ |
| | builtin_toolbit_path = Preferences.getBuiltinToolBitPath() |
| |
|
| | if asset_manager.is_empty("toolbit", store=store_name): |
| | for path in builtin_toolbit_path.glob("*.fctb"): |
| | asset_manager.add_file("toolbit", path) |
| |
|
| | ensure_toolbits_have_shape_type(asset_manager, store_name) |
| |
|
| |
|
| | def ensure_toolbitshape_assets_present(asset_manager: AssetManager, store_name: str = "local"): |
| | """ |
| | Ensures the given store is initialized with built-in shapes |
| | if it is currently empty. This copies all built-in shapes, |
| | which is generally not recommended, but is useful for |
| | testing. |
| | |
| | In practice, the built-in tools don't need to be copied, |
| | because the CamAssetManager will automatically fall back to |
| | fetching them from the builtin store if they are not |
| | present in the local store (=the user's Shape directory). |
| | """ |
| | builtin_shape_path = Preferences.getBuiltinShapePath() |
| |
|
| | if asset_manager.is_empty("toolbitshape", store=store_name): |
| | for path in builtin_shape_path.glob("*.fcstd"): |
| | uri = AssetUri.build( |
| | asset_type="toolbitshape", |
| | asset_id=path.stem, |
| | ) |
| | if not asset_manager.exists(uri, store=store_name): |
| | asset_manager.add_file("toolbitshape", path) |
| |
|
| | for path in builtin_shape_path.glob("*.svg"): |
| | uri = AssetUri.build( |
| | asset_type="toolbitshapesvg", |
| | asset_id=path.stem + ".svg", |
| | ) |
| | if not asset_manager.exists(uri, store=store_name): |
| | asset_manager.add_file("toolbitshapesvg", path, asset_id=path.stem + ".svg") |
| |
|
| | for path in builtin_shape_path.glob("*.png"): |
| | uri = AssetUri.build( |
| | asset_type="toolbitshapepng", |
| | asset_id=path.stem + ".png", |
| | ) |
| | if not asset_manager.exists(uri, store=store_name): |
| | asset_manager.add_file("toolbitshapepng", path, asset_id=path.stem + ".png") |
| |
|
| |
|
| | def ensure_toolbitshape_assets_initialized(asset_manager: AssetManager, store_name: str = "local"): |
| | """ |
| | Ensures the toolbitshape directory structure exists without adding any files. |
| | """ |
| | from pathlib import Path |
| |
|
| | |
| | shape_path = Preferences.getAssetPath() / "Tools" / "Shape" |
| | shape_path.mkdir(parents=True, exist_ok=True) |
| |
|
| |
|
| | def ensure_assets_initialized(asset_manager: AssetManager, store="local"): |
| | """ |
| | Ensures the given store is initialized with built-in assets. |
| | """ |
| | ensure_library_assets_initialized(asset_manager, store) |
| | ensure_toolbit_assets_initialized(asset_manager, store) |
| | ensure_toolbitshape_assets_initialized(asset_manager, store) |
| |
|
| |
|
| | def _on_asset_path_changed(group, key, value): |
| | Path.Log.info(f"CAM asset directory changed in preferences: {group} {key} {value}") |
| | user_asset_store.set_dir(value) |
| | ensure_assets_initialized(cam_assets) |
| |
|
| |
|
| | |
| | asset_mapping = { |
| | "toolbitlibrary": "Tools/Library/{asset_id}.fctl", |
| | "toolbit": "Tools/Bit/{asset_id}.fctb", |
| | "toolbitshape": "Tools/Shape/{asset_id}.fcstd", |
| | "toolbitshapesvg": "Tools/Shape/{asset_id}", |
| | "toolbitshapepng": "Tools/Shape/{asset_id}", |
| | "machine": "Machine/{asset_id}.fcm", |
| | } |
| |
|
| | |
| | builtin_asset_mapping = { |
| | "toolbitlibrary": "Library/{asset_id}.fctl", |
| | "toolbit": "Bit/{asset_id}.fctb", |
| | "toolbitshape": "Shape/{asset_id}.fcstd", |
| | "toolbitshapesvg": "Shape/{asset_id}", |
| | "toolbitshapepng": "Shape/{asset_id}", |
| | "machine": "Machine/{asset_id}.fcm", |
| | } |
| |
|
| | user_asset_store = FileStore( |
| | name="local", |
| | base_dir=Preferences.getAssetPath(), |
| | mapping=asset_mapping, |
| | ) |
| |
|
| | builtin_asset_store = FileStore( |
| | name="builtin", |
| | base_dir=Preferences.getBuiltinAssetPath(), |
| | mapping=builtin_asset_mapping, |
| | ) |
| |
|
| |
|
| | class CamAssetManager(AssetManager): |
| | """ |
| | Custom CAM Asset Manager that extends the base AssetManager, such |
| | that the get methods return fallbacks: if the asset is not present |
| | in the "local" store, then it falls back to the builtin-asset store. |
| | """ |
| |
|
| | def __init__(self): |
| | super().__init__() |
| | self.register_store(user_asset_store) |
| | self.register_store(builtin_asset_store) |
| |
|
| | def setup(self): |
| | try: |
| | ensure_assets_initialized(cam_assets) |
| | except Exception as e: |
| | Path.Log.error(f"Failed to initialize CAM assets in {user_asset_store._base_dir}: {e}") |
| | else: |
| | Path.Log.debug(f"Using CAM assets in {user_asset_store._base_dir}") |
| |
|
| | def get( |
| | self, |
| | uri: Union[AssetUri, str], |
| | store: Union[str, Sequence[str]] = ("local", "builtin"), |
| | depth: Optional[int] = None, |
| | ) -> Asset: |
| | """ |
| | Gets an asset from the "local" store, falling back to the "builtin" |
| | store if not found locally. |
| | """ |
| | return super().get(uri, store=store, depth=depth) |
| |
|
| | def get_or_none( |
| | self, |
| | uri: Union[AssetUri, str], |
| | store: Union[str, Sequence[str]] = ("local", "builtin"), |
| | depth: Optional[int] = None, |
| | ) -> Optional[Asset]: |
| | """ |
| | Gets an asset from the "local" store, falling back to the "builtin" |
| | store if not found locally. |
| | """ |
| | return super().get_or_none(uri, store=store, depth=depth) |
| |
|
| |
|
| | |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| | cam_assets = CamAssetManager() |
| | addToolPreferenceObserver(_on_asset_path_changed) |
| |
|