ocs_doc_api: fix review findings (scale/rotation/all-or-nothing/DoS/bounds/codegen)

CRITICAL:
- transform_entity_geometry: fix s^2 double-scale (axes already carry scale; drop
  the *s pre-multiply so 2D entities scale by s like solids), and handle rotation
  for direction/angle fields (apply_dir for Ray/XLine direction + Ellipse
  major_axis; offset Arc angles; compose Insert rotation).
- ensure_transformable: check layer-lock + resolve solid bodies (not just type),
  restoring TransformMany all-or-nothing; regression test with a locked layer.

WARNING:
- volume/centroid: memoize per (handle, geometry_epoch) + cap cold-cache
  tessellation budget (query-batch DoS).
- entity_to_profile_curves: fail Unsupported on polyline bulge (no silent
  chord-substituted sweep profile).
- Ellipse: validate minor_axis_ratio in (0,1] so bounds never under-bound.
- RasterImage bounds: bracket all four corners (rotated/flipped images).

SUGGESTION:
- binding_schema.json handles derived from spec families (includes ArcCurve/
  Ellipse/Spline/Ray/XLine).
- build.rs header: enums are hand-maintained (not 'GENERATED'); ops_gen/query_gen
  headers corrected to match.
- Dedupe divergence mesh_volume_centroid into the crate's geom module; host +
  both mocks delegate.

Tests: +3 regression tests (scale-once, rotation, locked-layer all-or-nothing)
-> 604 host lib green; 8 crate unit tests green.
This commit is contained in:
Sebastian committed 2026-09-06 08:36:06 +02:00
1 parent fabdeae8bf
commit c74504f30c
10 files changed
+271 -150

No files matched your search

@@ -265,7 +265,12 @@
"Line",
"Circle",
"Polyline",
"Point"
"Point",
"ArcCurve",
"Ellipse",
"Spline",
"Ray",
"XLine"
],
"root": "DocApi"
},
+5 -4
View File
@@ -1,7 +1,8 @@
// GENERATED by build.rs from spec/entities.toml — do NOT edit by hand.
// Committed snapshot for reviewability (plan §10); build.rs regenerates and CI
// fails on drift (`--check`). Append new variants at the END only (bincode
// discriminant stability, plan §7).
// HAND-MAINTAINED wire vocabulary (the Operation enum). This file is NOT derived
// from spec/entities.toml by build.rs — the enum is the canonical append-only
// wire vocabulary; the spec's `op` names must each map to a variant here
// (enforced by a test). Append new variants at the END only (bincode discriminant
// stability, plan §7). Do NOT reorder or remove variants.
use serde::{Deserialize, Serialize};
+4 -4
View File
@@ -1,7 +1,7 @@
// GENERATED by build.rs from spec/entities.toml — do NOT edit by hand.
// Committed snapshot for reviewability (plan §10); build.rs regenerates and CI
// fails on drift (`--check`). Append new variants at the END only (bincode
// discriminant stability, plan §7).
// HAND-MAINTAINED wire vocabulary (the Query enum). NOT derived from the spec by
// build.rs — the enum is the canonical append-only wire vocabulary; the spec's
// `query` names must each map to a variant here (enforced by a test). Append new
// variants at the END only (bincode discriminant stability, plan §7).
use serde::{Deserialize, Serialize};
+25
View File
@@ -13,6 +13,31 @@ fn geom_err(kind: GeometryErrorKind, msg: impl Into<String>) -> ApiError {
ApiError::geometry(kind, msg)
}
/// Signed volume and centroid of a closed triangle mesh via the divergence
/// theorem: V = Σ v0·(v1×v2)/6, C = Σ (v0+v1+v2)·tetra_vol / (4V). Single source
/// of truth shared by the host backend and the test/example mock backends.
pub fn mesh_volume_centroid(mesh: &cadkernel::brep::Mesh) -> (f64, [f64; 3]) {
let mut vol = 0.0;
let mut c = [0.0; 3];
for t in &mesh.triangles {
let (v0, v1, v2) = (mesh.positions[t[0]], mesh.positions[t[1]], mesh.positions[t[2]]);
let cross = [
v1[1] * v2[2] - v1[2] * v2[1],
v1[2] * v2[0] - v1[0] * v2[2],
v1[0] * v2[1] - v1[1] * v2[0],
];
let tet = (v0[0] * cross[0] + v0[1] * cross[1] + v0[2] * cross[2]) / 6.0;
vol += tet;
for i in 0..3 {
c[i] += (v0[i] + v1[i] + v2[i]) * tet;
}
}
if vol.abs() < 1e-12 {
return (0.0, [0.0; 3]);
}
(vol, [c[0] / (4.0 * vol), c[1] / (4.0 * vol), c[2] / (4.0 * vol)])
}
/// The XY work plane (origin at 0, X=(1,0,0), normal=(0,0,1)).
fn xy_plane() -> ApiResult<Plane> {
Plane::orthonormal([0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 0.0, 1.0])