Files
cadSet/SimpleCADAPI/docs/core/physical-units.md
T

203 lines
6.5 KiB
Markdown

# Physical Units And Dimension Inference
SimpleCADAPI attaches physical meaning at `Var` declarations and infers the
dimension of every derived scalar expression. This catches invalid formulas before
they reach geometry, tolerance analysis, model export, replay, or FreeCAD
translation.
## Canonical CAD Units
Declaration values are preserved on each `Var`, but evaluation converts them to a
single CAD coordinate system:
| Dimension | Canonical unit |
| --- | --- |
| Dimensionless | `1` |
| Length | `mm` |
| Area | `mm^2` |
| Volume | `mm^3` |
| Angle | `deg` |
Degrees are canonical because existing SimpleCAD rotation and angular APIs use
degrees. Trigonometric evaluation converts to radians internally and converts
inverse-trigonometric results back to degrees.
```python
import math
import simplecadapi as scad
width = scad.var("width", 1.0, unit="in")
angle = scad.var("angle", math.pi / 2, unit="rad")
assert width.default == 1.0
assert width.evaluate() == 25.4
assert angle.evaluate() == 90.0
assert math.isclose(scad.sin(angle).evaluate(), 1.0)
```
Bindings use the variable's declaration unit. `width.evaluate({"width": 2.0})`
therefore returns `50.8` millimeters for an inch-declared variable.
## Declaring Units And Tolerances
```python
width = scad.var(
"width",
1.0,
unit="in",
tolerance=(-0.1, 0.2),
tolerance_unit="mm",
)
```
- `default` is in `unit`.
- `tolerance` is in `tolerance_unit`.
- `tolerance_unit` defaults to `unit` when a tolerance is present.
- Nominal and tolerance units may differ, but their dimensions must match.
- A `tolerance_unit` requires both `unit` and `tolerance`.
- Values must be finite and representable after canonical conversion.
`width.default` and `width.tolerance` preserve declaration-space values.
`width.canonical_default` and `width.canonical_tolerance` expose values used by
geometry and tolerance propagation.
## Built-In Units
| Dimension | Symbols |
| --- | --- |
| Dimensionless | `1`, `%` |
| Length | `mm`, `cm`, `m`, `in`, `ft` |
| Area | `mm^2`, `cm^2`, `m^2`, `in^2`, `ft^2` |
| Volume | `mm^3`, `cm^3`, `m^3`, `in^3`, `ft^3` |
| Angle | `deg`, `rad` |
Common singular/plural names are accepted by `get_unit()`, including
`millimeters`, `inches`, `feet`, `degrees`, `radians`, `square feet`, and
`cubic inches`. `ml` aliases `cm^3`.
Use constants such as `MM`, `INCH`, `DEGREE`, `RADIAN`, `LENGTH`, and `ANGLE`, or
resolve strings:
```python
assert scad.get_unit("inch") == scad.INCH
assert scad.convert_value(1.0, "in", "mm") == 25.4
assert math.isclose(scad.convert_value(180.0, "deg", "rad"), math.pi)
```
Incompatible conversion raises `UnitValidationError`.
## Dimension Algebra
`Dimension` stores integer exponents for length and angle. Named dimensions are:
- `DIMENSIONLESS = Dimension()`
- `LENGTH = Dimension(length=1)`
- `AREA = Dimension(length=2)`
- `VOLUME = Dimension(length=3)`
- `ANGLE = Dimension(angle=1)`
`infer_dimension(expression)` applies these rules:
| Operation | Rule |
| --- | --- |
| `a + b`, `a - b` | dimensions must match |
| `a * b` | add dimension exponents |
| `a / b` | subtract dimension exponents |
| `a ** n` | multiply exponents by constant integer `n` |
| `sqrt(a)` or `a ** 0.5` | every exponent must be even |
| unary `-a`, `abs(a)` | preserve dimension |
| `sin`, `cos`, `tan` | input must be Angle; result is Dimensionless |
| `asin`, `acos`, `atan` | input must be Dimensionless; result is Angle |
| `atan2(y, x)` | inputs must have the same dimension; result is Angle |
Arbitrary and varying powers are permitted for dimensionless bases. A dimensioned
base requires a constant integer exponent, except `0.5` is accepted when all base
exponents are even.
```python
width = scad.var("width", 30.0, unit="mm")
height = scad.var("height", 40.0, unit="mm")
area = width * height
diagonal = scad.sqrt(width**2 + height**2)
assert scad.infer_dimension(area) == scad.AREA
assert scad.infer_dimension(diagonal) == scad.LENGTH
assert diagonal.evaluate() == 50.0
```
## Numeric Constants
Numeric literals are dimensionless coefficients in multiplication and division.
For addition and subtraction, a literal adopts the other operand's dimension as a
contextual offset:
```python
length = scad.var("length", 10.0, unit="mm")
assert scad.infer_dimension(length * 2.0) == scad.LENGTH
assert scad.infer_dimension(length + 2.0) == scad.LENGTH
```
The literal is already expressed in the canonical result unit. `length + 2.0`
therefore means two millimeters, not two units of `length.unit`. Prefer explicit
variables when declaration-unit intent must be retained.
## Legacy Unitless Expressions
Variables without `unit` retain the previous behavior:
- `infer_dimension()` returns `None`.
- Trigonometric inputs and results use radians.
- Existing arbitrary expression and tolerance behavior remains available.
- A legacy variable cannot be mixed with a unit-declared variable in one
expression because no safe physical meaning can be inferred.
Pure numeric constant expressions infer `Dimensionless`.
## Custom Units
Custom linear-scale units use the same canonical system:
```python
thou = scad.Unit("thou", scad.LENGTH, 0.0254)
width = scad.var("width", 1000.0, unit=thou)
assert width.evaluate() == 25.4
```
Built-in units serialize as symbols. Custom units serialize a definition:
```json
{
"symbol": "thou",
"dimension": {"length": 1, "angle": 0},
"scale_to_canonical": 0.0254
}
```
Units are scale-only. Offset units such as Celsius/Fahrenheit are not represented.
## Validation Boundaries
Unit and dimension validation runs when:
1. A `Var`, `Dimension`, or `Unit` is created.
2. An expression is directly evaluated.
3. An expression is registered or imported through `ExpressionGraph`.
4. A tolerance is analyzed or a requirement is declared.
5. Session/model JSON is imported, exported, replayed, or translated.
Malformed units, cyclic graphs, duplicate expression IDs, mixed legacy/typed
variables, incompatible dimensions, invalid roots, and invalid trigonometric inputs
are rejected before graph mutation or geometry replay.
## Manufacturing Requirement Scope
Area, volume, inverse length, and other compound dimensions can be inferred and
analyzed. Manufacturing requirements created by `check_tolerance()` or
`GraphSession.require_tolerance()` currently accept final `Length` and `Angle`
results only. This prevents an area or volume variation from being presented as a
linear dimension requirement without explicit engineering semantics.
See [Dimension Tolerance Chains](dimension-tolerance-chains.md) for propagation,
RSS assumptions, enforcement boundaries, and serialized requirement fields.