feat: integrate SimpleCADAPI 2.0.2 CAD workflows
This commit is contained in:
@@ -0,0 +1,202 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user