203 lines
6.5 KiB
Markdown
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.
|