`m.iter_point_cloud(per_m2, seed, unit, chunk_points)` yields
DataFrame chunks of `chunk_points` rows from a worker thread through
a bounded mpsc channel. Peak RAM is O(chunk_points) instead of
O(total_points), so 200 MB – 1 GB ARK IFCs that OOM'd the single-shot
`m.point_cloud()` now stream cleanly.
Rust panics inside the geometry pyfunctions (point_cloud, meshes,
mesh_qto, analyse_drift, iter_point_cloud) are caught at the PyO3
boundary and surfaced as `ifcfast.IfcfastError` — replaces the
uncatchable `pyo3_runtime.PanicException` that was killing worker
pools in #23's third failure mode.
- Each chunk has the same columns + `df.attrs["global_shift"]`
contract as the single-shot API; a single product whose samples
span a chunk boundary splits across consecutive chunks (guid
column still tags every row).
- `__next__` releases the GIL on recv, so Python KeyboardInterrupt
fires promptly and parallel Python work isn't blocked during
tessellation.
- Dropping the iterator flips an `Arc<AtomicBool>` stop flag; the
worker bails on the next on_product call instead of running the
whole mesh pass to discard.
- chunk_points=0 raises `IfcfastError` (rather than dividing by
zero deep in the Rust sink).
Bumps to v0.4.20 (no cache schema change; vertex / instance shapes
are unchanged).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>