Merge pull request #26409 from Connor9220/ToolbitsUnitsMigration

CAM: Add migration for Toolbit Units property
This commit is contained in:
sliptonic
2025-12-23 09:08:06 -06:00
committed by GitHub
3 changed files with 71 additions and 12 deletions

View File

@@ -25,6 +25,7 @@
import FreeCAD
import Path
from typing import Dict, Any, Optional, Union
from .util import units_from_json
class ParameterAccessor:
@@ -74,9 +75,9 @@ class ParameterAccessor:
else:
self.target.setEditorMode(key, mode)
def label(self):
def name(self):
if self.is_dict:
return "toolbit"
return self.target.get("name", "toolbit")
else:
return getattr(self.target, "Label", "unknown toolbit")
@@ -103,6 +104,7 @@ def migrate_parameters(accessor: ParameterAccessor) -> bool:
Currently handles:
- TorusRadius → CornerRadius
- FlatRadius/Diameter → CornerRadius
- Infers Units from parameter strings if not set
Args:
accessor: ParameterAccessor instance wrapping dict or FreeCAD object
@@ -110,13 +112,28 @@ def migrate_parameters(accessor: ParameterAccessor) -> bool:
Returns:
True if migration occurred, False otherwise
"""
migrated = False
has_torus = accessor.has("TorusRadius")
has_flat = accessor.has("FlatRadius")
has_diam = accessor.has("Diameter")
has_corner = accessor.has("CornerRadius")
label = accessor.label()
has_units = accessor.has("Units")
name = accessor.name()
shape_type = accessor.get_shape_type()
# Infer Units from parameter strings if not set
if not has_units:
# Gather all parameters to check for units
params = {}
if accessor.is_dict:
params = accessor.target.get("parameter", {})
inferred_units = units_from_json(params)
if inferred_units:
accessor.set("Units", inferred_units)
Path.Log.info(f"Adding Units as '{inferred_units}' for {name}")
migrated = True
# Only run migration logic if shape type == 'Bullnose'
if shape_type and str(shape_type).lower() == "bullnose":
# Case 1: TorusRadius exists, copy to CornerRadius
@@ -130,11 +147,11 @@ def migrate_parameters(accessor: ParameterAccessor) -> bool:
)
accessor.set_editor_mode("CornerRadius", 0)
accessor.set("CornerRadius", value)
Path.Log.info(f"Copied TorusRadius to CornerRadius={value} for {label}")
return True
Path.Log.info(f"Copied TorusRadius to CornerRadius={value} for {name}")
migrated = True
# Case 2: FlatRadius and Diameter exist, calculate CornerRadius
if has_flat and has_diam and not has_corner:
if has_flat and has_diam and not has_corner and not has_torus:
try:
diam_raw = accessor.get("Diameter")
flat_raw = accessor.get("FlatRadius")
@@ -161,10 +178,9 @@ def migrate_parameters(accessor: ParameterAccessor) -> bool:
)
accessor.set_editor_mode("CornerRadius", 0)
accessor.set("CornerRadius", value)
Path.Log.info(f"Migrated FlatRadius/Diameter to CornerRadius={value} for {label}")
return True
Path.Log.info(f"Migrated FlatRadius/Diameter to CornerRadius={value} for {name}")
migrated = True
except Exception as e:
Path.Log.error(f"Failed to migrate FlatRadius for toolbit {label}: {e}")
return False
Path.Log.error(f"Failed to migrate FlatRadius for toolbit {name}: {e}")
return False
return migrated

View File

@@ -793,7 +793,7 @@ class ToolBit(Asset, ABC):
# 3. Ensure Units property exists and is set
if not hasattr(self.obj, "Units"):
print("Adding Units property")
Path.Log.debug("Adding Units property")
self.obj.addProperty(
"App::PropertyEnumeration",
"Units",

View File

@@ -30,6 +30,49 @@ def to_json(value):
return value
def units_from_json(params):
"""
Infer Units (Metric/Imperial) from JSON parameter strings.
For JSON files from disk, values are stored as strings like "3.175 in" or "6 mm".
This function examines common dimensional parameters (Diameter, Length, CuttingEdgeHeight, etc.)
to determine if the toolbit uses metric or imperial units.
Args:
params: Dictionary of parameters from JSON (before conversion to FreeCAD.Units.Quantity)
Returns:
str: "Metric" or "Imperial", or None if units cannot be determined
"""
if not isinstance(params, dict):
return None
imperial_count = 0
metric_count = 0
for param_name in ("Diameter", "ShankDiameter", "Length", "CuttingEdgeLength"):
value = params.get(param_name)
if value is not None:
# Check if it's a string with unit suffix
if isinstance(value, str):
value_lower = value.lower().strip()
# Check for imperial units
if any(unit in value_lower for unit in ["in", "inch", '"', "thou"]):
imperial_count += 1
# Check for metric units
elif any(unit in value_lower for unit in ["mm", "cm", "m "]):
metric_count += 1
# Make a decision based on counts
if imperial_count > metric_count:
return "Imperial"
elif metric_count > imperial_count:
return "Metric"
return "Metric" # Default to Metric if uncertain
def format_value(
value: FreeCAD.Units.Quantity | int | float | None,
precision: int | None = None,