mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
Merge pull request #1395 from brett19/wire/trim-srf-cache-face-tree
wire: surface.trim_srf_cache — face-owned tree build (blend.hcr own scene; stacked on #1390)
This commit is contained in:
@@ -7,11 +7,16 @@
|
||||
//! vector or a view placement, seeds silhouette edges (the silhouette branch of
|
||||
//! `ComputeSilhouettes`' edge loop) and back-facing edges, and runs one member. It
|
||||
//! exports the scene B-rep (`GetSceneBrep()`, the copy transformed by the unported
|
||||
//! `SmBrep` copy constructor and `Transform`) with its `CalculateBoundingBox` (unit
|
||||
//! `query.brep`, unported); the Rust side imports that scene record by record (the
|
||||
//! `topology.vertexuse` importer), builds its HCR with [`Hcr::from_scene`], seeds the same
|
||||
//! edges and runs the same member. Both sides first build the scene (shim op 8) and check
|
||||
//! the seeds and op arguments against it with the same `ffi::validate`.
|
||||
//! `SmBrep` copy constructor and `Transform`) with its `CalculateBoundingBox`. The Rust
|
||||
//! side makes everything itself, in the shim's call order, so no C++ state reaches it:
|
||||
//! the B-rep block built by the Rust kernel ([`crate::own_builder`], `.smb` files by the
|
||||
//! Rust reader), the extra wire edge (`create_wire_edge_from_curve`), the HCR from
|
||||
//! [`Hcr::new`] / [`Hcr::with_transform`] (its own scene copy, transform, infinite
|
||||
//! region and bounding box), the scene renumbered into the shim's traversal order
|
||||
//! ([`crate::own_builder::renumber_with`], so edge and vertex selectors name the same
|
||||
//! entities), then the same seeds and member. The C++ side checks the seeds and op
|
||||
//! arguments against its scene with `ffi::validate` (shim op 8); the Rust side makes the
|
||||
//! same checks against its own scene ([`validate_rust`]).
|
||||
//!
|
||||
//! Compared: the construction state (transforms, projection box, ray offset tolerance,
|
||||
//! view vector, options), `IsSilhouetteEdge` / `IsBackFacingEdge`, the intervals and
|
||||
@@ -31,26 +36,21 @@
|
||||
//! generated.
|
||||
//!
|
||||
//! Status `-2` marks an upstream path that is undefined (UB-170, null topology): the
|
||||
//! shim does not run it and the port returns `InvalidInput`. Ops whose edge curves (or,
|
||||
//! for op 5, face surfaces) the importer cannot hold exactly (an `SmEllipse` edge curve
|
||||
//! is imported as its B-spline part) report `inexact_geometry` on both sides, decided
|
||||
//! from the same export.
|
||||
//! shim does not run it and the port returns `InvalidInput`. The Rust scene holds its
|
||||
//! geometry natively, so no case is skipped as inexact.
|
||||
//!
|
||||
//! The importer leaves the surfaces' owners unset; the runner sets each face surface's
|
||||
//! owner to its face, as the upstream B-rep build does, so `SmBrep::GetSurfaces`
|
||||
//! (`IsPointVisible`) selects the same surfaces on both sides.
|
||||
//! The renumbered scene's geometry is owned under the kernel's arena identities (and the
|
||||
//! B-rep records that naming), so `SmBrep::GetSurfaces` (`IsPointVisible`) finds each face
|
||||
//! surface's owner, as upstream's copy constructor sets it.
|
||||
//!
|
||||
//! `ComputeSilhouettes` and `ComputeGlobalVisibility` are not run: their C++ path reaches
|
||||
//! UB-170 on open silhouette chains.
|
||||
|
||||
use super::topology_vertexuse::{
|
||||
gen_brep, import, import_raw, path_of, push_curve, push_surface, sel, BrepIn, CrvIn,
|
||||
};
|
||||
use crate::{Case, CaseContext, HarnessError, Output, Rng, UnitDiff};
|
||||
use super::topology_vertexuse::{gen_brep, path_of, push_curve, push_surface, sel, BrepIn, CrvIn};
|
||||
use crate::{own_reader, Case, CaseContext, HarnessError, Output, Rng, UnitDiff};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{json, Value};
|
||||
use solidcore::blend::hcr::{new_curve_classification, view_transform_for_vector, Hcr, HcrObject, HcrOptions};
|
||||
use solidcore::curve::curve::p2::{BSPLINE_CURVE_TYPE, LINE_TYPE};
|
||||
use solidcore::blend::hcr::{new_curve_classification, Hcr, HcrObject, HcrOptions};
|
||||
use solidcore::curve::curve::Curve;
|
||||
use solidcore::geom::axis2_placement::Axis2Placement;
|
||||
use solidcore::geom::extent1d::Extent1d;
|
||||
@@ -59,9 +59,11 @@ use solidcore::intersect::curve_class::p1::CurveClassification;
|
||||
use solidcore::intersect::point_class::PointClassification;
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::math::Vec3;
|
||||
use solidcore::topology::brep_cache::{arena_identity, CacheObject};
|
||||
use solidcore::topology::{edge, Brep, EdgeId, FaceId, VertexId};
|
||||
use solidcore::SolidError;
|
||||
use solidcore::geom::attribute::ObjectKey;
|
||||
use solidcore::geom::object::ObjectRef;
|
||||
use solidcore::topology::brep::p2::ArenaNotifyState;
|
||||
use solidcore::topology::{edge, EdgeId, VertexId};
|
||||
use solidcore::{EntityRef, SolidError};
|
||||
use solidcore_ffi::units::{blend_hcr as ffi, topology_vertexuse as fixture};
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
@@ -235,23 +237,29 @@ fn ops_block(r: &Resolved) -> (u8, Vec<f64>) {
|
||||
(op, v)
|
||||
}
|
||||
|
||||
/// What both sides compute before running: the scene (shim op 8), the resolved
|
||||
/// selectors and the op block, validated against the scene.
|
||||
/// What the C++ side computes before running: the scene (shim op 8), the resolved
|
||||
/// selectors and the op block, validated against that scene.
|
||||
struct Prepared {
|
||||
path: String,
|
||||
brep: Vec<f64>,
|
||||
hcr: Vec<f64>,
|
||||
scene: Vec<f64>,
|
||||
r: Resolved,
|
||||
op: u8,
|
||||
ops: Vec<f64>,
|
||||
}
|
||||
|
||||
/// [`Prepared`], or the label of a rejection both sides report identically.
|
||||
/// [`Prepared`], or the label of a rejection.
|
||||
fn prepare(i: &Inputs) -> Result<Result<Prepared, String>, HarnessError> {
|
||||
let path = path_of(&i.brep)?;
|
||||
let brep = brep_block(&i.brep)?;
|
||||
let hcr = hcr_block(i)?;
|
||||
// a failed build of the B-rep block (the shim's `build` returns NULL), reported with
|
||||
// its status as the Rust side reports its own failed build
|
||||
if let Ok(ex) = fixture::eval(fixture::Op::Export, &path, &brep) {
|
||||
if ex.first() == Some(&-1.0) {
|
||||
let status = ex.get(1).copied().unwrap_or(0.0);
|
||||
return Ok(Err(format!("brep_build_status_{status}")));
|
||||
}
|
||||
}
|
||||
let scene = match ffi::scene(&path, &brep, &hcr) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return Ok(Err(format!("rejected: {e}"))),
|
||||
@@ -269,27 +277,225 @@ fn prepare(i: &Inputs) -> Result<Result<Prepared, String>, HarnessError> {
|
||||
path,
|
||||
brep,
|
||||
hcr,
|
||||
scene,
|
||||
r,
|
||||
op,
|
||||
ops,
|
||||
}))
|
||||
}
|
||||
|
||||
fn exact_curve(types: &[f64], e: i64) -> bool {
|
||||
types
|
||||
.get(e as usize)
|
||||
.is_some_and(|&t| t == BSPLINE_CURVE_TYPE as f64 || t == LINE_TYPE as f64)
|
||||
// ---- the Rust side's own scene and checks ---------------------------------------------------
|
||||
|
||||
/// The label of an input rejection (`ffi::scene` / `ffi::validate`'s `bad`).
|
||||
fn bad(msg: &str) -> String {
|
||||
format!("rejected: invalid argument: blend.hcr: {msg}")
|
||||
}
|
||||
|
||||
/// Whether the op depends on geometry the import does not hold exactly.
|
||||
fn inexact(r: &Resolved, types: &[f64], exact_faces: &[bool]) -> bool {
|
||||
let curves = |e: i64| !exact_curve(types, e) || r.sil.iter().any(|&s| !exact_curve(types, s));
|
||||
match r.op {
|
||||
OpIn::Intersect { edge, .. } | OpIn::CurveVisibility { edge, .. } => curves(edge),
|
||||
OpIn::EdgeVisibility { edge, .. } => curves(edge) || exact_faces.iter().any(|x| !x),
|
||||
_ => false,
|
||||
/// `ffi`'s `MAX_SCENE_MAGNITUDE` check on the inputs (the `±f64::MAX` markers aside).
|
||||
fn check_magnitudes(v: &[f64]) -> Result<(), String> {
|
||||
if v.iter().any(|x| {
|
||||
let a = x.abs();
|
||||
a > ffi::MAX_SCENE_MAGNITUDE && a < f64::MAX
|
||||
}) {
|
||||
return Err(bad("scene value above 1e15 (grid walk does not end)"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `ffi`'s view checks on the inputs (UB-169: a view vector `SmVector3d::Unitize`
|
||||
/// cannot unitize; the placement, tolerances and flags are typed inputs here).
|
||||
fn check_view(i: &Inputs) -> Result<(), String> {
|
||||
if i.placement.is_none() {
|
||||
let [x, y, z] = i.view;
|
||||
let l = x * x + y * y + z * z;
|
||||
if !(l >= 1.0e-24 && l.is_finite()) {
|
||||
return Err(bad("UB-169: view vector cannot be unitized"));
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// `ffi::validate`'s checks of the seeds and op arguments (`ops`, as [`ops_block`]
|
||||
/// makes them) against the Rust scene `h` (its edge and vertex counts and which edges
|
||||
/// have a curve).
|
||||
fn validate_rust(op: u8, h: &Hcr, ops: &[f64]) -> Result<(), String> {
|
||||
if op == 7 {
|
||||
check_magnitudes(ops)?;
|
||||
}
|
||||
let b = h.scene_brep();
|
||||
let (n_e, n_v) = (b.edges().len(), b.vertices().len());
|
||||
let mut at = 0usize;
|
||||
let mut num = || -> Result<f64, String> {
|
||||
let x = *ops.get(at).ok_or_else(|| bad("short input"))?;
|
||||
at += 1;
|
||||
if !x.is_finite() {
|
||||
return Err(bad("non-finite number"));
|
||||
}
|
||||
Ok(x)
|
||||
};
|
||||
let count = |x: f64| -> Result<usize, String> {
|
||||
if x.fract() != 0.0 || !(0.0..=64.0).contains(&x) {
|
||||
return Err(bad("count out of range"));
|
||||
}
|
||||
Ok(x as usize)
|
||||
};
|
||||
let index = |x: f64, n: usize, allow_none: bool| -> Result<i64, String> {
|
||||
if x.fract() != 0.0 || x < if allow_none { -1.0 } else { 0.0 } || x >= n as f64 {
|
||||
return Err(bad("index out of range"));
|
||||
}
|
||||
Ok(x as i64)
|
||||
};
|
||||
let flag = |x: f64| -> Result<(), String> {
|
||||
if x != 0.0 && x != 1.0 {
|
||||
return Err(bad("flag"));
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
let n_sil = count(num()?)?;
|
||||
for _ in 0..n_sil {
|
||||
let e = index(num()?, n_e, false)?;
|
||||
let has_curve = b.edge(eid(e)).is_ok_and(|x| x.curve.is_some());
|
||||
if !has_curve {
|
||||
return Err(bad("seeded silhouette edge without curve"));
|
||||
}
|
||||
}
|
||||
let n_back = count(num()?)?;
|
||||
for _ in 0..n_back {
|
||||
index(num()?, n_e, false)?;
|
||||
}
|
||||
match op {
|
||||
1 | 2 => {
|
||||
index(num()?, n_e, false)?;
|
||||
}
|
||||
3 | 4 => {
|
||||
index(num()?, n_e, false)?;
|
||||
let (fa, fb) = (num()?, num()?);
|
||||
if !(0.0..=1.0).contains(&fa) || !(0.0..=1.0).contains(&fb) {
|
||||
return Err(bad("interval fractions"));
|
||||
}
|
||||
for _ in 0..2 {
|
||||
let h = num()?;
|
||||
if !(-9.223_372_036_854_775_808e18..9.223_372_036_854_775_808e18).contains(&h) {
|
||||
return Err(bad("hint"));
|
||||
}
|
||||
}
|
||||
}
|
||||
5 => {
|
||||
index(num()?, n_e, false)?;
|
||||
index(num()?, n_v, true)?;
|
||||
flag(num()?)?;
|
||||
flag(num()?)?;
|
||||
}
|
||||
7 => {
|
||||
for _ in 0..3 {
|
||||
num()?;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
if at != ops.len() {
|
||||
return Err(bad("trailing op input"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The identity of an original B-rep object the scene copy notifies under: the
|
||||
/// notification naming in a key namespace of its own (as the sweep's HCR scene copy).
|
||||
fn source_key(e: EntityRef) -> ObjectRef {
|
||||
let r = own_reader::read_key_of(e);
|
||||
ObjectRef {
|
||||
type_code: r.type_code,
|
||||
key: ObjectKey(r.key.0 | 1 << 50),
|
||||
}
|
||||
}
|
||||
|
||||
/// The Rust side's HCR, as the shim makes it: the B-rep block built by the Rust kernel,
|
||||
/// the extra wire edge, the HCR constructor (scene copy, transform, infinite region,
|
||||
/// bounding box), the scene renumbered into the shim's `walk` order; or the output of a
|
||||
/// rejected or failed build.
|
||||
fn rust_hcr(k: &mut Kernel, i: &Inputs) -> Result<Result<Hcr, Output>, HarnessError> {
|
||||
use crate::own_builder::{build_as, failed_output, renumber_with, Built, Conventions};
|
||||
let conv = &Conventions::EDGEUSE_GRAPH;
|
||||
// `ffi::scene`'s input checks
|
||||
let (brep, hcr) = (brep_block(&i.brep)?, hcr_block(i)?);
|
||||
if let Err(m) = check_magnitudes(&brep).and_then(|()| check_magnitudes(&hcr)) {
|
||||
return Ok(Err(Output::err(m)));
|
||||
}
|
||||
// the fixture export's block checks (`fixture::eval`; pure Rust, no shim call)
|
||||
if let Err(e) = fixture::validate_block(&path_of(&i.brep)?, &brep) {
|
||||
return Ok(Err(Output::err(format!("rejected: {e}"))));
|
||||
}
|
||||
if let Err(m) = check_view(i) {
|
||||
return Ok(Err(Output::err(m)));
|
||||
}
|
||||
// the shim's `build` (`sc_topology_fixture.h`, no validate-only, no gap walk)
|
||||
let built = match &i.brep {
|
||||
BrepIn::File { rebuild, .. } => {
|
||||
own_reader::file_in_order(k, &path_of(&i.brep)?, *rebuild, false, false, conv)?
|
||||
}
|
||||
src => build_as(k, src, false, false, conv)?
|
||||
.ok_or_else(|| harness("own builder: no build for the source"))?,
|
||||
};
|
||||
let mut b = match built {
|
||||
Built::Brep(b) => *b,
|
||||
Built::Failed(e) => return Ok(Err(failed_output(&e))),
|
||||
};
|
||||
// the extra wire edge (`CreateWireEdgeFromCurve`)
|
||||
if let Some(x) = &i.extra {
|
||||
let curve: super::topology_edgeuse_p1::CrvIn = serde_json::to_value(&x.curve)
|
||||
.and_then(serde_json::from_value)
|
||||
.map_err(|e| harness(format!("extra curve: {e}")))?;
|
||||
let c = Curve::from(super::topology_edgeuse_p1::bspline_of(&curve)?);
|
||||
if let Err(e) = solidcore::topology::brep::p5::create_wire_edge_from_curve(
|
||||
k,
|
||||
&mut b,
|
||||
c,
|
||||
Extent1d::from_min_max(x.t0, x.t1),
|
||||
) {
|
||||
return Ok(Err(Output::unported(&e)
|
||||
.unwrap_or_else(|| Output::err(format!("extra_wire_failed: {e}")))));
|
||||
}
|
||||
}
|
||||
let opts = HcrOptions {
|
||||
approx_tol_3d: i.approx_tol,
|
||||
ang_tol_rad: i.ang_tol,
|
||||
display_seams: i.seams,
|
||||
display_smooth_edges: i.smooth,
|
||||
};
|
||||
let mut state = ArenaNotifyState::default();
|
||||
let made = match i.placement {
|
||||
None => Hcr::new(
|
||||
k,
|
||||
&mut state.env(None),
|
||||
&b,
|
||||
&source_key,
|
||||
Vec3::new(i.view[0], i.view[1], i.view[2]),
|
||||
opts,
|
||||
),
|
||||
Some([z, x]) => {
|
||||
let mut a = Axis2Placement::default();
|
||||
a.set_step_canonical(
|
||||
Vec3::new(i.view[0], i.view[1], i.view[2]),
|
||||
Vec3::new(z[0], z[1], z[2]),
|
||||
Vec3::new(x[0], x[1], x[2]),
|
||||
)
|
||||
.and_then(|()| Hcr::with_transform(k, &mut state.env(None), &b, &source_key, &a, opts))
|
||||
}
|
||||
};
|
||||
let mut h = match made {
|
||||
Ok(h) => h,
|
||||
Err(e) => {
|
||||
return Ok(Err(Output::unported(&e)
|
||||
.unwrap_or_else(|| Output::err(format!("hcr_construct_failed: {e}")))))
|
||||
}
|
||||
};
|
||||
// the shim's `walk(scene, g)` order: the scene's arena ids renumbered to it (nothing
|
||||
// in the freshly constructed HCR refers to a scene id yet), its geometry owned under
|
||||
// the arena identities the renumbering gives it
|
||||
let mut scene = renumber_with(h.scene_brep(), conv)?;
|
||||
scene
|
||||
.use_arena_naming()
|
||||
.map_err(|e| harness(format!("scene naming: {e}")))?;
|
||||
*h.scene_brep_mut() = scene;
|
||||
Ok(Ok(h))
|
||||
}
|
||||
|
||||
fn output(status: f64, record: Vec<f64>) -> Output {
|
||||
@@ -307,11 +513,6 @@ impl UnitDiff for Runner {
|
||||
Ok(p) => p,
|
||||
Err(msg) => return Ok(Output::err(msg)),
|
||||
};
|
||||
let s = ffi::split(&p.scene).ok_or_else(|| harness("short scene output"))?;
|
||||
let (_, exact_faces) = import_raw(s.export)?;
|
||||
if inexact(&p.r, s.curve_types, &exact_faces) {
|
||||
return Ok(Output::err("inexact_geometry"));
|
||||
}
|
||||
let v = match ffi::eval(p.op, &p.path, &p.brep, &p.hcr, &p.ops) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Ok(Output::err(format!("rejected: {e}"))),
|
||||
@@ -322,42 +523,20 @@ impl UnitDiff for Runner {
|
||||
|
||||
fn run_rust(&self, case: &Case) -> Result<Output, HarnessError> {
|
||||
let i: Inputs = case.parse()?;
|
||||
let p = match prepare(&i)? {
|
||||
Ok(p) => p,
|
||||
Err(msg) => return Ok(Output::err(msg)),
|
||||
let mut k = Kernel::default();
|
||||
let h = match rust_hcr(&mut k, &i)? {
|
||||
Ok(h) => h,
|
||||
Err(o) => return Ok(o),
|
||||
};
|
||||
let s = ffi::split(&p.scene).ok_or_else(|| harness("short scene output"))?;
|
||||
let (mut scene, exact_faces) = import(s.export)?;
|
||||
if inexact(&p.r, s.curve_types, &exact_faces) {
|
||||
return Ok(Output::err("inexact_geometry"));
|
||||
}
|
||||
// the surfaces' owners (`SmSurface::GetOwner`, set by the B-rep build upstream and
|
||||
// read by `SmBrep::GetSurfaces`): the kernel's arena identities
|
||||
let face_ids: Vec<FaceId> = scene.faces().ids().collect();
|
||||
for id in face_ids {
|
||||
if let Some(surface) = scene
|
||||
.face_mut(id)
|
||||
.map_err(|e| harness(e.to_string()))?
|
||||
.surface
|
||||
.as_mut()
|
||||
{
|
||||
let _ = surface.set_owner(Some(arena_identity(CacheObject::Face(id))));
|
||||
}
|
||||
// the face's tolerance, as the face upstream answers GetTolerance with
|
||||
scene
|
||||
.sync_face_surface_tolerance(id)
|
||||
.map_err(|e| harness(e.to_string()))?;
|
||||
}
|
||||
let [x0, y0, z0, x1, y1, z1] = s.scene_box;
|
||||
let (lo, hi) = (Vec3::new(x0, y0, z0), Vec3::new(x1, y1, z1));
|
||||
let scene_box = if lo.x <= hi.x && lo.y <= hi.y && lo.z <= hi.z {
|
||||
Extent3d::from_points(lo, hi)
|
||||
} else if Extent3d::new().min() == lo && Extent3d::new().max() == hi {
|
||||
Extent3d::new()
|
||||
} else {
|
||||
return Err(harness("scene box neither ordered nor empty"));
|
||||
let b = h.scene_brep();
|
||||
let Some(r) = resolve(&i, b.edges().len(), b.vertices().len()) else {
|
||||
return Ok(Output::err("no_edge"));
|
||||
};
|
||||
match run_rust(&i, &p.r, scene, scene_box) {
|
||||
let (op, ops) = ops_block(&r);
|
||||
if let Err(m) = validate_rust(op, &h, &ops) {
|
||||
return Ok(Output::err(m));
|
||||
}
|
||||
match run_rust(&k, h, &r) {
|
||||
Ok((status, record)) => Ok(output(status, record)),
|
||||
Err(e) => Ok(Output::unported(&e).unwrap_or_else(|| match e {
|
||||
SolidError::InvalidInput {
|
||||
@@ -445,29 +624,9 @@ fn put_piece(k: &Kernel, o: &mut Vec<f64>, c: &Curve) {
|
||||
}
|
||||
}
|
||||
|
||||
fn run_rust(i: &Inputs, r: &Resolved, scene: Brep, scene_box: Extent3d) -> Result<(f64, Vec<f64>), SolidError> {
|
||||
let k = Kernel::default();
|
||||
let transform = match i.placement {
|
||||
None => view_transform_for_vector(Vec3::new(i.view[0], i.view[1], i.view[2]))?,
|
||||
Some([z, x]) => {
|
||||
let mut a = Axis2Placement::default();
|
||||
a.set_step_canonical(
|
||||
Vec3::new(i.view[0], i.view[1], i.view[2]),
|
||||
Vec3::new(z[0], z[1], z[2]),
|
||||
Vec3::new(x[0], x[1], x[2]),
|
||||
)?;
|
||||
a
|
||||
}
|
||||
};
|
||||
let opts = HcrOptions {
|
||||
approx_tol_3d: i.approx_tol,
|
||||
ang_tol_rad: i.ang_tol,
|
||||
display_seams: i.seams,
|
||||
display_smooth_edges: i.smooth,
|
||||
};
|
||||
let mut h = Hcr::from_scene(&k, scene, &transform, scene_box, opts);
|
||||
fn run_rust(k: &Kernel, mut h: Hcr, r: &Resolved) -> Result<(f64, Vec<f64>), SolidError> {
|
||||
for &e in &r.sil {
|
||||
h.add_silhouette_edge(&k, eid(e))?;
|
||||
h.add_silhouette_edge(k, eid(e))?;
|
||||
}
|
||||
for &e in &r.back {
|
||||
h.add_back_facing_edge(eid(e));
|
||||
@@ -484,19 +643,19 @@ fn run_rust(i: &Inputs, r: &Resolved, scene: Brep, scene_box: Extent3d) -> Resul
|
||||
rec.extend([v.x, v.y, v.z, h.approx_tol_3d(), h.ang_tol_rad()]);
|
||||
rec.extend([flag(h.display_seams()), flag(h.display_smooth_edges())]);
|
||||
}
|
||||
OpIn::IsSilhouette { edge } => rec.push(flag(h.is_silhouette_edge(&k, eid(edge))?)),
|
||||
OpIn::IsBackFacing { edge } => rec.push(flag(h.is_back_facing_edge(&k, eid(edge))?)),
|
||||
OpIn::IsSilhouette { edge } => rec.push(flag(h.is_silhouette_edge(k, eid(edge))?)),
|
||||
OpIn::IsBackFacing { edge } => rec.push(flag(h.is_back_facing_edge(k, eid(edge))?)),
|
||||
OpIn::Intersect { edge, fa, fb } | OpIn::CurveVisibility { edge, fa, fb, .. } => {
|
||||
let e = eid(edge);
|
||||
let ivl = edge::interval(h.scene_brep(), e)?;
|
||||
let ivl = Extent1d::from_min_max(ivl.evaluate(fa), ivl.evaluate(fb));
|
||||
let tol = edge::tolerance(h.scene_brep(), e)?;
|
||||
let mut cc = new_curve_classification(&k, HcrObject::Curve(e), ivl, tol);
|
||||
let mut cc = new_curve_classification(k, HcrObject::Curve(e), ivl, tol);
|
||||
let res = match r.op {
|
||||
OpIn::CurveVisibility { start_hint, end_hint, .. } => {
|
||||
h.compute_curve_visibility(&k, &mut cc, hint_ulong(start_hint), hint_ulong(end_hint))
|
||||
h.compute_curve_visibility(k, &mut cc, hint_ulong(start_hint), hint_ulong(end_hint))
|
||||
}
|
||||
_ => h.intersect_curve_with_silhouettes(&k, &mut cc),
|
||||
_ => h.intersect_curve_with_silhouettes(k, &mut cc),
|
||||
};
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
@@ -512,18 +671,18 @@ fn run_rust(i: &Inputs, r: &Resolved, scene: Brep, scene_box: Extent3d) -> Resul
|
||||
marked.insert(eid(edge));
|
||||
}
|
||||
let v = (vertex >= 0).then(|| VertexId::from_index(vertex as u32));
|
||||
match h.compute_edge_visibility(&k, eid(edge), v, visible, &mut marked) {
|
||||
match h.compute_edge_visibility(k, eid(edge), v, visible, &mut marked) {
|
||||
Ok(()) => {}
|
||||
Err(SolidError::Upstream { code, .. }) => status = f64::from(code),
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
rec.push(h.visible_curves().len() as f64);
|
||||
for c in h.visible_curves() {
|
||||
put_piece(&k, &mut rec, c);
|
||||
put_piece(k, &mut rec, c);
|
||||
}
|
||||
rec.push(h.invisible_curves().len() as f64);
|
||||
for c in h.invisible_curves() {
|
||||
put_piece(&k, &mut rec, c);
|
||||
put_piece(k, &mut rec, c);
|
||||
}
|
||||
}
|
||||
OpIn::SilhouetteEdges => {
|
||||
@@ -533,7 +692,7 @@ fn run_rust(i: &Inputs, r: &Resolved, scene: Brep, scene_box: Extent3d) -> Resul
|
||||
rec.push(edges.len() as f64);
|
||||
rec.extend(edges.iter().map(|e| f64::from(e.index())));
|
||||
}
|
||||
OpIn::PointVisible { p } => rec.push(flag(h.is_point_visible(&k, Vec3::new(p[0], p[1], p[2]))?)),
|
||||
OpIn::PointVisible { p } => rec.push(flag(h.is_point_visible(k, Vec3::new(p[0], p[1], p[2]))?)),
|
||||
}
|
||||
Ok((status, rec))
|
||||
}
|
||||
|
||||
@@ -41,6 +41,10 @@ struct Inputs {
|
||||
#[serde(default)]
|
||||
occurrence: u32,
|
||||
build: bool,
|
||||
/// `GetTree` after the build step: 0 none, 1 with the editing flag as is, 2 with
|
||||
/// editing disabled first.
|
||||
#[serde(default)]
|
||||
get_tree: u8,
|
||||
#[serde(default)]
|
||||
editing: bool,
|
||||
#[serde(default)]
|
||||
@@ -61,12 +65,22 @@ fn flag(value: bool) -> f64 {
|
||||
}
|
||||
|
||||
impl Inputs {
|
||||
/// The shim's `build_mode` (see the FFI module).
|
||||
fn build_mode(&self) -> f64 {
|
||||
match (self.build, self.get_tree) {
|
||||
(false, 0) => 0.0,
|
||||
(true, 0) => 1.0,
|
||||
(true, g) => 1.0 + f64::from(g),
|
||||
(false, g) => 3.0 + f64::from(g),
|
||||
}
|
||||
}
|
||||
|
||||
fn flat(&self) -> Vec<f64> {
|
||||
let mut out = vec![
|
||||
f64::from(self.surface_type),
|
||||
f64::from(self.loops),
|
||||
f64::from(self.occurrence),
|
||||
flag(self.build),
|
||||
self.build_mode(),
|
||||
flag(self.editing),
|
||||
flag(self.modified),
|
||||
flag(self.test_tol),
|
||||
@@ -81,6 +95,9 @@ impl Inputs {
|
||||
fn parse(value: &Value) -> Result<Inputs, HarnessError> {
|
||||
let input: Inputs = serde_json::from_value(value.clone())
|
||||
.map_err(|e| harness(format!("native_face input: {e}")))?;
|
||||
if input.get_tree > 2 {
|
||||
return Err(harness("native_face get_tree must be 0, 1 or 2"));
|
||||
}
|
||||
ffi::validate(&input.path, &input.flat()).map_err(|e| harness(e.to_string()))?;
|
||||
Ok(input)
|
||||
}
|
||||
@@ -359,8 +376,20 @@ fn rust_record(input: &Inputs, brep: &mut Brep, face: FaceId) -> SolidResult<Vec
|
||||
EntityRef::Face(face),
|
||||
tolerance,
|
||||
)?;
|
||||
if input.build {
|
||||
let built = cache.build_tree_with_face(&k, &mut BrepCacheFace { b: brep, f: face });
|
||||
if input.build || input.get_tree != 0 {
|
||||
let mut built = if input.build {
|
||||
cache.build_tree_with_face(&k, &mut BrepCacheFace { b: brep, f: face })
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
if built.is_ok() && input.get_tree != 0 {
|
||||
if input.get_tree == 2 {
|
||||
brep.set_editing_enabled(false);
|
||||
}
|
||||
built = cache
|
||||
.get_tree_with_face(&k, &mut BrepCacheFace { b: brep, f: face })
|
||||
.map(|_| ());
|
||||
}
|
||||
let build_status = match built {
|
||||
Ok(()) => SUCCESS,
|
||||
Err(error) => status(error)?,
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
//! Upstream: `source/SMLib/src/SmTrimSrfCache.cpp` (`BuildTree`, `PointTest`,
|
||||
//! `PointClassify`). Shim: `harness/cpp/shim/surface_trim_srf_cache_faces.cpp`.
|
||||
//!
|
||||
//! Input: `surface_type loop_count occurrence build editing modified test_tol
|
||||
//! Input: `surface_type loop_count occurrence build_mode editing modified test_tol
|
||||
//! boundary_probes sample_count (u_fraction v_fraction source_tolerance)*`.
|
||||
//! `build_mode` is 0 (none), 1 (`BuildTree`), 2 (`BuildTree`, then `GetTree`), 3
|
||||
//! (`BuildTree`, editing disabled, `GetTree`), 4 (`GetTree`) or 5 (editing disabled,
|
||||
//! `GetTree`); `build_status` is that of the last call (`GetTree` NULL is `SM_ERR`).
|
||||
//! The selector is the zero-based occurrence among faces of that **exact** concrete
|
||||
//! surface type and upward-loop count in the shared native Graph walk.
|
||||
//!
|
||||
@@ -70,8 +73,12 @@ pub fn validate(path: &str, input: &[f64]) -> Result<(), FfiError> {
|
||||
));
|
||||
}
|
||||
}
|
||||
let mode = *head.get(3).ok_or_else(|| bad("missing build mode"))?;
|
||||
if mode.fract() != 0.0 || !(0.0..=5.0).contains(&mode) {
|
||||
return Err(bad("build mode must be an integer in 0..=5"));
|
||||
}
|
||||
if head
|
||||
.get(3..8)
|
||||
.get(4..8)
|
||||
.ok_or_else(|| bad("missing flags"))?
|
||||
.iter()
|
||||
.any(|v| *v != 0.0 && *v != 1.0)
|
||||
@@ -149,7 +156,9 @@ mod tests {
|
||||
(0, 15030.0),
|
||||
(1, -1.0),
|
||||
(2, 0.5),
|
||||
(3, 2.0),
|
||||
(3, 6.0),
|
||||
(3, 0.5),
|
||||
(4, 2.0),
|
||||
(8, 65.0),
|
||||
(9, f64::INFINITY),
|
||||
(10, f64::NAN),
|
||||
|
||||
@@ -269,6 +269,20 @@ pub(crate) fn brep_block_at(input: &[f64], path: &str) -> Result<usize, FfiError
|
||||
Ok(rd.at)
|
||||
}
|
||||
|
||||
/// The input checks [`eval`] makes on a B-rep block before any shim call (pure Rust,
|
||||
/// no C++ is run): a runner whose Rust side builds the block itself refuses exactly the
|
||||
/// blocks [`eval`] refuses.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`FfiError::InvalidArgument`] for a block [`eval`] rejects.
|
||||
pub fn validate_block(path: &str, input: &[f64]) -> Result<(), FfiError> {
|
||||
let mut rd = Reader::new(input);
|
||||
brep_block(&mut rd, path)?;
|
||||
let block = input.get(..rd.at).ok_or_else(|| bad("input too short"))?;
|
||||
check_block_magnitudes(block)
|
||||
}
|
||||
|
||||
/// Most faces of a kind-10 block.
|
||||
pub const MAX_FACES: usize = 4;
|
||||
|
||||
|
||||
@@ -70,7 +70,12 @@
|
||||
//! [`Hcr::compute_silhouettes`] and [`Hcr::compute_global_visibility`] take the
|
||||
//! kernel mutably.
|
||||
//! - `IsPointVisible` keeps the ray tracer's surface set and grid and rebuilds the
|
||||
//! tracer (which borrows the surfaces) for each ray.
|
||||
//! tracer (which borrows the surfaces) for each ray. A face-owned surface's cache
|
||||
//! (`smsurf_GetSurfaceCache`, then the `GetTree` calls of `AddSurfaceToGrid` and
|
||||
//! `ExpandHierarchy`, in that order) runs through its face before the tracer borrows
|
||||
//! the surfaces: the trimmed cache's `BuildTree` reads the face and may change its
|
||||
//! B-rep. The tracer then reads that tree as is
|
||||
//! (`RayTracer::add_surface_with_fetched_cache`).
|
||||
//! - `DrawVisibleCurves`, `IsConcaveSilhouette` and `IsFrontFacingFace` are not owned by
|
||||
//! this unit (no caller, graphics only).
|
||||
|
||||
@@ -102,10 +107,13 @@ use crate::nurbs::b_spline_curve_n_lib::TrimMode;
|
||||
use crate::nurbs::b_spline_surface::BSplineSurface;
|
||||
use crate::nurbs::b_spline_surface_n_lib::p2::OrientType;
|
||||
use crate::query::brep::{self as qbrep, BoundingBoxOptions};
|
||||
use crate::query::face::p2::BrepCacheFace;
|
||||
use crate::query::face::p2::{point_classify_prepared, PointClassifyOptions};
|
||||
use crate::surface::surface::p1::get_surface_cache_with_face;
|
||||
use crate::surface::surface::p2::surface_cache;
|
||||
use crate::surface::surface::{Surface, SurfaceKind};
|
||||
use crate::surface::surface_cache::face_context::prepare_face_surface_cache;
|
||||
use crate::surface::surface_cache::face_context::MappedCacheFace;
|
||||
use crate::surface::surface_cache::SurfaceCache;
|
||||
use crate::surface::volume::Volume;
|
||||
use crate::topology::{edge, edgeuse, vertex, vertexuse};
|
||||
use crate::topology::{Brep, BrepId, EdgeId, EdgeuseId, FaceId, RegionId, VertexId};
|
||||
@@ -498,7 +506,92 @@ enum RaySurface {
|
||||
/// First cache handle of a scene entity: below it are the tracer's surface indices.
|
||||
const ENTITY_HANDLE_BASE: usize = usize::MAX / 2;
|
||||
|
||||
/// The handles `prepare_face_surface_cache` addresses the scene's entities by, in the
|
||||
/// A surface cache the tracer lists: one whose `GetTree` calls the tracer runs itself
|
||||
/// (`Lazy`), or a face-owned one whose `GetTree` calls already ran through its face
|
||||
/// (`Fetched`, see [`face_surface_cache`]).
|
||||
enum ListedCache {
|
||||
Lazy(SurfaceCache<usize>),
|
||||
Fetched(SurfaceCache<usize>),
|
||||
}
|
||||
|
||||
/// `smsurf_GetSurfaceCache` of a surface without face and `AddSurfaceToGrid`'s
|
||||
/// `GetTree() == NULL` check: the cache, `None` when the cache or its tree fails (the
|
||||
/// surface is not listed; the status is ignored upstream) or the tree has no top node.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`SolidError::Unported`] from the cache build.
|
||||
fn standalone_surface_cache(
|
||||
k: &Kernel,
|
||||
surface: &Surface,
|
||||
handle: usize,
|
||||
) -> Result<Option<ListedCache>> {
|
||||
let mut cache = match surface_cache::<usize>(k, surface, Some(handle)) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
// `AddSurfaceToGrid` lists the surface only once its tree has a top node
|
||||
let has_top = match cache.get_tree(k) {
|
||||
Ok(tree) => tree.top_node().is_some(),
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => false,
|
||||
};
|
||||
Ok(has_top.then_some(ListedCache::Lazy(cache)))
|
||||
}
|
||||
|
||||
/// `smsurf_GetSurfaceCache` of the surface of face `f` of `scene` (built through the
|
||||
/// face, which may create its UV trim curves), then the three `GetTree` calls the
|
||||
/// tracer makes on it (`AddSurfaceToGrid`'s `GetTree() == NULL` check and
|
||||
/// `GetTree()->GetTopNode()`, then `ExpandHierarchy`'s `GetTree()`), each with the
|
||||
/// face: a face-owned trimmed cache whose face has no trim curves, or whose B-rep is
|
||||
/// being edited, rebuilds on every call (`SmTrimSrfCache::BuildTree`). `None` when a
|
||||
/// step fails (the surface is not listed) or the tree has no top node.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`SolidError::Unported`] from the cache build; those of the face lookups.
|
||||
fn face_surface_cache(
|
||||
k: &Kernel,
|
||||
scene: &mut Brep,
|
||||
f: FaceId,
|
||||
handle: usize,
|
||||
handle_of: &dyn Fn(EntityRef) -> Result<usize>,
|
||||
entity_of: &dyn Fn(usize) -> Result<EntityRef>,
|
||||
) -> Result<Option<ListedCache>> {
|
||||
// the cache refers to the face's surface (an exact evaluation copy: the face's
|
||||
// B-rep is changed through the face while the cache is built)
|
||||
let surface = scene
|
||||
.face(f)?
|
||||
.surface
|
||||
.as_ref()
|
||||
.ok_or(undefined("face without surface"))?
|
||||
.clone_for_evaluation()?;
|
||||
let mut mapped = match MappedCacheFace::new(BrepCacheFace { b: scene, f }, handle_of, entity_of)
|
||||
{
|
||||
Ok(mapped) => mapped,
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
let mut cache = match get_surface_cache_with_face(k, &surface, Some(handle), &mut mapped) {
|
||||
Ok(cache) => cache,
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
// AddSurfaceToGrid: GetTree() == NULL, GetTree()->GetTopNode(); ExpandHierarchy:
|
||||
// GetTree()
|
||||
for check_top in [true, true, false] {
|
||||
match cache.get_tree_with_face(k, &mut mapped) {
|
||||
Ok(tree) if check_top && tree.top_node().is_none() => return Ok(None),
|
||||
Ok(_) => {}
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => return Ok(None),
|
||||
}
|
||||
}
|
||||
Ok(Some(ListedCache::Fetched(cache.into_base())))
|
||||
}
|
||||
|
||||
/// The handles [`face_surface_cache`] addresses the scene's entities by, in the
|
||||
/// tracer's usize handle space above [`ENTITY_HANDLE_BASE`], and the inverse table.
|
||||
fn entity_handles(b: &Brep) -> (BTreeMap<EntityRef, usize>, Vec<EntityRef>) {
|
||||
let all: Vec<EntityRef> = b
|
||||
@@ -1509,23 +1602,19 @@ impl Hcr {
|
||||
}
|
||||
RaySurface::Owned(_) => None,
|
||||
};
|
||||
let built = match (face_owned, s) {
|
||||
let handle = listed_faces.len();
|
||||
let fetched = match (face_owned, s) {
|
||||
(Some(f), _) => {
|
||||
let (to_handle, to_entity) =
|
||||
entities.get_or_insert_with(|| entity_handles(&self.scene));
|
||||
prepare_face_surface_cache(
|
||||
k,
|
||||
&mut self.scene,
|
||||
f,
|
||||
listed_faces.len(),
|
||||
&|e| to_handle.get(&e).copied().ok_or(undefined("cache entity")),
|
||||
&|h| {
|
||||
h.checked_sub(ENTITY_HANDLE_BASE)
|
||||
.and_then(|i| to_entity.get(i))
|
||||
.copied()
|
||||
.ok_or(undefined("cache handle"))
|
||||
},
|
||||
)
|
||||
let handle_of = |e| to_handle.get(&e).copied().ok_or(undefined("cache entity"));
|
||||
let entity_of = |h: usize| {
|
||||
h.checked_sub(ENTITY_HANDLE_BASE)
|
||||
.and_then(|i| to_entity.get(i))
|
||||
.copied()
|
||||
.ok_or(undefined("cache handle"))
|
||||
};
|
||||
face_surface_cache(k, &mut self.scene, f, handle, &handle_of, &entity_of)?
|
||||
}
|
||||
(None, RaySurface::Face(f)) => {
|
||||
let surface = self
|
||||
@@ -1534,31 +1623,16 @@ impl Hcr {
|
||||
.surface
|
||||
.as_ref()
|
||||
.ok_or(undefined("face without surface"))?;
|
||||
surface_cache::<usize>(k, surface, Some(listed_faces.len()))
|
||||
standalone_surface_cache(k, surface, handle)?
|
||||
}
|
||||
(None, RaySurface::Owned(surface)) => {
|
||||
surface_cache::<usize>(k, surface, Some(listed_faces.len()))
|
||||
(None, RaySurface::Owned(surface)) => standalone_surface_cache(k, surface, handle)?,
|
||||
};
|
||||
match fetched {
|
||||
Some(cache) => {
|
||||
listed_faces.push(face_owned);
|
||||
caches.push(Some(cache));
|
||||
}
|
||||
};
|
||||
let mut cache = match built {
|
||||
Ok(cache) => cache,
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => {
|
||||
caches.push(None);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
// `AddSurfaceToGrid` lists the surface only once its tree has a top node
|
||||
let has_top = match cache.get_tree(k) {
|
||||
Ok(tree) => tree.top_node().is_some(),
|
||||
Err(e) if e.is_unported() => return Err(e),
|
||||
Err(_) => false,
|
||||
};
|
||||
if has_top {
|
||||
listed_faces.push(face_owned);
|
||||
caches.push(Some(cache));
|
||||
} else {
|
||||
caches.push(None);
|
||||
None => caches.push(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1584,8 +1658,14 @@ impl Hcr {
|
||||
.ok_or(undefined("face without surface"))?,
|
||||
RaySurface::Owned(ref s) => &**s,
|
||||
};
|
||||
if let Some(cache) = cache {
|
||||
ignore_status(tracer.add_surface_with_cache(k, surface, cache))?;
|
||||
match cache {
|
||||
Some(ListedCache::Fetched(cache)) => {
|
||||
ignore_status(tracer.add_surface_with_fetched_cache(k, surface, cache))?;
|
||||
}
|
||||
Some(ListedCache::Lazy(cache)) => {
|
||||
ignore_status(tracer.add_surface_with_cache(k, surface, cache))?;
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
//! | [`Grid::add_element`], [`ElementSource`] | `SmGrid::AddElement`, `SmGridElementType` |
|
||||
//! | [`Grid::add_surface_node`], [`Grid::add_surface_element`] | `SmGrid::AddElement(SM_GE_SURFACE)` (from a tree node; from prepared leaf data) |
|
||||
//! | [`Grid::add_hierarchy_element`] | `SmGrid::AddHierarchyElement` |
|
||||
//! | [`Grid::expand_hierarchy`] | `SmSurfaceHierarchyElement::ExpandHierarchy` |
|
||||
//! | [`Grid::expand_hierarchy`], [`Grid::expand_hierarchy_fetched`] | `SmSurfaceHierarchyElement::ExpandHierarchy` |
|
||||
//! | [`Grid::expand_hierarchy_with`] | `SmSurfaceHierarchyElement::ExpandHierarchy` over a caller-held surface cache tree |
|
||||
//! | [`Grid::setup_3ddda`] | `SmGrid::Setup3DDDA` |
|
||||
//! | `AxisStep` (private) | one axis of `SmGrid::Setup3DDDA` |
|
||||
@@ -1180,6 +1180,33 @@ impl<O: Copy + PartialEq> Grid<O> {
|
||||
k: &Kernel,
|
||||
id: HierarchyId,
|
||||
cache: &mut SurfaceCache<O>,
|
||||
) -> Result<()> {
|
||||
self.expand_hierarchy_in(k, id, cache, true)
|
||||
}
|
||||
|
||||
/// [`Grid::expand_hierarchy`] for a cache whose `GetTree` the caller has already
|
||||
/// run (a face-owned trimmed cache, whose tree build needs its face:
|
||||
/// [`TrimSrfCache::get_tree_with_face`](crate::surface::trim_srf_cache::TrimSrfCache::get_tree_with_face)):
|
||||
/// the cache's tree is read as is, never rebuilt.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// As [`Grid::expand_hierarchy`]; `InvalidInput` when the cache has no tree.
|
||||
pub fn expand_hierarchy_fetched(
|
||||
&mut self,
|
||||
k: &Kernel,
|
||||
id: HierarchyId,
|
||||
cache: &mut SurfaceCache<O>,
|
||||
) -> Result<()> {
|
||||
self.expand_hierarchy_in(k, id, cache, false)
|
||||
}
|
||||
|
||||
fn expand_hierarchy_in(
|
||||
&mut self,
|
||||
k: &Kernel,
|
||||
id: HierarchyId,
|
||||
cache: &mut SurfaceCache<O>,
|
||||
fetch: bool,
|
||||
) -> Result<()> {
|
||||
const OP: &str = "geom::grid::Grid::expand_hierarchy";
|
||||
let element = self.hierarchy.get(id.0).ok_or_else(|| bad_id(OP))?;
|
||||
@@ -1191,7 +1218,17 @@ impl<O: Copy + PartialEq> Grid<O> {
|
||||
});
|
||||
}
|
||||
// The clone lets `init` borrow the cache mutably (it builds polar boxes).
|
||||
let tree = cache.get_tree(k)?.clone();
|
||||
let tree = if fetch {
|
||||
cache.get_tree(k)?.clone()
|
||||
} else {
|
||||
cache
|
||||
.tree()
|
||||
.ok_or(SolidError::InvalidInput {
|
||||
operation: OP,
|
||||
reason: "surface cache has no tree",
|
||||
})?
|
||||
.clone()
|
||||
};
|
||||
self.expand_hierarchy_with(id, &tree, |_, node| {
|
||||
SurfaceGridElement::init(k, cache, node)
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
//! | `Drop` (implicit) | `~SmRayTracer` |
|
||||
//! | [`RayTracer::set_solver_tolerance`] | `SmRayTracer::SetSolverTolerance` |
|
||||
//! | [`RayTracer::fire_ray`], [`RayHit`] | `SmRayTracer::FireRay` |
|
||||
//! | [`RayTracer::add_surface_to_grid`], [`RayTracer::add_surface_with_bbox`] | `SmRayTracer::AddSurfaceToGrid` |
|
||||
//! | [`RayTracer::add_surface_to_grid`], [`RayTracer::add_surface_with_bbox`], [`RayTracer::add_surface_with_fetched_cache`] | `SmRayTracer::AddSurfaceToGrid` |
|
||||
//! | [`RayTracer::add_poly_brep_to_grid`] | `SmRayTracer::AddPolyBrepToGrid` |
|
||||
//! | [`RayTracer::poly_brep`], [`RayTracer::poly_brep_count`] | `m_sPolyBreps` |
|
||||
//! | [`RayRenderer`], [`RayRenderer::new`] | `SmRayRenderer`, `SmRayRenderer::SmRayRenderer` |
|
||||
@@ -1251,6 +1251,31 @@ impl<'s> RayTracer<'s> {
|
||||
self.grid.expand_hierarchy(k, id, &mut cache)
|
||||
}
|
||||
|
||||
/// [`RayTracer::add_surface_with_cache`] for a cache whose `GetTree` calls the
|
||||
/// caller has already run, in upstream order: `AddSurfaceToGrid`'s two and
|
||||
/// `ExpandHierarchy`'s one. A face-owned trimmed cache rebuilds its tree through
|
||||
/// its face ([`TrimSrfCache::get_tree_with_face`](crate::surface::trim_srf_cache::TrimSrfCache::get_tree_with_face)),
|
||||
/// which may change the face's B-rep, so it cannot run while the tracer borrows
|
||||
/// the surfaces. Here the tree is read as is.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// As [`RayTracer::add_surface_with_cache`]; `SM_ERR` when the cache has no tree
|
||||
/// or the tree no top node.
|
||||
pub fn add_surface_with_fetched_cache(
|
||||
&mut self,
|
||||
k: &Kernel,
|
||||
surface: &'s Surface,
|
||||
mut cache: SurfaceCache<usize>,
|
||||
) -> Result<()> {
|
||||
const OP: &str = "intersect::ray_tracer::add_surface_with_fetched_cache";
|
||||
let tree = cache.tree().ok_or(SolidError::sm_err(OP))?;
|
||||
let top = tree.top_node().ok_or(SolidError::sm_err(OP))?;
|
||||
let bbox = tree.node(top)?.bbox;
|
||||
let id = self.add_surface_with_bbox(surface, &bbox)?;
|
||||
self.grid.expand_hierarchy_fetched(k, id, &mut cache)
|
||||
}
|
||||
|
||||
/// Embeds the faces of a poly B-rep in the grid (`AddPolyBrepToGrid`): notifies
|
||||
/// the poly B-rep of an edit (`env` is its notification environment), builds its
|
||||
/// `SmBrepCache` and adds every leaf of the cache's surface tree to the grid as a
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
//! | [`TrimSrfCache::point_test`] | `SmTrimSrfCache::PointTest` |
|
||||
//! | [`TrimSrfCache::point_classify`] | `SmTrimSrfCache::PointClassify` |
|
||||
//! | [`TrimSrfCache::build_tree`], [`TrimSrfCache::build_tree_with_face`] | `SmTrimSrfCache::BuildTree` |
|
||||
//! | [`TrimSrfCache::get_tree_with_face`] | `SmSurfaceCache::GetTree` of a trimmed cache (virtual `BuildTree`) |
|
||||
//! | [`TrimSrfCache::point_test_in`], [`TrimSrfCache::point_classify_in`] | `PointTest`, `PointClassify` over the given face |
|
||||
//! | [`CacheFace`] | `GetFace()` and what the cache reads from the face and its B-rep |
|
||||
//! | [`TrimSrfCache::bind_face`] | `GetFace()` with an explicit caller-handle mapping |
|
||||
@@ -88,7 +89,7 @@ use crate::geom::extent1d::Extent1d;
|
||||
use crate::geom::extent2d::Extent2d;
|
||||
use crate::geom::extent3d::Extent3d;
|
||||
use crate::geom::object::ObjectRef;
|
||||
use crate::geom::tree::{NodeClass, NodeData, NodeId};
|
||||
use crate::geom::tree::{NodeClass, NodeData, NodeId, Tree};
|
||||
use crate::geom::vector2d::{Point2, Vec2};
|
||||
use crate::geom::vector3d::{Point3, Vec3};
|
||||
use crate::kernel::Kernel;
|
||||
@@ -133,6 +134,7 @@ pub const POINT_CLASS_VERTEX: i32 = 104;
|
||||
const OP_CLASSIFY: &str = "surface::trim_srf_cache::TrimSrfCache::point_classify";
|
||||
const OP_EU: &str = "surface::trim_srf_cache::TrimSrfCache::implant_edgeuse";
|
||||
const OP_VU: &str = "surface::trim_srf_cache::TrimSrfCache::implant_vertexuse";
|
||||
const OP_GET_TREE: &str = "surface::trim_srf_cache::TrimSrfCache::get_tree_with_face";
|
||||
const OP_BUILD: &str = "surface::trim_srf_cache::TrimSrfCache::build_tree_with_face";
|
||||
|
||||
/// `SmTrimSrfCache::IsKindOf`: the trimmed surface cache or a base type.
|
||||
@@ -696,6 +698,39 @@ impl<O: Copy + PartialEq> TrimSrfCache<O> {
|
||||
self.build_tree_in(k, Some(face))
|
||||
}
|
||||
|
||||
/// The tree, built first when there is none, or when the surface belongs to a face
|
||||
/// whose boundary is not in the tree yet and no build is under way
|
||||
/// (`SmSurfaceCache::GetTree`, whose virtual `BuildTree` is this cache's
|
||||
/// [`TrimSrfCache::build_tree_with_face`]); `face` is the face owning the cache's
|
||||
/// surface (`GetFace()`). A cache that is not a trimmed one (the plain
|
||||
/// `SmSurfaceCache` fallback of `smsurf_GetSurfaceCache`) runs its own
|
||||
/// [`SurfaceCache::get_tree`].
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Those of [`TrimSrfCache::build_tree_with_face`] (upstream returns NULL), and
|
||||
/// of [`SurfaceCache::get_tree`] for a plain cache.
|
||||
pub fn get_tree_with_face(
|
||||
&mut self,
|
||||
k: &Kernel,
|
||||
face: &mut dyn CacheFace<O>,
|
||||
) -> Result<&mut Tree<O>> {
|
||||
if !self.base.trimmed_dispatch {
|
||||
return self.base.get_tree(k);
|
||||
}
|
||||
let face_owned = self.base.surface().face()?.is_some();
|
||||
if self.base.tree().is_none()
|
||||
|| (!self.base.have_t_surface_cache
|
||||
&& face_owned
|
||||
&& self.base.face_containment_done != FaceContainment::UnderConstruction)
|
||||
{
|
||||
self.build_tree_with_face(k, face)?;
|
||||
}
|
||||
self.base
|
||||
.tree_mut()
|
||||
.ok_or_else(|| invalid(OP_GET_TREE, "cache has no tree"))
|
||||
}
|
||||
|
||||
fn build_tree_in(&mut self, k: &Kernel, face: Option<&mut dyn CacheFace<O>>) -> Result<()> {
|
||||
// prevent bad recursion back into BuildTree
|
||||
let saved = self.base.face_containment_done;
|
||||
|
||||
@@ -69,6 +69,76 @@
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [-0.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_bspline_get_tree_editing_rebuilds_standalone",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Mouse.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; GetTree on an editing-enabled face (the blend.hcr IsPointVisible regime): BuildTree runs again on the built tree and keeps the standalone flags",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Mouse.smb"},
|
||||
"surface_type": 15002, "loops": 1, "build": true, "get_tree": 1, "editing": true,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [-0.25,0.5,0.00001], [1.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_bspline_get_tree_after_editing_implants_trims",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Mouse.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; BuildTree with editing on, then editing off and GetTree: the rebuild implants the trims into the existing tree",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Mouse.smb"},
|
||||
"surface_type": 15002, "loops": 1, "build": true, "get_tree": 2, "editing": true,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [-0.25,0.5,0.00001], [1.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_bspline_get_tree_trimmed_no_rebuild",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Mouse.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; GetTree after a trimmed build (m_bHaveTSurfaceCache) returns the tree without rebuilding",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Mouse.smb"},
|
||||
"surface_type": 15002, "loops": 1, "build": true, "get_tree": 1,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [-0.25,0.5,0.00001], [1.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_bspline_get_tree_builds_unbuilt",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Mouse.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; GetTree with no tree builds it through the face",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Mouse.smb"},
|
||||
"surface_type": 15002, "loops": 1, "build": false, "get_tree": 1,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [-0.25,0.5,0.00001], [1.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_holed_plane_get_tree_editing_rebuilds_standalone",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Fillet/ext_w_hole.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; GetTree on an editing-enabled planar face rebuilds with the standalone flags",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Fillet/ext_w_hole.smb"},
|
||||
"surface_type": 15011, "loops": 2, "build": true, "get_tree": 1, "editing": true,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [0.5,0.25,0.00001], [0.1,0.1,0.00001], [-0.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_holed_plane_get_tree_after_editing_implants_trims",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Fillet/ext_w_hole.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; planar face: editing off before GetTree implants both loops into the existing tree",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Fillet/ext_w_hole.smb"},
|
||||
"surface_type": 15011, "loops": 2, "build": true, "get_tree": 2, "editing": true,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [0.5,0.25,0.00001], [0.1,0.1,0.00001], [-0.25,0.5,0.00001]]
|
||||
}}
|
||||
},
|
||||
{
|
||||
"name": "native_face_holed_plane_get_tree_unbuilt_editing",
|
||||
"upstream_ref": "TestFiles/pt_TestFiles/Fillet/ext_w_hole.smb; source/SMLib/src/SmSurfaceCache.cpp:5459 (GetTree); source/SMLib/src/SmTrimSrfCache.cpp:132; GetTree with no tree on an editing-enabled face builds the standalone tree",
|
||||
"inputs": {"native_face": {
|
||||
"path": {"$upstream_file": "Fillet/ext_w_hole.smb"},
|
||||
"surface_type": 15011, "loops": 2, "build": false, "get_tree": 1, "editing": true,
|
||||
"test_tol": true, "boundary_probes": true,
|
||||
"samples": [[0.5,0.5,0.00001], [0.25,0.5,0.00001], [0.5,0.25,0.00001], [0.1,0.1,0.00001], [-0.25,0.5,0.00001]]
|
||||
}}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ void face_classify(std::vector<double>& out, SmSurfaceCache* cache, const FacePr
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// Input: surface_type loop_count occurrence build editing modified test_tol
|
||||
// Input: surface_type loop_count occurrence build_mode editing modified test_tol
|
||||
// boundary_probes sample_count (u_fraction v_fraction source_tolerance)*.
|
||||
// Output is documented in the safe FFI module. Every native result is computed here.
|
||||
SC_EXPORT int32_t sc_surface_trim_srf_cache_faces_eval(
|
||||
@@ -92,7 +92,8 @@ SC_EXPORT int32_t sc_surface_trim_srf_cache_faces_eval(
|
||||
input[1] < 0 || input[1] > UINT32_MAX || std::floor(input[1]) != input[1] ||
|
||||
input[2] < 0 || input[2] > UINT32_MAX || std::floor(input[2]) != input[2] ||
|
||||
input[8] < 0 || input[8] > 64 || std::floor(input[8]) != input[8]) return SC_ERR_ARG;
|
||||
for (int i = 3; i <= 7; ++i)
|
||||
if (input[3] < 0 || input[3] > 5 || std::floor(input[3]) != input[3]) return SC_ERR_ARG;
|
||||
for (int i = 4; i <= 7; ++i)
|
||||
if (input[i] != 0.0 && input[i] != 1.0) return SC_ERR_ARG;
|
||||
const int64_t count = (int64_t)input[8];
|
||||
if (input_len != 9 + count * 3) return SC_ERR_ARG;
|
||||
@@ -129,8 +130,16 @@ SC_EXPORT int32_t sc_surface_trim_srf_cache_faces_eval(
|
||||
face_domain(out, domain);
|
||||
built.brep->m_bEditingEnabled = input[4] != 0.0 ? TRUE : FALSE;
|
||||
std::unique_ptr<NativeFaceCache> cache(new (context) NativeFaceCache(*face->GetSurface()));
|
||||
const bool build_tree = input[3] != 0.0;
|
||||
const SmStatus build_status = build_tree ? cache->BuildTree(nullptr) : SM_SUCCESS;
|
||||
// input[3]: 0 none, 1 BuildTree, 2 BuildTree then GetTree, 3 BuildTree, editing
|
||||
// off, GetTree, 4 GetTree, 5 editing off, GetTree (GetTree's virtual BuildTree).
|
||||
const int mode = (int)input[3];
|
||||
const bool build_tree = mode >= 1;
|
||||
SmStatus build_status = (mode >= 1 && mode <= 3) ? cache->BuildTree(nullptr) : SM_SUCCESS;
|
||||
if (build_status == SM_SUCCESS && mode >= 2)
|
||||
{
|
||||
if (mode == 3 || mode == 5) built.brep->m_bEditingEnabled = FALSE;
|
||||
build_status = cache->GetTree() != nullptr ? SM_SUCCESS : SM_ERR;
|
||||
}
|
||||
cache->m_bFaceWasModified = input[5] != 0.0 ? TRUE : FALSE;
|
||||
out.insert(out.end(), {build_tree ? (double)build_status : -1.0,
|
||||
built.brep->m_bEditingEnabled == TRUE ? 1.0 : 0.0,
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
Updates: plan/findings/blend.hcr--own-scene-face-trim-cache-deferrals.md
|
||||
|
||||
# blend.hcr: face-owned GetTree wired, the three deferrals compare again (2026-10-28)
|
||||
|
||||
Unit: `blend.hcr` (caller), `surface.trim_srf_cache` (owner of the build).
|
||||
|
||||
Runtime path (confirmed with a temporary trace on `gen0103`): `ComputeEdgeVisibility`
|
||||
runs with the scene's editing flag still on (upstream sets `m_bEditingEnabled = TRUE`
|
||||
in `ConstructorInit` and clears it only in `ComputeGlobalVisibility`). The factory build
|
||||
of each face-owned trimmed cache therefore takes `SmTrimSrfCache::BuildTree`'s
|
||||
standalone branch (`m_bHaveTSurfaceCache = FALSE`, containment restored), so every later
|
||||
`SmSurfaceCache::GetTree` re-runs the virtual `BuildTree`. The Rust `SurfaceCache::get_tree`
|
||||
has no face to give it, which was the stub the cases hit (first in `Hcr::is_point_visible`'s
|
||||
top-node check).
|
||||
|
||||
Fix: `TrimSrfCache::get_tree_with_face` is `GetTree` with the virtual `BuildTree`
|
||||
dispatched to `build_tree_with_face`. `Hcr::is_point_visible` now builds a face-owned
|
||||
cache with `smsurf_GetSurfaceCache` through the face and then makes the tracer's three
|
||||
`GetTree` calls in upstream order (`AddSurfaceToGrid`: `GetTree() == NULL`,
|
||||
`GetTree()->GetTopNode()`; `ExpandHierarchy`: `GetTree()`), each with the face, before
|
||||
the tracer borrows the surfaces. `RayTracer::add_surface_with_fetched_cache` and
|
||||
`Grid::expand_hierarchy_fetched` read that tree without calling `GetTree` again. The
|
||||
bare `SurfaceCache::get_tree` stub for a face-owned trimmed cache without a face stays as is.
|
||||
|
||||
Result: `blend.hcr` 434 cases, 434 pass, 0 deferred, with_values 406 (before 403),
|
||||
max_rel_err 0. `surface.trim_srf_cache` gains 7 direct native-face `GetTree` cases
|
||||
(editing on: standalone rebuild; editing turned off between `BuildTree` and `GetTree`:
|
||||
the rebuild implants the trims into the existing tree, 417 nodes on `Mouse.smb`;
|
||||
trimmed tree: no rebuild; no tree: `GetTree` builds it), all passing with max_rel_err 0.
|
||||
@@ -0,0 +1,35 @@
|
||||
# blend.hcr: the Rust-built scene reaches TrimSrfCache::build_tree_with_face in IsPointVisible (2026-10-28)
|
||||
|
||||
Unit: `blend.hcr` (runner `crates/difftest/src/units/blend_hcr.rs`).
|
||||
|
||||
Change: the runner's Rust side no longer imports the C++ scene (the `SmHCR` scene copy,
|
||||
its transform and its `CalculateBoundingBox`). It builds the B-rep block with the Rust
|
||||
kernel (`.smb` files with the Rust reader), adds the extra wire edge, constructs the HCR
|
||||
with `Hcr::new` / `Hcr::with_transform` (the Rust `copy_brep`, transform, infinite region
|
||||
and bounding box) and renumbers the scene into the shim's traversal order.
|
||||
|
||||
Effect: 434 cases, 431 pass, 3 deferred (before: 434 pass). The three cases are
|
||||
`ComputeEdgeVisibility` (op `edge_visibility`) on `.smb` file scenes (`gen0103`,
|
||||
`gen0253`, `gen0320`; for example `gen0103`: `Mouse.smb`, view
|
||||
`(0.0936, -0.5806, -0.8407)`, `approx_tol 1e-6`, `ang_tol 0.2`). The C++ side completes
|
||||
them, as it did before. The Rust side returns
|
||||
`SolidError::Unported { upstream: "TrimSrfCache::build_tree_with_face for a face-owned
|
||||
surface", unit: "surface.trim_srf_cache" }` from `SurfaceCache::get_tree`
|
||||
(`surface/surface_cache/p3.rs`), called by `Hcr::is_point_visible` from
|
||||
`compute_curve_visibility` from `compute_edge_visibility`. So the cases are deferred,
|
||||
not failed. Nothing was compared for them (before, each compared 25 values).
|
||||
|
||||
Suspected cause: with the imported scene, the ray surfaces of these faces took a path
|
||||
that never built a face-owned trimmed cache through `get_tree`. The scene the Rust kernel
|
||||
builds itself (native surfaces and UV trim state from the Rust reader, copied by
|
||||
`copy_brep`) makes `IsPointVisible` list the faces as face-owned `RaySurface::Face`
|
||||
entries, which then need the unported face-owned trimmed tree build. This is a gap in
|
||||
the library, not in the harness. The cases should run again once
|
||||
`TrimSrfCache::build_tree_with_face` is ported for face-owned surfaces.
|
||||
|
||||
Also: `gen0110` (a face over an out-of-box B-spline at 1e-7 scale) fails to build on both
|
||||
sides. The C++ side now reports its own build failure as `brep_build_status_<status>`
|
||||
(from the `topology.vertexuse` fixture export, C++ side only), matching the Rust
|
||||
build's label, instead of the FFI's `fixture rejected`.
|
||||
|
||||
PR: difftest: blend.hcr — Rust-side Hcr scene instead of the C++ export (stacked on #1375).
|
||||
File diff suppressed because one or more lines are too long
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:
|
||||
status = "green"
|
||||
claimed_by = ""
|
||||
claimed_at = ""
|
||||
notes = "OWNED UV CURVE TOLERANCE (PR #1307, 2026-10-27): ARM64 native gate 772 cases, 772 value-bearing passes, 0 failed/deferred/rejected/vacuous, max_rel_err=0; all 762 baseline cases retained. Ten new nonlinear/control/reimplantation cases exercise the real owning-edge tolerance. EdgeuseInput transfers exact geometry and resolved optional tolerance into retention; BuildTree resolves edgeuse ownership, child subdivision reuses retained geometry without cloning, and handle mapping preserves its tolerance. Ownerless curves retain system tolerance. Native guards keep unrelated coordinate/scalar/z limits unchanged; the larger 1e11 coordinate envelope is isolated to ImplantEu. No core CurveCache or numerical tolerance change. Prior history: POLY-EDGE SUBDIVISION (PR #1016, 2026-10-25): 762 cases, 762 passed with values, 0 failed/deferred/rejected/vacuous, max_rel_err=0, rel_tol=1e-9, no absolute floors. All 630 prior cases retain their value coverage; 128 separate fixed-seed cases and 4 tolerance-boundary fixtures now exercise polygon implantation followed by repeated subdivision, including mixed edge-use lists, zero/negative native tolerances, different original/cache surfaces and handle mapping. SubdivideNode reimplants parent polygon edges head first into child 1 then child 2, preserving the parent lists. Endpoints, tolerance and an exact bounded evaluation copy of the original face surface are retained per handle; reimplant after edits to refresh the snapshot. GREEN (PR #594, 2026-10-23): canonical Linux CI run 39936710786 on 3eefd5fc8 (includes upstream/main 38b0745b3) compares 630 cases, 630 passed with values, 0 failed, 0 deferred, max_rel_err=0, rel_tol=1e-9, no absolute floors. The full differential gate passes all 260 units (177 passed, 83 partial, 0 failed); native runner tests pass (253 passed, 0 failed, 32 ignored), including direct C++/Rust equality for built/unbuilt nonplanar face interior/exterior/edge/vertex queries. That run's only failure was a test-only Clippy chunks_exact suggestion, corrected with this promotion; final-head all-seven CI remains the review gate. All 628 original cases and 568 original passes are retained: 60 former runner-only SmBrep::CreateFaceFromSurface deferrals (4 file fixtures, 56 generated) now compare, plus 2 new saddle boundary fixtures. OpIn::Face independently builds real topology through landed brep p8 and uses the existing BrepCacheFace; build=false stays lazy, statuses/object types/owner tolerances are preserved. No production kernel, shared native_face helper, C++/FFI, seeds, guards or tolerances changed; no primitive/sweep parity improvement is claimed. No local native tests, baseline repeats or C++ builds were run. Prior #533/#537 already supplied the shared trimmed-cache machinery and 568 baseline passes, including seven real holed/nonplanar face probes; those gains are not attributed to this follow-up. Earlier notes' construction/classifier gaps are superseded by the landed adapters and this measured evidence. 11 fns; largest: SmTrimSrfCache::ImplantEdgeuse, SmTrimSrfCache::BuildTree, SmTrimSrfCache::ImplantVertexuse, SmTrimSrfCache::ImplantPolyEdge; cycle back-edges: intersect.point_class(7), query.poly(5), surface.surface_cache_p3(5), query.face_p2(2), topology.face_p2(2), topology.face_p4(2), topology.loopuse(2), topology.vertexuse(2), query.tess(1), surface.surface_p1(1), surface.surface_p3(1), topology.edgeuse_p1(1), topology.face_p3(1), topology.vertex(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit."
|
||||
notes = "FACE-OWNED GetTree (wire: face-owned tree build, 2026-10-28): TrimSrfCache::get_tree_with_face is SmSurfaceCache::GetTree with the virtual BuildTree dispatched to build_tree_with_face; blend.hcr IsPointVisible uses it (its three deferrals compare again). 7 new direct native-face cases (build_mode 2..5 in the faces shim: GetTree after an editing-on build rebuilds standalone; editing turned off before GetTree implants the trims into the existing tree; trimmed tree is not rebuilt; GetTree builds a missing tree), all pass with values, max_rel_err=0. SurfaceCache::get_tree without a face keeps its Unported stub for a face-owned trimmed cache. Earlier: OWNED UV CURVE TOLERANCE (PR #1307, 2026-10-27): ARM64 native gate 772 cases, 772 value-bearing passes, 0 failed/deferred/rejected/vacuous, max_rel_err=0; all 762 baseline cases retained. Ten new nonlinear/control/reimplantation cases exercise the real owning-edge tolerance. EdgeuseInput transfers exact geometry and resolved optional tolerance into retention; BuildTree resolves edgeuse ownership, child subdivision reuses retained geometry without cloning, and handle mapping preserves its tolerance. Ownerless curves retain system tolerance. Native guards keep unrelated coordinate/scalar/z limits unchanged; the larger 1e11 coordinate envelope is isolated to ImplantEu. No core CurveCache or numerical tolerance change. Prior history: POLY-EDGE SUBDIVISION (PR #1016, 2026-10-25): 762 cases, 762 passed with values, 0 failed/deferred/rejected/vacuous, max_rel_err=0, rel_tol=1e-9, no absolute floors. All 630 prior cases retain their value coverage; 128 separate fixed-seed cases and 4 tolerance-boundary fixtures now exercise polygon implantation followed by repeated subdivision, including mixed edge-use lists, zero/negative native tolerances, different original/cache surfaces and handle mapping. SubdivideNode reimplants parent polygon edges head first into child 1 then child 2, preserving the parent lists. Endpoints, tolerance and an exact bounded evaluation copy of the original face surface are retained per handle; reimplant after edits to refresh the snapshot. GREEN (PR #594, 2026-10-23): canonical Linux CI run 39936710786 on 3eefd5fc8 (includes upstream/main 38b0745b3) compares 630 cases, 630 passed with values, 0 failed, 0 deferred, max_rel_err=0, rel_tol=1e-9, no absolute floors. The full differential gate passes all 260 units (177 passed, 83 partial, 0 failed); native runner tests pass (253 passed, 0 failed, 32 ignored), including direct C++/Rust equality for built/unbuilt nonplanar face interior/exterior/edge/vertex queries. That run's only failure was a test-only Clippy chunks_exact suggestion, corrected with this promotion; final-head all-seven CI remains the review gate. All 628 original cases and 568 original passes are retained: 60 former runner-only SmBrep::CreateFaceFromSurface deferrals (4 file fixtures, 56 generated) now compare, plus 2 new saddle boundary fixtures. OpIn::Face independently builds real topology through landed brep p8 and uses the existing BrepCacheFace; build=false stays lazy, statuses/object types/owner tolerances are preserved. No production kernel, shared native_face helper, C++/FFI, seeds, guards or tolerances changed; no primitive/sweep parity improvement is claimed. No local native tests, baseline repeats or C++ builds were run. Prior #533/#537 already supplied the shared trimmed-cache machinery and 568 baseline passes, including seven real holed/nonplanar face probes; those gains are not attributed to this follow-up. Earlier notes' construction/classifier gaps are superseded by the landed adapters and this measured evidence. 11 fns; largest: SmTrimSrfCache::ImplantEdgeuse, SmTrimSrfCache::BuildTree, SmTrimSrfCache::ImplantVertexuse, SmTrimSrfCache::ImplantPolyEdge; cycle back-edges: intersect.point_class(7), query.poly(5), surface.surface_cache_p3(5), query.face_p2(2), topology.face_p2(2), topology.face_p4(2), topology.loopuse(2), topology.vertexuse(2), query.tess(1), surface.surface_p1(1), surface.surface_p3(1), topology.edgeuse_p1(1), topology.face_p3(1), topology.vertex(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit."
|
||||
|
||||
Reference in New Issue
Block a user