From 5aadc9cd6c284cc27472a5c0da38ecc3faebc508 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 13:50:52 +0000 Subject: [PATCH] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Mod/CAM/InitGui.py | 3 +- src/Mod/CAM/Path/Preferences.py | 4 +- src/Mod/CAM/Path/Tool/assets/ui/filedialog.py | 57 +++++++------ .../CAM/Path/Tool/assets/ui/preferences.py | 2 +- src/Mod/CAM/Path/Tool/camassets.py | 1 + .../CAM/Path/Tool/library/serializers/fctl.py | 11 +-- src/Mod/CAM/Path/Tool/migration/migration.py | 80 +++++++++---------- src/Mod/CAM/Path/Tool/toolbit/ui/browser.py | 4 +- 8 files changed, 86 insertions(+), 76 deletions(-) diff --git a/src/Mod/CAM/InitGui.py b/src/Mod/CAM/InitGui.py index 38b10015f5..ec2ef11ebc 100644 --- a/src/Mod/CAM/InitGui.py +++ b/src/Mod/CAM/InitGui.py @@ -31,6 +31,7 @@ else: class Workbench: pass + FreeCAD.__unit_test__ += ["TestCAMGui"] @@ -65,7 +66,6 @@ class CAMWorkbench(Workbench): self.__class__.MenuText = "CAM" self.__class__.ToolTip = "CAM workbench" - def Initialize(self): global PathCommandGroup @@ -97,6 +97,7 @@ class CAMWorkbench(Workbench): # Check if CAM asset migration is needed for version upgrade from Path.Tool.migration.migration import CAMAssetMigrator + migrator = CAMAssetMigrator() migrator.check_migration_needed() diff --git a/src/Mod/CAM/Path/Preferences.py b/src/Mod/CAM/Path/Preferences.py index 83e475863d..d37f4a99a8 100644 --- a/src/Mod/CAM/Path/Preferences.py +++ b/src/Mod/CAM/Path/Preferences.py @@ -140,7 +140,9 @@ def getAssetPath() -> pathlib.Path: # Migrate: Set the legacy path as the new CamAssets path setAssetPath(legacy_path_obj) # Return the most recent version of the legacy path - most_recent_legacy = FreeCAD.ApplicationDirectories.mostRecentConfigFromBase(str(legacy_path_obj)) + most_recent_legacy = FreeCAD.ApplicationDirectories.mostRecentConfigFromBase( + str(legacy_path_obj) + ) return pathlib.Path(most_recent_legacy) # Fallback to default if no legacy path found diff --git a/src/Mod/CAM/Path/Tool/assets/ui/filedialog.py b/src/Mod/CAM/Path/Tool/assets/ui/filedialog.py index b2a40963ff..3bf0c5a8a8 100644 --- a/src/Mod/CAM/Path/Tool/assets/ui/filedialog.py +++ b/src/Mod/CAM/Path/Tool/assets/ui/filedialog.py @@ -75,12 +75,12 @@ class AssetOpenDialog(QFileDialog): raw_data = file_path.read_bytes() dependencies = serializer_class.extract_dependencies(raw_data) external_toolbits = [] # Track toolbits found externally - + for dependency_uri in dependencies: # 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": @@ -88,8 +88,8 @@ class AssetOpenDialog(QFileDialog): # 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/ - + 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: @@ -98,7 +98,7 @@ class AssetOpenDialog(QFileDialog): dependency_found = True external_toolbits.append((dependency_uri, toolbit_file)) break - + if not dependency_found: QMessageBox.critical( self, @@ -106,22 +106,23 @@ class AssetOpenDialog(QFileDialog): 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.", + 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 + QMessageBox.Yes, ) - + if reply == QMessageBox.Yes: # Import the external toolbits into local store self._import_external_toolbits(external_toolbits) @@ -139,13 +140,13 @@ class AssetOpenDialog(QFileDialog): # Load and return the asset. try: # Always use context-aware deserialization for libraries to get meaningful names - if hasattr(serializer_class, 'deep_deserialize_with_context'): + if hasattr(serializer_class, "deep_deserialize_with_context"): # Pass file path context for meaningful library names and external dependency resolution asset = serializer_class.deep_deserialize_with_context(raw_data, file_path) else: # Fallback to regular deserialization 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 @@ -185,10 +186,10 @@ class AssetOpenDialog(QFileDialog): """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 @@ -196,33 +197,39 @@ class AssetOpenDialog(QFileDialog): 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}") + 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_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) + 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) + message = f"Failed to import all {len(failed_imports)} toolbit(s):\n" + "\n".join( + failed_imports + ) QMessageBox.warning(self, "Import Failed", message) diff --git a/src/Mod/CAM/Path/Tool/assets/ui/preferences.py b/src/Mod/CAM/Path/Tool/assets/ui/preferences.py index dac0b8c8df..ff46b2e606 100644 --- a/src/Mod/CAM/Path/Tool/assets/ui/preferences.py +++ b/src/Mod/CAM/Path/Tool/assets/ui/preferences.py @@ -129,4 +129,4 @@ class AssetPreferencesPage: asset_path = pref.GetString(Path.Preferences.ToolPath, "") if not asset_path: asset_path = str(Path.Preferences.getDefaultAssetPath()) - self.asset_path_edit.setText(asset_path) \ No newline at end of file + self.asset_path_edit.setText(asset_path) diff --git a/src/Mod/CAM/Path/Tool/camassets.py b/src/Mod/CAM/Path/Tool/camassets.py index c3ee5c88ba..75abad2ef9 100644 --- a/src/Mod/CAM/Path/Tool/camassets.py +++ b/src/Mod/CAM/Path/Tool/camassets.py @@ -33,6 +33,7 @@ if False: 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 diff --git a/src/Mod/CAM/Path/Tool/library/serializers/fctl.py b/src/Mod/CAM/Path/Tool/library/serializers/fctl.py index e19843176b..c04ce3874b 100644 --- a/src/Mod/CAM/Path/Tool/library/serializers/fctl.py +++ b/src/Mod/CAM/Path/Tool/library/serializers/fctl.py @@ -72,7 +72,7 @@ class FCTLSerializer(AssetSerializer): # for the asset being deserialized. We should use this ID for the library # instance, overriding any 'id' that might be in the data_dict (which # is from an older version of the format). - + # For the label, prefer data_dict["label"], then "name", then fallback to using the id as filename # The id parameter often contains the filename stem when importing from files label = data_dict.get("label") or data_dict.get("name") or id or "Unnamed Library" @@ -225,12 +225,12 @@ class FCTLSerializer(AssetSerializer): 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/ - + bit_dir = tools_dir / "Bit" # e.g., /path/to/Bit/ + toolbit_loaded = False if bit_dir.exists(): possible_extensions = [".fctb", ".json", ".yaml", ".yml"] @@ -240,6 +240,7 @@ class FCTLSerializer(AssetSerializer): 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 ) @@ -258,7 +259,7 @@ class FCTLSerializer(AssetSerializer): 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" diff --git a/src/Mod/CAM/Path/Tool/migration/migration.py b/src/Mod/CAM/Path/Tool/migration/migration.py index 2dc9ecbf28..73eb441f64 100644 --- a/src/Mod/CAM/Path/Tool/migration/migration.py +++ b/src/Mod/CAM/Path/Tool/migration/migration.py @@ -44,44 +44,44 @@ if FreeCAD.GuiUp: from PySide.QtWidgets import QApplication, QMessageBox from PySide.QtCore import Qt + class CAMAssetMigrator: """ Handles migration of CAM assets during FreeCAD version upgrades. - + This class provides functionality to: - Check if migration is needed for custom CAM asset locations - Offer migration to users through a dialog - Perform the actual asset migration with versioned directories """ - + def __init__(self): self.pref_group_path = "User parameter:BaseApp/Preferences/Mod/CAM/Migration" - def check_migration_needed(self): """ Check if CAM asset migration is needed for version upgrade. - + This method determines if the current CAM assets are stored in a custom location outside the default user data directory and if migration has not been offered for the current FreeCAD version. - + Returns: bool: True if migration should be offered, False otherwise """ Path.Log.info("Starting CAM asset migration check") - + try: # Get current directories user_app_data_dir = FreeCAD.getUserAppDataDir() user_app_data_path = pathlib.Path(user_app_data_dir) Path.Log.debug(f"User app data directory: {user_app_data_dir}") - + # Get the current CAM asset path (may be naked or versioned) current_asset_path = Path.Preferences.getAssetPath() current_asset_pathlib = pathlib.Path(current_asset_path) Path.Log.debug(f"Current CAM asset path: {current_asset_path}") - + # Only migrate if CamAssets is outside the standard user data directory if current_asset_pathlib.is_relative_to(user_app_data_path): Path.Log.debug("CamAssets is in default location, no custom migration needed") @@ -95,7 +95,9 @@ class CAMAssetMigrator: # Determine the base path (naked path without version) if FreeCAD.ApplicationDirectories.isVersionedPath(str(current_asset_path)): # Check if we're already using the current version - if FreeCAD.ApplicationDirectories.usingCurrentVersionConfig(str(current_asset_path)): + if FreeCAD.ApplicationDirectories.usingCurrentVersionConfig( + str(current_asset_path) + ): Path.Log.debug("Already using current version, no migration needed") return @@ -107,13 +109,14 @@ class CAMAssetMigrator: except Exception as e: Path.Log.error(f"Error checking CAM asset migration: {e}") import traceback + Path.Log.debug(f"Full traceback: {traceback.format_exc()}") return False - + def _offer_migration_to_user(self): """ Present migration dialog to user. - + Returns: bool: True if user accepted migration, False otherwise """ @@ -121,16 +124,16 @@ class CAMAssetMigrator: major = int(FreeCAD.ConfigGet("BuildVersionMajor")) minor = int(FreeCAD.ConfigGet("BuildVersionMinor")) current_version = FreeCAD.ApplicationDirectories.versionStringForPath(major, minor) - + # Get current asset path for display current_asset_path = Path.Preferences.getAssetPath() - + Path.Log.debug(f"Offering migration to user for version {current_version}") - + if not FreeCAD.GuiUp: Path.Log.debug("GUI not available, skipping migration offer") return False - + msg = ( f"FreeCAD has been upgraded to version {current_version}.\n\n" f"Your CAM assets are stored in a custom location:\n{current_asset_path}\n\n" @@ -138,69 +141,64 @@ class CAMAssetMigrator: "to preserve them during future upgrades?\n\n" "This will copy your assets to a new directory." ) - + Path.Log.debug("Showing migration dialog to user") - + reply = QMessageBox.question( - None, - "CAM Asset Migration", - msg, - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes + None, "CAM Asset Migration", msg, QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes ) - + # Record that we offered migration for this version pref_group = FreeCAD.ParamGet(self.pref_group_path) offered_versions = pref_group.GetString("OfferedToMigrateCAMAssets", "") - known_versions = set(offered_versions.split(',')) if offered_versions else set() + known_versions = set(offered_versions.split(",")) if offered_versions else set() known_versions.add(current_version) pref_group.SetString("OfferedToMigrateCAMAssets", ",".join(known_versions)) Path.Log.debug(f"Updated offered versions: {known_versions}") - + if reply == QMessageBox.Yes: Path.Log.info("User accepted migration, starting asset migration") return True else: Path.Log.info("User declined migration") return False - + def _migrate_assets(self, source_path): """ Perform actual directory copying and preference updates. - + Args: source_path: Current CAM asset directory path """ Path.Log.info(f"Starting asset migration from {source_path}") - + try: FreeCAD.ApplicationDirectories.migrateAllPaths([source_path]) - Path.Log.info("Migration complete - preferences will be handled automatically by the system") - + Path.Log.info( + "Migration complete - preferences will be handled automatically by the system" + ) + if FreeCAD.GuiUp: QMessageBox.information( None, "Migration Complete", f"CAM assets have been migrated from:\n{source_path}\n\n" - "The system will automatically handle preference updates." + "The system will automatically handle preference updates.", ) - + except Exception as e: error_msg = f"Failed to migrate CAM assets: {e}" Path.Log.error(error_msg) import traceback + Path.Log.debug(f"Migration error traceback: {traceback.format_exc()}") if FreeCAD.GuiUp: - QMessageBox.critical( - None, - "Migration Failed", - error_msg - ) - + QMessageBox.critical(None, "Migration Failed", error_msg) + def has_migration_been_offered(self): """ Check if migration has been offered for current version. - + Returns: bool: True if migration was offered for this version """ @@ -211,5 +209,5 @@ class CAMAssetMigrator: current_version_string = FreeCAD.ApplicationDirectories.versionStringForPath(major, minor) offered_versions = pref_group.GetString("OfferedToMigrateCAMAssets", "") - known_versions = set(offered_versions.split(',')) if offered_versions else set() - return current_version_string in known_versions \ No newline at end of file + known_versions = set(offered_versions.split(",")) if offered_versions else set() + return current_version_string in known_versions diff --git a/src/Mod/CAM/Path/Tool/toolbit/ui/browser.py b/src/Mod/CAM/Path/Tool/toolbit/ui/browser.py index 2c5ea7f33f..a3b52c3757 100644 --- a/src/Mod/CAM/Path/Tool/toolbit/ui/browser.py +++ b/src/Mod/CAM/Path/Tool/toolbit/ui/browser.py @@ -277,8 +277,8 @@ class ToolBitBrowserWidget(QtGui.QWidget): 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." + "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: