diff --git a/cad_core/units.py b/cad_core/units.py new file mode 100644 index 0000000..74a0566 --- /dev/null +++ b/cad_core/units.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from decimal import ROUND_HALF_UP, Decimal + +MM_PER_INCH = 25.4 + +# Global default tolerance for geometric comparisons +EPS: float = 1e-9 + + +def mm_to_inch(mm: float) -> float: + return mm / MM_PER_INCH + + +def inch_to_mm(inch: float) -> float: + return inch * MM_PER_INCH + + +def almost_equal(a: float, b: float, tol: float = EPS) -> bool: + return abs(a - b) <= tol + + +def clamp(x: float, lo: float, hi: float) -> float: + if lo > hi: + lo, hi = hi, lo + return hi if x > hi else lo if x < lo else x + + +def sgn(x: float, tol: float = EPS) -> int: + if x > tol: + return 1 + if x < -tol: + return -1 + return 0 + + +def round_tol(value: float, tol: float = 1e-6) -> float: + if tol <= 0: + return value + # Try decimal quantization for decimal-friendly tolerances to avoid FP drift + try: + dval = Decimal(str(value)) + dtol = Decimal(str(tol)) + q = (dval / dtol).to_integral_value(rounding=ROUND_HALF_UP) + return float(q * dtol) + except Exception: + return round(value / tol) * tol diff --git a/tasks/chore-cad-core-units-tolerances-tests.md b/tasks/chore-cad-core-units-tolerances-tests.md new file mode 100644 index 0000000..bab04f2 --- /dev/null +++ b/tasks/chore-cad-core-units-tolerances-tests.md @@ -0,0 +1,17 @@ +Task: cad_core – Units tolerances tests + +Scope +- Add focused tests for `cad_core/units.py`: `EPS`, `almost_equal`, `clamp`, `sgn`, `round_tol`. + +Details +- New file: `tests/cad_core/test_units_tolerances.py` with small, isolated cases. +- Ensure no dependency on UI or other modules; pure functions only. +- Keep tolerance behavior consistent across core geometry. + +Acceptance +- `pytest -q` passes; new tests cover edge cases (negative, swapped bounds, zero tol). +- `ruff` and `black --check` pass. + +Branch +- `chore/cad-core-units-tolerances-tests` + diff --git a/tasks/pr/chore-cad-core-units-tolerances-tests.md b/tasks/pr/chore-cad-core-units-tolerances-tests.md new file mode 100644 index 0000000..dc2ff3d --- /dev/null +++ b/tasks/pr/chore-cad-core-units-tolerances-tests.md @@ -0,0 +1,29 @@ +Title: chore(cad_core): add units tolerances tests + +Summary +- Adds focused tests for `cad_core/units.py` covering `EPS`, `almost_equal`, `clamp`, `sgn`, and `round_tol`. +- Introduces `cad_core/units.py` helpers with improved `round_tol` using Decimal to avoid FP drift. + +Changes +- New: `tests/cad_core/test_units_tolerances.py` +- New: `cad_core/units.py` +- Task: `tasks/chore-cad-core-units-tolerances-tests.md` + +Rationale +- Establish consistent tolerance behavior used across core geometry. +- Provide small, pure tests to keep regressions visible and fast. + +Test Plan (agents pulling this) +- From repo root: + - `python -m pip install -e .` (or set `PYTHONPATH` to repo root) + - `ruff check cad_core tests/cad_core/test_units_tolerances.py` + - `black --check cad_core tests/cad_core/test_units_tolerances.py` + - `pytest -q tests/cad_core/test_units_tolerances.py` + +Notes +- No UI impact; pure functions only. +- Keep `EPS` and helpers as the single source of tolerance logic across CAD core. + +Refs +- Issue: N/A (please update if applicable) + diff --git a/tests/cad_core/test_units_tolerances.py b/tests/cad_core/test_units_tolerances.py new file mode 100644 index 0000000..22cdfe3 --- /dev/null +++ b/tests/cad_core/test_units_tolerances.py @@ -0,0 +1,37 @@ +from cad_core.units import EPS, almost_equal, clamp, round_tol, sgn + + +def test_eps_value_and_usage(): + assert isinstance(EPS, float) + assert EPS == 1e-9 + # Values within EPS are considered equal + assert almost_equal(1.0, 1.0 + EPS * 0.5) + + +def test_almost_equal_with_custom_tol(): + assert almost_equal(0.0, 1e-6, tol=1e-5) + assert not almost_equal(0.0, 2e-5, tol=1e-5) + + +def test_clamp_basic_and_swapped_bounds(): + assert clamp(5.0, 0.0, 10.0) == 5.0 + assert clamp(-1.0, 0.0, 10.0) == 0.0 + assert clamp(11.0, 0.0, 10.0) == 10.0 + # Swapped bounds should be handled + assert clamp(5.0, 10.0, 0.0) == 5.0 + + +def test_sgn_with_tolerance(): + assert sgn(1.0) == 1 + assert sgn(-1.0) == -1 + assert sgn(EPS * 0.5) == 0 + assert sgn(-EPS * 0.5) == 0 + + +def test_round_tol_behaviour(): + assert round_tol(1.2345, 0.01) == 1.23 + assert round_tol(-1.234, 0.1) == -1.2 + # Non-positive tolerance returns the input as-is + x = 3.14159 + assert round_tol(x, 0.0) == x + assert round_tol(x, -0.1) == x