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