From 0ddf511fbf15739671e42010669786b6ebc12de2 Mon Sep 17 00:00:00 2001 From: Samuel Abels Date: Wed, 28 May 2025 09:39:43 +0200 Subject: [PATCH] CAM: Fix: Custom tool parameters not showing up in property editor --- src/Mod/CAM/Path/Tool/shape/doc.py | 18 ++++++++++-- src/Mod/CAM/Path/Tool/shape/models/base.py | 33 +++++++++++----------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Mod/CAM/Path/Tool/shape/doc.py b/src/Mod/CAM/Path/Tool/shape/doc.py index 4b9d01b585..28387dc3c2 100644 --- a/src/Mod/CAM/Path/Tool/shape/doc.py +++ b/src/Mod/CAM/Path/Tool/shape/doc.py @@ -55,8 +55,20 @@ def find_shape_object(doc: "FreeCAD.Document") -> Optional["FreeCAD.DocumentObje return doc.Objects[0] if doc.Objects else None +def get_unset_value_for(attribute_type: str): + if attribute_type == "App::PropertyLength": + return FreeCAD.Units.Quantity(0) + elif attribute_type == "App::PropertyString": + return "" + elif attribute_type == "App::PropertyInteger": + return 0 + return None + + def get_object_properties( - obj: "FreeCAD.DocumentObject", expected_params: List[str] + obj: "FreeCAD.DocumentObject", + props: List[str] | None = None, + group: Optional[str] = None, ) -> Dict[str, Any]: """ Extract properties matching expected_params from a FreeCAD PropertyBag. @@ -72,7 +84,9 @@ def get_object_properties( Values are FreeCAD native types. """ properties = {} - for name in expected_params: + for name in props or obj.PropertiesList: + if group and not obj.getGroupOfProperty(name) == group: + continue if hasattr(obj, name): properties[name] = getattr(obj, name) else: diff --git a/src/Mod/CAM/Path/Tool/shape/models/base.py b/src/Mod/CAM/Path/Tool/shape/models/base.py index 9ca8b366b3..339c4f43b0 100644 --- a/src/Mod/CAM/Path/Tool/shape/models/base.py +++ b/src/Mod/CAM/Path/Tool/shape/models/base.py @@ -34,6 +34,7 @@ from ...camassets import cam_assets from ..doc import ( find_shape_object, get_object_properties, + get_unset_value_for, update_shape_object_properties, ShapeDocFromBytes, ) @@ -372,33 +373,34 @@ class ToolBitShape(Asset): props_obj = ToolBitShape._find_property_object(temp_doc) if not props_obj: raise ValueError("No 'Attributes' PropertyBag object found in document bytes") - - # Get properties from the properties object - expected_params = shape_class.get_expected_shape_parameters() - loaded_params = get_object_properties(props_obj, expected_params) - - missing_params = [ - name - for name in expected_params - if name not in loaded_params or loaded_params[name] is None - ] + loaded_params = get_object_properties(props_obj, group="Shape") # For now, we log missing parameters, but do not raise an error. # This allows for more flexible shape files that may not have all # parameters set, while still warning the user. # In the future, we may want to raise an error if critical parameters # are missing. + expected_params = shape_class.get_expected_shape_parameters() + missing_params = [ + name + for name in expected_params + if name not in loaded_params or loaded_params[name] is None + ] if missing_params: Path.Log.error( f"Validation error: Object '{props_obj.Label}' in document {id} " f"is missing parameters for {shape_class.__name__}: {', '.join(missing_params)}." f" In future releases, these shapes will not load!" ) + for param in missing_params: + param_type = shape_class.get_parameter_property_type(param) + loaded_params[param] = get_unset_value_for(param_type) # Instantiate the specific subclass with the provided ID instance = shape_class(id=id) - instance._data = data # Cache the byte content + instance._data = data # Keep the byte content instance._defaults = loaded_params + instance._params = instance._defaults | instance._params if dependencies: # dependencies is None = shallow load # Assign resolved dependencies (like the icon) to the instance @@ -415,10 +417,6 @@ class ToolBitShape(Asset): ) instance.icon = cast(ToolBitShapeIcon, dependencies.get(icon_uri)) - # Update instance parameters, prioritizing loaded defaults but not - # overwriting parameters that may already be set during __init__ - instance._params = instance._defaults | instance._params - return instance def to_bytes(self, serializer: Type[AssetSerializer]) -> bytes: @@ -584,11 +582,12 @@ class ToolBitShape(Asset): entry = self.schema().get(param_name) return entry[0] if entry else str_param_name - def get_parameter_property_type(self, param_name: str) -> str: + @classmethod + def get_parameter_property_type(cls, param_name: str) -> str: """ Get the FreeCAD property type string for a given parameter name. """ - return self.schema()[param_name][1] + return cls.schema()[param_name][1] def get_parameters(self) -> Dict[str, Any]: """