1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
//! Deterministic physical measurement of a faceted stone from its plane
//! arrangement.
//!
//! [`super::meet_solver`]'s corpus work established that wrong mast
//! configurations are *self-consistent*: every tier of a wrong solve is still
//! vertex-incident, so nothing internal to the arrangement separates right from
//! wrong. The proportions printed on a real diagram (`Vol/W^3`, `L/W`, `C/W`,
//! `P/W`, `H/W`) are **external** constraints: a candidate configuration either
//! reproduces them or it does not. [`measure_solid`] computes those same figures
//! from a plane arrangement -- deterministically, in `f64`, with no convex-hull
//! library. (Since 2026-09-28 [`super::brep`] no longer uses one either: it is
//! built on this module's own [`vertices`] arrangement walk. The meet solver
//! still must not depend on `brep`.)
//!
//! The mechanism: enumerate the solid's vertices as feasible triple
//! intersections of the planes (the same primitive `meet_solver` uses), then
//! reconstruct each facet's polygon by collecting the vertices on its plane and
//! ordering them by angle. Volume comes from the divergence theorem
//! (`V = (1/3) * sum over faces of offset * area`, exact for outward-oriented
//! planes), heights from vertex `y` extents against the girdle band, and
//! width/length from the girdle outline's `x`/`z` extents (both axis-aligned
//! and rotating-caliper are computed; corpus measurement settled that the
//! printed figures use the axis convention -- see [`ExternalProportions`]).
//!
//! Split into: [`types`] (the reported data shapes), [`vertices`] (plane-
//! arrangement vertex enumeration and dedup, shared by both entry points),
//! [`measure`] ([`measure_solid`] itself), [`mesh`] ([`build_solid_mesh`] and
//! face reconstruction), [`concave`] (the tool-carved mesh behind
//! [`build_solid_mesh_geom`]), [`caliper`] (the rotating-caliper cross-check), and
//! [`proportions`] ([`StoneProportions`], the cutter-facing readout).
pub use ;
pub use ToolBounds;
pub use ;
pub use ;
pub use StoneProportions;
pub use ;
// Shared with `geometry::brep`, which reconstructs its B-rep from the same
// deterministic arrangement walk (see that module's docs).
pub use max_abs_offset;
pub use ;
use GIRDLE_NORMAL_Y_EPSILON;
// One definition of the blank box half-extent (a solid that reaches it is
// unbounded -- a schedule missing its closing planes -- which [`measure_solid`]
// reports as `None`), the feasibility slack (a vertex may poke this far beyond a
// plane and still count as part of the solid) and the minimum triple
// determinant, shared with the meet solver's own candidate enumeration.
pub use ;
/// A vertex within this absolute distance of a plane counts as lying on its
/// face. Sized to cover [`EPS_FEAS`] feasibility drift and `VERTEX_DEDUP`
/// merging; the resulting polygon-corner error is quadratically small in it.
const EPS_FACE: f64 = 2e-5;
/// Two candidate vertices within this distance (per axis) are one vertex.
const VERTEX_DEDUP: f64 = 1e-6;
/// `|normal.y|` at or below this means a vertical (girdle) plane.
///
/// Purpose: classify `f32`-derived plane normals widened to `f64`, hence the
/// `1e-3` noise floor; `meet_solver::classify_blocks` works on exact `f64`
/// schedule angles (`1e-6`) and `meet_solver::names` picks its girdle tier by an
/// 85 degree naming heuristic, so the three thresholds answer different questions.
///
/// Shares [`super::girdle::GIRDLE_NORMAL_Y_EPSILON`] rather than defining its own
/// separate threshold -- see that constant's doc comment for why a second,
/// independently-tuned value here would be wrong: this module's planes carry the
/// same f32-normalize rounding noise `girdle::is_girdle_plane`'s planes do, not the
/// exact-angle precision `meet_solver::classify_blocks` operates on, so a threshold
/// tuned for that exact-angle case could disagree with this one and classify a facet
/// as girdle in one module but not the other.
const GIRDLE_NY: f64 = GIRDLE_NORMAL_Y_EPSILON as f64;
/// `normal.y` at or above this counts as a flat table facet: an outward
/// normal within this of straight up (`+Y`) -- the horizontal-plane
/// counterpart to [`GIRDLE_NY`]'s vertical-plane threshold.
const TABLE_NY: f64 = 1.0 - 1e-6;
/// Rounds a design's representative absolute scale (the largest `|m|`
/// among its planes) to the nearest power of two. [`measure_solid`] and
/// [`build_solid_mesh`] divide every plane offset by this before running any
/// vertex-arrangement geometry, and multiply their length/volume results back
/// by it afterward -- [`EPS_FEAS`], [`EPS_FACE`], [`VERTEX_DEDUP`] and
/// `BLANK_HALF_EXTENT` are all absolute constants tuned for masts of order 1,
/// so a design authored at a very different absolute scale (see
/// [`super::meet_solver`]'s sibling fix in `SolveContext::new`, same
/// mechanism, same reasoning) would otherwise make them too tight or too
/// loose. Non-finite or non-positive input (an empty plane list, or every
/// offset exactly `0.0`) falls back to `1.0`, i.e. no normalisation.
/// `scale == 1.0` (or already an exact power of two) makes this `1.0` exactly,
/// so every division/multiplication by it is a bit-exact no-op.
///
/// Computed from the IEEE exponent and mantissa (`scale = m * 2^k`, `m` in
/// `[1, 2)`; the result is `2^k` when `m < sqrt(2)`, else `2^(k+1)`), with no
/// platform `log2`/`powi`. The exponent is clamped to the normal range.
pub