Files
cdsl-cad/cadfs_to_cdsl/units.py
T
2026-09-02 13:51:35 +08:00

39 lines
2.0 KiB
Python

from __future__ import annotations
import ast
import operator
import re
from typing import Any
SCALE = {"mm": 1.0, "millimeter": 1.0, "millimeters": 1.0, "cm": 10.0, "m": 1000.0,
"meter": 1000.0, "meters": 1000.0, "in": 25.4, "inch": 25.4, "inches": 25.4,
"ft": 304.8, "foot": 304.8, "feet": 304.8}
_NUM_UNIT = re.compile(r"^\s*([+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?)\s*(?:\*\s*)?([A-Za-z]+)\s*$")
def _eval(node: ast.AST, names: dict[str, float]) -> float:
if isinstance(node, ast.Expression): return _eval(node.body, names)
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)): return float(node.value)
if isinstance(node, ast.Name) and node.id in names: return names[node.id]
if isinstance(node, ast.UnaryOp) and isinstance(node.op, (ast.UAdd, ast.USub)):
value = _eval(node.operand, names); return value if isinstance(node.op, ast.UAdd) else -value
if isinstance(node, ast.BinOp) and isinstance(node.op, (ast.Add, ast.Sub, ast.Mult, ast.Div)):
left, right = _eval(node.left, names), _eval(node.right, names)
return {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}[type(node.op)](left, right)
raise ValueError("expression is not a safe constant")
def length_mm(expression: Any, names: dict[str, float] | None = None) -> float:
if not isinstance(expression, str): raise ValueError("missing length expression")
text = expression.strip().replace("millimeter", "mm").replace("inch", "in")
match = _NUM_UNIT.match(text)
if match:
unit = match.group(2).lower()
if unit not in SCALE: raise ValueError(f"unsupported unit {unit!r}")
return float(match.group(1)) * SCALE[unit]
env = {"mm": 1.0, "cm": 10.0, "m": 1000.0, "inch": 25.4, "in": 25.4, **(names or {})}
try:
return _eval(ast.parse(text, mode="eval"), env)
except Exception as exc:
raise ValueError(f"unsupported length expression {expression!r}") from exc