geomcore 0.37.0

A pure, standalone geometric kernel for CAD-grade curves and surfaces: parametric evaluation, transformations, and curve-on-surface parametrization.
Documentation
# Python

```sh
pip install geomcore
```

Pre-built wheels are published for Linux (x86_64, aarch64), macOS and
Windows, for Python 3.9+ (abi3: one wheel covers every version). The
package ships type stubs (PEP 561), so IDEs and type checkers see the full
API.

=== "Python"

    ```python
    from geomcore import Point, Vector
    from geomcore.curves import Circle
    import math

    circle = Circle(Point.origin(), Vector.unit_z(), 2.0)
    point = circle.eval_point(math.pi / 4)
    ```

=== "Rust"

    ```rust
    use geomcore::{Point, Vector};
    use geomcore::curves::Circle;

    let circle = Circle::new(Point::ORIGIN, Vector::Z, 2.0).unwrap();
    let point = circle.eval_point(std::f64::consts::FRAC_PI_4);
    ```

## Local properties and one-sided evaluation

Curves and surfaces answer the same differential queries as in Rust,
with the tolerance as the keyword `tol=None` (`Tolerance.default()`):

- 3D curves: `tangent(u)`, `curvature(u)` and `curvature_center(u)`,
  `is_closed()`, `continuity(u)` and `min_continuity()`.
- Surfaces: `normal(u, v)` (`Plane.normal()` takes no parameters),
  `curvatures(u, v)` returning a `SurfaceCurvatures`, `is_u_closed()` /
  `is_v_closed()`, `continuity_u(u)` / `continuity_v(v)` and their
  `min_continuity_u()` / `min_continuity_v()`, and `reversed_u()` /
  `reversed_v()` with `u_reversed_parameter(u)` /
  `v_reversed_parameter(v)` mapping a parameter onto the reversed
  surface.
- B-splines: `eval_d1_sided`, `eval_d2_sided` and
  `eval_derivative_sided`, taking a `Side` (`Side.BELOW` or `Side.ABOVE`)
  per direction, to read each side of a knot or periodic seam.

Where Rust returns `None` (no tangent at a cusp, no normal or curvatures
at a cone's apex), Python returns `None`. A continuity query returns the
order as an `int`, or `math.inf` where the shape is smooth (Rust's
`Continuity::Order(k)` and `Continuity::Smooth`), so `c >= 2` and
`min(...)` need no special case.

=== "Python"

    ```python
    import math

    from geomcore import Point, Side
    from geomcore.curves import BSplineCurve

    poles = [Point(0.0, 0.0, 0.0), Point(1.0, 0.0, 0.0), Point(1.0, 1.0, 0.0)]
    kinked = BSplineCurve(1, poles, [0.0, 1.0, 2.0], [2, 1, 2], False)
    assert kinked.continuity(1.0) == 0
    assert kinked.continuity(0.5) == math.inf
    _, incoming = kinked.eval_d1_sided(1.0, Side.BELOW)  # (1, 0, 0)
    _, outgoing = kinked.eval_d1_sided(1.0, Side.ABOVE)  # (0, 1, 0)
    ```

=== "Rust"

    ```rust
    use geomcore::{BSplineCurve, Continuity, Point, Side, Tolerance};

    let poles = vec![
        Point::new(0.0, 0.0, 0.0),
        Point::new(1.0, 0.0, 0.0),
        Point::new(1.0, 1.0, 0.0),
    ];
    let kinked = BSplineCurve::new(1, poles, vec![0.0, 1.0, 2.0], vec![2, 1, 2], false).unwrap();
    assert_eq!(kinked.continuity(1.0, Tolerance::DEFAULT), Continuity::Order(0));
    assert_eq!(kinked.continuity(0.5, Tolerance::DEFAULT), Continuity::Smooth);
    let (_, incoming) = kinked.eval_d1_sided(1.0, Side::Below); // (1, 0, 0)
    let (_, outgoing) = kinked.eval_d1_sided(1.0, Side::Above); // (0, 1, 0)
    ```

## Reprs

Reprs print floats as Python does and show coordinates below `1e-15` as
`0.0`, so rounding noise such as `cos(pi / 2)` does not clutter them. A repr is
therefore for reading, not an exact round trip of such tiny coordinates.

A Python API reference generated from docstrings, and tutorials, are on
the roadmap. Until then, the Rust items on
[docs.rs](https://docs.rs/geomcore) mirror the Python surface one-to-one.