File size: 7,646 Bytes
985c397 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | # SPDX-License-Identifier: LGPL-2.1-or-later
# ***************************************************************************
# * *
# * Copyright (c) 2025 Billy Huddleston <billy@ivdc.com> *
# * *
# * This file is part of FreeCAD. *
# * *
# * FreeCAD is free software: you can redistribute it and/or modify it *
# * under the terms of the GNU Lesser General Public License as *
# * published by the Free Software Foundation, either version 2.1 of the *
# * License, or (at your option) any later version. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, but *
# * WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
# * Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Lesser General Public *
# * License along with FreeCAD. If not, see *
# * <https://www.gnu.org/licenses/>. *
# * *
# ***************************************************************************
import FreeCAD
import Path
from typing import Dict, Any, Optional, Union
from .util import units_from_json
class ParameterAccessor:
"""
Unified accessor for dicts and FreeCAD objects for migration logic.
"""
def __init__(self, target):
self.target = target
self.is_dict = isinstance(target, dict)
def has(self, key):
if self.is_dict:
# For dicts, check in nested 'parameter' dict
param = self.target.get("parameter", {})
return key in param if isinstance(param, dict) else False
else:
return key in getattr(self.target, "PropertiesList", [])
def get(self, key):
if self.is_dict:
# For dicts, get from nested 'parameter' dict
param = self.target.get("parameter", {})
return param.get(key) if isinstance(param, dict) else None
else:
return self.target.getPropertyByName(key)
def set(self, key, value):
if self.is_dict:
# For dicts, set in nested 'parameter' dict
if "parameter" not in self.target:
self.target["parameter"] = {}
self.target["parameter"][key] = value
else:
setattr(self.target, key, value)
def add_property(self, prop_type, key, group, doc):
if self.is_dict:
# For dicts, just set the value
pass # No-op, handled by set()
else:
self.target.addProperty(prop_type, key, group, doc)
def set_editor_mode(self, key, mode):
if self.is_dict:
pass # No-op
else:
self.target.setEditorMode(key, mode)
def name(self):
if self.is_dict:
return self.target.get("name", "toolbit")
else:
return getattr(self.target, "Label", "unknown toolbit")
def get_shape_type(self):
if self.is_dict:
# For dicts, shape-type is at top level of attrs dict
return self.target.get("shape-type")
else:
# For FreeCAD objects, use ShapeType attribute
return getattr(self.target, "ShapeType", None)
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 migrate_parameters(accessor: ParameterAccessor) -> bool:
"""
Migrates legacy parameters using a unified accessor.
Currently handles:
- TorusRadius → CornerRadius
- FlatRadius/Diameter → CornerRadius
- Infers Units from parameter strings if not set
Args:
accessor: ParameterAccessor instance wrapping dict or FreeCAD object
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")
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
if has_torus and not has_corner:
value = accessor.get("TorusRadius")
accessor.add_property(
"App::PropertyLength",
"CornerRadius",
"Shape",
"Corner radius copied from TorusRadius",
)
accessor.set_editor_mode("CornerRadius", 0)
accessor.set("CornerRadius", value)
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 and not has_torus:
try:
diam_raw = accessor.get("Diameter")
flat_raw = accessor.get("FlatRadius")
diameter = FreeCAD.Units.Quantity(diam_raw)
flat_radius = FreeCAD.Units.Quantity(flat_raw)
corner_radius = (float(diameter) / 2.0) - float(flat_radius)
# Convert to correct unit
if isinstance(diam_raw, str) and diam_raw.strip().endswith("in"):
cr_in = FreeCAD.Units.Quantity(f"{corner_radius} mm").getValueAs("in")
value = f"{float(cr_in):.4f} in"
else:
if isinstance(diam_raw, str) and not diam_raw.strip().endswith("mm"):
cr_mm = FreeCAD.Units.Quantity(f"{corner_radius} in").getValueAs("mm")
value = f"{float(cr_mm):.4f} mm"
else:
value = f"{float(corner_radius):.4f} mm"
accessor.add_property(
"App::PropertyLength",
"CornerRadius",
"Shape",
"Corner radius migrated from FlatRadius/Diameter",
)
accessor.set_editor_mode("CornerRadius", 0)
accessor.set("CornerRadius", value)
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 {name}: {e}")
return migrated
|