| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | import FreeCAD |
| |
|
| |
|
| | def to_json(value): |
| | """Convert a value to JSON format.""" |
| | if isinstance(value, FreeCAD.Units.Quantity): |
| | return value.UserString |
| | 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: |
| | |
| | if isinstance(value, str): |
| | value_lower = value.lower().strip() |
| |
|
| | |
| | if any(unit in value_lower for unit in ["in", "inch", '"', "thou"]): |
| | imperial_count += 1 |
| | |
| | elif any(unit in value_lower for unit in ["mm", "cm", "m "]): |
| | metric_count += 1 |
| |
|
| | |
| | if imperial_count > metric_count: |
| | return "Imperial" |
| | elif metric_count > imperial_count: |
| | return "Metric" |
| |
|
| | return "Metric" |
| |
|
| |
|
| | def format_value( |
| | value: FreeCAD.Units.Quantity | int | float | None, |
| | precision: int | None = None, |
| | units: str | None = None, |
| | ) -> str | None: |
| | """ |
| | Format a numeric value as a string, optionally appending a unit and controlling precision. |
| | |
| | This function uses the ToolBitSchema (via setToolBitSchema) to ensure that units are formatted according to the correct schema (Metric or Imperial) when a FreeCAD.Units.Quantity is provided. The schema is temporarily set for formatting and then restored. |
| | |
| | Args: |
| | value: The numeric value to format. |
| | unit: (Optional) The unit string to append (e.g., 'mm', 'in'). |
| | precision: (Optional) Number of decimal places (default: 3). |
| | |
| | Returns: |
| | str: The formatted value as a string, with unit if provided. |
| | """ |
| | if value is None: |
| | return None |
| | elif isinstance(value, FreeCAD.Units.Quantity): |
| | if precision is not None: |
| | user_val, _, user_unit = value.getUserPreferred() |
| | if user_unit in ("deg", "°", "degree", "degrees"): |
| | |
| | try: |
| | deg_val = float(str(user_val)[:-1]) |
| | except Exception: |
| | return value.getUserPreferred()[0] |
| | formatted_value = f"{deg_val:.1f}".rstrip("0").rstrip(".") |
| | return f"{formatted_value}°" |
| | |
| | setToolBitSchema(units) |
| | _value = value.getUserPreferred()[0] |
| | setToolBitSchema() |
| | return _value |
| | return value.UserString |
| | return str(value) |
| |
|
| |
|
| | def is_imperial_pitch(pitch_mm, tol=1e-6): |
| | """ |
| | Classify a pitch in mm as imperial vs metric. |
| | Rule: |
| | - If pitch_mm is ~2 decimal places clean -> metric, |
| | unless it corresponds to an exact whole-number TPI. |
| | - Otherwise, treat as imperial. |
| | """ |
| | import math |
| |
|
| | try: |
| | mm = float(pitch_mm) |
| | except Exception: |
| | return False |
| | if mm <= 0: |
| | return False |
| |
|
| | |
| | two_dec_clean = abs(mm - round(mm, 2)) <= tol |
| |
|
| | |
| | tpi = 25.4 / mm |
| | whole_tpi = round(tpi) |
| | is_whole_tpi = math.isclose(tpi, whole_tpi, abs_tol=1e-6) |
| |
|
| | if two_dec_clean and not is_whole_tpi: |
| | return False |
| | return True |
| |
|
| |
|
| | def setToolBitSchema(schema=None): |
| | """ |
| | Set the FreeCAD units schema. If passed 'Metric' or 'Imperial', set accordingly (case-insensitive). |
| | Otherwise, if a document is open, set to its schema. If no document, fallback to user preference or provided schema. |
| | """ |
| | units_schema_map = { |
| | "metric": 6, |
| | "imperial": 3, |
| | } |
| | if isinstance(schema, str) and schema.lower() in units_schema_map: |
| | FreeCAD.Units.setSchema(units_schema_map[schema.lower()]) |
| | return |
| | if FreeCAD.ActiveDocument is not None: |
| | try: |
| | doc_schema = FreeCAD.ActiveDocument.getSchema() |
| | FreeCAD.Units.setSchema(doc_schema) |
| | return |
| | except Exception: |
| | pass |
| | |
| | if schema is None: |
| | schema = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Units").GetInt( |
| | "UserSchema", 6 |
| | ) |
| | FreeCAD.Units.setSchema(schema) |
| |
|