Merge pull request #352 from PixelSoftwareStudios/fix/geom.object-shape-box

geom.object: port SmShape::SmShape over a B-rep store; green (follow-up to #329)
This commit is contained in:
bherbruck
2026-10-21 21:53:32 +00:00
committed by GitHub
16 changed files with 921 additions and 540 deletions
+89 -37
View File
@@ -14,26 +14,31 @@
//! - `tcv`: a script of nested `SmTemporaryChangeValue` guards on one variable of
//! type double (`ty` 0), `SmBoolean` (1) or `ULONG` (2); output is the variable
//! after every step and after each remaining guard is destroyed.
//! - `shape`: `SmShape` from a B-rep (none, `SmApiCreateBox`, or a `.smb` file) and a
//! selection of the whole B-rep / faces / edges / vertices. Non-empty shapes need
//! unported topology units and are deferred on the Rust side.
//! - `shape`: `SmShape` from a B-rep (none, the `SmApiCreateBox` solid, or an ASCII
//! `.smb` file, both built by the shared topology fixture; the Rust side imports the
//! C++ export) and a selection of the whole B-rep / faces / edges / vertices, by index
//! into the fixture walk's lists (the import's arena order).
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use serde_json::{json, Value};
use solidcore::geom::attribute::ObjectKey;
use solidcore::geom::context::{Context, NotifyCallback};
use solidcore::geom::object::{
self as obj, NotifyOperation, ObjectRef, Shape, ShapeMember, TemporaryChangeValue,
self as obj, BrepStore, NoBreps, NotifyOperation, ObjectRef, Shape, ShapeMember, TemporaryChangeValue,
};
use solidcore::geom::tree::Tree;
use solidcore::kernel::Kernel;
use solidcore::topology::Brep;
use solidcore::{BrepId, EdgeId, FaceId, SolidError, VertexId};
use solidcore_ffi::units::geom_object::{
self as ffi, tcv_op, NotifyStep, ShapeSelection, ShapeSource, MAX_NOTIFY_OP, NUM_OBJECTS,
};
use super::query_face_p1::{everything, inexact, model};
use super::topology_edgeuse_p1::fixture_input;
use crate::case::CaseContext;
use crate::json::num;
use crate::runner::default_cases;
@@ -130,6 +135,45 @@ fn rust_err(e: &SolidError) -> Output {
Output::unported(e).unwrap_or_else(|| Output::err(format!("rust_error: {e}")))
}
/// Gives the import the B-rep's face, edge and vertex list orders (`GetFaces` /
/// `GetEdges` / `GetVertices`, which order the members of a whole B-rep); the walk
/// numbers faces and vertices in another order.
fn set_list_orders(b: &mut Brep, path: &str, block: &[f64]) -> Result<(), HarnessError> {
let orders = ffi::list_orders(path, block).map_err(|e| harness(format!("c++ list orders: {e}")))?;
let mut at = 0;
let mut next = || -> Result<Vec<u32>, HarnessError> {
let n = *orders.get(at).ok_or_else(|| harness("short list orders"))? as usize;
let list = orders
.get(at + 1..at + 1 + n)
.ok_or_else(|| harness("short list orders"))?
.iter()
.map(|&i| i as u32)
.collect();
at += 1 + n;
Ok(list)
};
let (faces, edges, vertices) = (next()?, next()?, next()?);
let bad = |e: SolidError| harness(format!("list order: {e}"));
b.set_face_list(faces.into_iter().map(FaceId::from_index).collect()).map_err(bad)?;
b.set_edge_list(edges.into_iter().map(EdgeId::from_index).collect()).map_err(bad)?;
b.set_vertex_list(vertices.into_iter().map(VertexId::from_index).collect()).map_err(bad)?;
Ok(())
}
/// The shared topology fixture of a shape's B-rep (`None` without one).
fn fixture_brep(source: &SourceIn) -> Result<Option<Value>, HarnessError> {
Ok(match source {
SourceIn::None => None,
SourceIn::Box([x, y, z, l, w, h]) => Some(json!({
"kind": "box",
"origin": [x, y, z],
"size": [l, w, h],
})),
SourceIn::File(path, true) => Some(json!({"kind": "file", "file": path, "rebuild": false})),
SourceIn::File(_, false) => return Err(harness("the shared fixture reads ASCII files only")),
})
}
// ---- C++ side ----------------------------------------------------------------------
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
@@ -160,11 +204,12 @@ fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
edges,
vertices,
} => {
no_unselected_brep(source, *whole_brep, faces, edges, vertices)?;
let src = match source {
SourceIn::None => ShapeSource::None,
SourceIn::Box(b) => ShapeSource::Box(*b),
SourceIn::File(p, a) => ShapeSource::File(p.clone(), *a),
let src = match fixture_brep(source)? {
None => ShapeSource::None,
Some(brep) => {
let (path, block) = fixture_input(brep)?;
ShapeSource::Fixture { path, block }
}
};
let sel = ShapeSelection {
whole_brep: *whole_brep,
@@ -177,23 +222,6 @@ fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
}
}
/// Both sides refuse a B-rep source with nothing selected: the C++ output then
/// lists `IsInShape` for every entity of that B-rep, which the Rust side (which
/// builds no B-rep) cannot reproduce; with a selection the Rust side defers.
fn no_unselected_brep(
source: &SourceIn,
whole_brep: bool,
faces: &[i64],
edges: &[i64],
vertices: &[i64],
) -> Result<(), HarnessError> {
let empty = !whole_brep && faces.is_empty() && edges.is_empty() && vertices.is_empty();
if empty && !matches!(source, SourceIn::None) {
return Err(harness("B-rep source without a selection"));
}
Ok(())
}
// ---- Rust side ---------------------------------------------------------------------
fn type_string_codes(out: &mut Vec<i64>, s: &str) {
@@ -202,7 +230,7 @@ fn type_string_codes(out: &mut Vec<i64>, s: &str) {
}
fn rust_types(ts: &[i64]) -> Result<Vec<i64>, SolidError> {
let shape = Shape::new(&[], &[], &[], &[])?;
let shape = Shape::new(&Kernel::default(), &NoBreps, &[], &[], &[], &[])?;
let mut o = Vec::new();
for &t in ts {
o.push(i64::from(obj::is_kind_of(t)));
@@ -451,12 +479,21 @@ fn put_tree<O: Copy + PartialEq>(o: &mut Vec<f64>, t: &Tree<O>) -> Result<(), So
}
fn rust_shape(
brep: Option<&Brep>,
whole_brep: bool,
faces: &[i64],
edges: &[i64],
vertices: &[i64],
) -> Result<Vec<f64>, SolidError> {
let b = BrepId::from_index(0);
let pair;
let store: &dyn BrepStore = match brep {
Some(brep) => {
pair = (b, brep);
&pair
}
None => &NoBreps,
};
let breps: Vec<BrepId> = if whole_brep { vec![b] } else { Vec::new() };
let f: Vec<_> = faces
.iter()
@@ -470,21 +507,26 @@ fn rust_shape(
.iter()
.map(|&i| (b, VertexId::from_index(i as u32)))
.collect();
let shape = Shape::new(&breps, &f, &e, &v)?;
let shape = Shape::new(&Kernel::default(), store, &breps, &f, &e, &v)?;
let mut o = vec![
shape.faces().len() as f64,
shape.edges().len() as f64,
shape.vertices().len() as f64,
];
// B-rep list indices of the members (all B-reps are empty here)
// walk indices of the members (the import's arena ids)
o.extend(shape.faces().iter().map(|k| f64::from(k.1.index())));
o.extend(shape.edges().iter().map(|k| f64::from(k.1.index())));
o.extend(shape.vertices().iter().map(|k| f64::from(k.1.index())));
put_tree(&mut o, shape.vertex_tree())?;
put_tree(&mut o, shape.curve_tree())?;
put_tree(&mut o, shape.surface_tree())?;
// IsInShape of every B-rep entity: there is no B-rep without topology support
let _ = shape.is_in_shape(ShapeMember::Other);
// IsInShape of every face, edge and vertex of the B-rep
let flag = |member| if shape.is_in_shape(member) { 1.0 } else { 0.0 };
if let Some(brep) = brep {
o.extend(brep.faces().ids().map(|f| flag(ShapeMember::Face((b, f)))));
o.extend(brep.edges().ids().map(|e| flag(ShapeMember::Edge((b, e)))));
o.extend(brep.vertices().ids().map(|v| flag(ShapeMember::Vertex((b, v)))));
}
Ok(o)
}
@@ -507,11 +549,21 @@ fn rust(i: &Inputs) -> Result<Output, HarnessError> {
vertices,
} => {
let empty = !*whole_brep && faces.is_empty() && edges.is_empty() && vertices.is_empty();
if matches!(source, SourceIn::None) && !empty {
return Err(harness("selection without B-rep"));
}
no_unselected_brep(source, *whole_brep, faces, edges, vertices)?;
Ok(match rust_shape(*whole_brep, faces, edges, vertices) {
let model = match fixture_brep(source)? {
None if !empty => return Err(harness("selection without B-rep")),
None => None,
Some(brep) => {
let mut m = model(&brep)?;
set_list_orders(&mut m.brep, &m.path, &m.block)?;
let (all_faces, all_edges) = everything(&m.brep);
if let Some(o) = inexact(&m, &all_faces, &all_edges) {
return Ok(o);
}
Some(m)
}
};
let brep = model.as_ref().map(|m| &m.brep);
Ok(match rust_shape(brep, *whole_brep, faces, edges, vertices) {
Ok(v) => Output::new().values("v", v),
Err(e) => rust_err(&e),
})
@@ -362,7 +362,8 @@ fn static_rust(case: &Case) -> Result<Output, HarnessError> {
handle: StaticHandle(999),
};
let interval = Extent1d::from_min_max(0.0, 1.0);
let shape = Shape::new(&[], &[], &[], &[]).map_err(|e| HarnessError::new(e.to_string()))?;
let shape = Shape::new(&kernel, &solidcore::geom::object::NoBreps, &[], &[], &[], &[])
.map_err(|e| HarnessError::new(e.to_string()))?;
let result = match i.entry {
0 => TopologySolver::brep_point_solve(
&kernel,
@@ -681,7 +681,13 @@ fn topology(k: &Kernel, i: &Inputs, b: &Brep, second: &Brep) -> Result<Vec<f64>>
objects.build(k)?;
closest(
&mut o,
api::face_closest_point(k, &objects, (BrepId::from_index(0), face), point)?,
api::face_closest_point(
k,
&(BrepId::from_index(0), b),
&objects,
(BrepId::from_index(0), face),
point,
)?,
);
}
21 => cclosest(
+5 -4
View File
@@ -161,14 +161,15 @@ impl solidcore::topology::shell::ShellRayWorld<NoRayObject> for EmptyRayWorld {
}
fn shell_ray_shape(
&self,
kernel: &solidcore::kernel::Kernel,
brep: (solidcore::topology::BrepId, &solidcore::topology::Brep),
faces: &[solidcore::geom::object::FaceKey],
) -> solidcore::Result<solidcore::geom::object::Shape> {
if faces.is_empty() {
return solidcore::geom::object::Shape::new(&[], &[], &[], &[]);
return solidcore::geom::object::Shape::new(kernel, &brep, &[], &[], &[], &[]);
}
// Harness-limited (see the note above): this adapter has no B-rep geometry, and
// a face shape also needs SmFace::CalculateBoundingBox through the face handle
// (the DISPATCH GAP in `geom::object::Shape::new`).
// Harness-limited (see the note above): the shape builds, but this adapter has
// no handles for the B-rep geometry the shape solve then reads.
Err(solidcore::SolidError::unported(
"SmShape::SmShape / SmTopologySolver::ShapePointSolve on boundary faces",
"harness:shell-ray-world",
+73 -23
View File
@@ -11,9 +11,11 @@
//! | [`notify`] | `sc_geom_object_notify` | a script of `SmObject::Notify` calls |
//! | [`tcv`] | `sc_geom_object_tcv` | a script of `SmTemporaryChangeValue<TYPE>` calls |
//! | [`shape`] | `sc_geom_object_shape` | `SmShape::SmShape` and its accessors |
//! | [`list_orders`] | `sc_geom_object_list_orders` | `SmBrep::GetFaces`, `GetEdges`, `GetVertices` of the fixture B-rep |
use std::ffi::CString;
use super::topology_edgeuse_p1 as graph;
use crate::{call_with_buffer, FfiError};
mod sys {
@@ -42,10 +44,18 @@ mod sys {
cap: i64,
out_len: *mut i64,
) -> i32;
pub fn sc_geom_object_shape(
source: i32,
bx: *const f64,
pub fn sc_geom_object_list_orders(
path: *const std::ffi::c_char,
block: *const f64,
n_block: i64,
out: *mut f64,
cap: i64,
out_len: *mut i64,
) -> i32;
pub fn sc_geom_object_shape(
path: *const std::ffi::c_char,
block: *const f64,
n_block: i64,
sel: *const i64,
n_sel: i64,
out: *mut f64,
@@ -217,14 +227,18 @@ pub fn tcv(ty: i32, init: f64, steps: &[(i64, f64)]) -> Result<Vec<f64>, FfiErro
pub enum ShapeSource {
/// No B-rep (only the empty selection is valid).
None,
/// `SmApiCreateBox(origin, length, width, height)`.
Box([f64; 6]),
/// `SmApiReadBrepFromFile(path, ascii)`.
File(String, bool),
/// The B-rep of the shared topology fixture `path` / `block` (the `topology_edgeuse_p1`
/// encoding).
Fixture {
/// The fixture's file path (empty for constructed B-reps).
path: String,
/// The fixture block.
block: Vec<f64>,
},
}
/// Which members of the B-rep go into the shape: the whole B-rep and indices into
/// its `GetFaces` / `GetEdges` / `GetVertices` lists.
/// the fixture walk's face, edge and vertex lists.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ShapeSelection {
/// Add the whole B-rep.
@@ -244,18 +258,25 @@ pub struct ShapeSelection {
pub fn shape(source: &ShapeSource, sel: &ShapeSelection) -> Result<Vec<f64>, FfiError> {
let empty =
!sel.whole_brep && sel.faces.is_empty() && sel.edges.is_empty() && sel.vertices.is_empty();
let (code, bx, path) = match source {
ShapeSource::None if empty => (0, [0.0; 6], None),
let (path, block): (&str, &[f64]) = match source {
ShapeSource::None if empty => ("", &[]),
ShapeSource::None => {
return Err(FfiError::InvalidArgument("selection without B-rep".into()))
}
ShapeSource::Box(b) => (1, *b, None),
ShapeSource::File(p, ascii) => (
2,
[if *ascii { 1.0 } else { 0.0 }, 0.0, 0.0, 0.0, 0.0, 0.0],
Some(CString::new(p.as_str()).map_err(|_| FfiError::InvalidArgument("path".into()))?),
),
ShapeSource::Fixture { path, block } => {
// the graph wrapper validates (and builds) the block first
let export = graph::eval(graph::Op::Export, path, block)?;
if graph::counts(&export).is_none() {
return Err(FfiError::InvalidArgument(
"native fixture construction failed".into(),
));
}
(path, block)
}
};
let path = CString::new(path).map_err(|_| FfiError::InvalidArgument("path".into()))?;
let n_block = i64::try_from(block.len())
.map_err(|_| FfiError::InvalidArgument("block too large".into()))?;
let mut s = vec![
i64::from(sel.whole_brep),
sel.faces.len() as i64,
@@ -265,16 +286,16 @@ pub fn shape(source: &ShapeSource, sel: &ShapeSelection) -> Result<Vec<f64>, Ffi
s.extend(&sel.faces);
s.extend(&sel.edges);
s.extend(&sel.vertices);
let p = path.as_ref().map_or(std::ptr::null(), |c| c.as_ptr());
call_with_buffer(512, |buf: &mut [f64], len| {
// SAFETY: bx holds 6 values; p is NULL or a NUL-terminated string living
// across the call; s holds s.len() values in the documented layout (indices
// are range-checked by the shim); buf is valid for buf.len() writes.
// SAFETY: path is a NUL-terminated string and block holds n_block values (validated
// by the graph wrapper), both living across the call; s holds s.len() values in
// the documented layout (indices are range-checked by the shim); buf is valid for
// buf.len() writes.
unsafe {
sys::sc_geom_object_shape(
code,
bx.as_ptr(),
p,
path.as_ptr(),
block.as_ptr(),
n_block,
s.as_ptr(),
s.len() as i64,
buf.as_mut_ptr(),
@@ -284,3 +305,32 @@ pub fn shape(source: &ShapeSource, sel: &ShapeSelection) -> Result<Vec<f64>, Ffi
}
})
}
/// The fixture B-rep's `GetFaces`, `GetEdges` and `GetVertices` orders as fixture walk
/// indices: `n, indices...` per kind.
pub fn list_orders(path: &str, block: &[f64]) -> Result<Vec<f64>, FfiError> {
let export = graph::eval(graph::Op::Export, path, block)?;
if graph::counts(&export).is_none() {
return Err(FfiError::InvalidArgument(
"native fixture construction failed".into(),
));
}
let path = CString::new(path).map_err(|_| FfiError::InvalidArgument("path".into()))?;
let n_block = i64::try_from(block.len())
.map_err(|_| FfiError::InvalidArgument("block too large".into()))?;
call_with_buffer(256, |buf: &mut [f64], len| {
// SAFETY: path is a NUL-terminated string and block holds n_block values (validated
// by the graph wrapper), both living across the call; buf is valid for buf.len()
// writes.
unsafe {
sys::sc_geom_object_list_orders(
path.as_ptr(),
block.as_ptr(),
n_block,
buf.as_mut_ptr(),
buf.len() as i64,
&raw mut *len,
)
}
})
}
@@ -1495,7 +1495,12 @@ mod tests {
assert_eq!(snapshot.source_before, snapshot.source_after);
assert_eq!(snapshot.foreign_before, snapshot.foreign_after);
let source = ShapeSource::Box([0.0, 0.0, 0.0, 2.0, 3.0, 4.0]);
// the shared fixture's SmApiCreateBox solid (kind 7: origin, size, then the
// validate-only and init-gaps flags)
let source = ShapeSource::Fixture {
path: String::new(),
block: vec![7.0, 0.0, 0.0, 0.0, 2.0, 3.0, 4.0, 0.0, 0.0],
};
let selection = ShapeSelection {
whole_brep: true,
..ShapeSelection::default()
+397 -84
View File
@@ -53,19 +53,37 @@
//! - `Notify`'s debug echo (`SM_ECHO_NOTIFY`), `SmObject::Dump`, `SmShape::Draw`
//! and `GetObjectColor` print or draw only and are not ported. Callback status
//! values are ignored, as upstream.
//! - The shape's topology members are `(BrepId, id)` pairs over a caller-owned
//! B-rep slice. Building a non-empty shape needs the topology and geometry
//! queries of units not ported yet and returns [`SolidError::Unported`]; the
//! empty shape (upstream's `SE_MSG` "no Topology Objects" case, whose message is
//! not ported) is built exactly as upstream.
//! - **Shape members.** They are `(BrepId, id)` keys resolved in a caller's
//! [`BrepStore`] (upstream's are pointers into their B-reps). The curve and surface
//! trees hold the edge and face keys (upstream: the `SmCurve` / `SmSurface`), and
//! a curve's box uses its edge's interval: a port edge or face owns its own
//! geometry, so upstream's `GetOwner()` is the entity itself (no composite
//! topology). The empty shape's `SE_MSG` "no Topology Objects" message is not
//! ported. A B-rep without infinite region listed last has no context to build
//! the trees in (upstream dereferences it): [`SolidError::InvalidInput`], as for a
//! missing face surface, edge curve or edge vertex.
//! - **Marks.** `SmNewMarkAndLock`'s mark is a set of loaded keys per kind: a shape
//! may span B-reps, and the mark is released before the constructor returns, so
//! nothing else observes it. `AddUnique` keeps a set next to each member list.
use crate::error::{Result, SolidError};
use std::collections::BTreeSet;
use crate::error::{ignore_status, ignored, Result, SolidError};
use crate::geom::attribute::ObjectKey;
use crate::geom::context::Context;
use crate::geom::extent3d::Extent3d;
use crate::geom::tree::Tree;
use crate::geom::tree::{NodeGeomType, Tree};
use crate::kernel::Kernel;
use crate::math::Vec3;
use crate::topology::{BrepId, EdgeId, FaceId, VertexId};
use crate::nurbs::b_spline_curve::BoundingBoxRequest;
use crate::query;
use crate::query::brep::BoundingBoxOptions;
use crate::query::edge as qe;
use crate::query::face::p1 as qf1;
use crate::query::face::p2::face_vertices;
use crate::topology::brep::p1 as bp1;
use crate::topology::topology_traverser as traverser;
use crate::topology::{edge, vertex, Brep, BrepId, EdgeId, FaceId, VertexId};
/// `SmObject_TYPE` (`OS_BASE_TYPE + 1`).
pub const OBJECT_TYPE: i64 = 11001;
@@ -372,75 +390,201 @@ impl Shape {
pub const TYPE: i64 = 16500;
/// Builds a shape from whole B-reps, faces, edges and vertices
/// (`SmShape::SmShape`). The lower-level members of a listed entity need not be
/// listed again.
/// (`SmShape::SmShape`), their B-reps resolved in `store`. The lower-level members
/// of a listed entity need not be listed again.
///
/// The trees cover the union of the listed B-reps' boxes, the listed faces' boxes
/// and those of their edges, the listed edges' boxes and the listed vertices; the
/// statuses of these boxes and of the tree insertions are ignored, as upstream (a
/// failed box is the empty one). Each face, edge and vertex is loaded into its tree
/// once: B-reps first, then faces (with their edges and vertices), edges (with
/// their vertices) and vertices.
///
/// With every list empty the shape is empty and its three trees cover the box
/// `(0,0,0)-(1,1,1)` (upstream also reports an `SE_MSG` error and continues).
///
/// # Errors
///
/// [`SolidError::Unported`] for a non-empty shape: its bounding boxes, marks and
/// tree loading need the B-rep, face, edge, vertex and geometry queries of
/// units not ported yet.
/// [`SolidError::InvalidInput`] for a key whose B-rep `store` does not hold or a
/// missing face surface, edge curve or edge vertex (upstream dereferences them), and
/// when the last listed object is a B-rep without infinite region (upstream builds
/// the trees in its null context); those of the topology lists; [`SolidError::Unported`]
/// from the box computations.
pub fn new(
k: &Kernel,
store: &dyn BrepStore,
breps: &[BrepId],
faces: &[FaceKey],
edges: &[EdgeKey],
vertices: &[VertexKey],
) -> Result<Self> {
// Compute the union of all of the extents of the input objects; the first
// upstream call of each list needs an unported topology query.
if !breps.is_empty() {
// DISPATCH GAP: SmBrep::CalculateBoundingBox is ported
// (`query::brep::calculate_bounding_box`, query.brep partial; no UNPORTED
// marker), but it takes the `Brep`, which these B-rep keys do not name.
// SmBrep::GetInfiniteRegion / GetVertices / GetEdges / GetFaces are ported in
// topology::brep::p1.
return Err(SolidError::Unported {
upstream: "SmBrep::CalculateBoundingBox",
unit: "query.brep",
});
}
if !faces.is_empty() {
// DISPATCH GAP: SmFace::CalculateBoundingBox (then SmFace::GetEdges /
// GetVertices) is ported (`query::face::p1::calculate_bounding_box`,
// query.face_p1 partial; no UNPORTED marker), but it takes the face's `Brep`,
// which this object handle does not name. GetSurface/GetFace are ported.
return Err(SolidError::Unported {
upstream: "SmFace::CalculateBoundingBox",
unit: "query.face_p1",
});
}
if !edges.is_empty() {
// UNPORTED(topology.edge): SmEdge::GetCurve (then SmCurve::GetOwner,
// SmEdge::CalculateBoundingBox, SmEdge::GetVertices)
return Err(SolidError::Unported {
upstream: "SmEdge::GetCurve",
unit: "topology.edge",
});
}
if !vertices.is_empty() {
// UNPORTED(topology.vertex): SmVertex::GetPoint (then
// SmTopology::IsMarked / Mark)
return Err(SolidError::Unported {
upstream: "SmVertex::GetPoint",
unit: "topology.vertex",
});
}
// signal an error for empty shapes: specify BBox to be anything reasonable
let brep_of = |id: BrepId| {
store
.brep(id)
.ok_or_else(|| SolidError::invalid_input(OP_NEW, "B-rep not in the store"))
};
let mut members = ShapeMembers::default();
let mut bbox_union = Extent3d::new();
bbox_union.set_min_max(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0))?;
// `pTopo != NULL` for the last listed object (it carries the trees' context)
let mut has_context = false;
// Compute the union of all of the extents of the input objects
for &id in breps {
let b = brep_of(id)?;
has_context = b.infinite_region().is_some();
// (status not checked upstream; the box is written only on success)
let bbox = ignored(query::brep::calculate_bounding_box(
k,
b,
BoundingBoxOptions::default(),
))?
.unwrap_or_default();
bbox_union = bbox_union.union(&bbox);
}
// union in face bounding boxes
for &(id, f) in faces {
let b = brep_of(id)?;
has_context = true;
members.faces.add_unique((id, f));
// (`GetSurface()->GetFace()` is the face itself)
face_surface(b, f)?;
let bbox = ignored(qf1::calculate_bounding_box(k, b, f, false))?.unwrap_or_default();
bbox_union = bbox_union.union(&bbox);
// Load edge->curves of the face
for e in traverser::face_edges(b, f)? {
let bbox = edge_box(k, b, e)?;
bbox_union = bbox_union.union(&bbox);
}
}
// union in edge bounding boxes
for &(id, e) in edges {
let b = brep_of(id)?;
has_context = true;
members.edges.add_unique((id, e));
let bbox = edge_box(k, b, e)?;
bbox_union = bbox_union.union(&bbox);
}
// union in vertex bounding boxes
for &(id, v) in vertices {
let b = brep_of(id)?;
has_context = true;
members.vertices.add_unique((id, v));
let bbox = Extent3d::from_point(vertex::point(b, v)?);
bbox_union = bbox_union.union(&bbox);
}
// signal an error for empty shapes
let empty = breps.is_empty() && faces.is_empty() && edges.is_empty() && vertices.is_empty();
if !has_context {
if !empty {
// `new (*cpContext) SmTree` with a null context
return Err(SolidError::invalid_input(
OP_NEW,
"no context: the last object is a B-rep without infinite region",
));
}
// specify BBox to be anything reasonable
bbox_union.set_min_max(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 1.0, 1.0))?;
}
// init trees for each topology item to be stored
let mut trees = ShapeTrees {
vertices: Tree::new_spatial(&bbox_union),
curves: Tree::new_spatial(&bbox_union),
surfaces: Tree::new_spatial(&bbox_union),
};
// Now add topology item->shapes in a unique way to the appropriate trees
// (`SmNewMarkAndLock`: the sets of loaded keys)
if !empty {
let mut marked = ShapeMarks::default();
for &id in breps {
let b = brep_of(id)?;
// Load Brep vertices into vertex tree
for v in bp1::vertices(b, None)? {
if marked.vertices.insert((id, v)) {
members.vertices.add_unique((id, v));
trees.add_vertex(b, (id, v))?;
}
}
// Now load brep edges into the Edge Tree
for e in bp1::edges(b, None)? {
if marked.edges.insert((id, e)) {
members.edges.add_unique((id, e));
trees.add_curve(k, b, (id, e))?;
}
}
// Now load the brep faces into the Face Tree
for f in bp1::faces(b, None)? {
if marked.faces.insert((id, f)) {
members.faces.add_unique((id, f));
trees.add_surface(k, b, (id, f))?;
}
}
} // end iter every Brep loading its vertices, edge->curves, and face->surfaces to trees
// Load individual face->Surfaces
for &(id, f) in faces {
if !marked.faces.insert((id, f)) {
continue;
}
let b = brep_of(id)?;
trees.add_surface(k, b, (id, f))?;
// Load edge->curves of the face
for e in traverser::face_edges(b, f)? {
if marked.edges.insert((id, e)) {
members.edges.add_unique((id, e));
trees.add_curve(k, b, (id, e))?;
}
}
// Load vertices of the face
for v in face_vertices(b, f, false, None)? {
if marked.vertices.insert((id, v)) {
members.vertices.add_unique((id, v));
trees.add_vertex(b, (id, v))?;
}
}
} // end iter every face loading their vertices, edge->curves, and face->surfaces to trees
// Now load edge objects and adjacent vertices
for &(id, e) in edges {
if !marked.edges.insert((id, e)) {
continue;
}
let b = brep_of(id)?;
trees.add_curve(k, b, (id, e))?;
// Load vertices of the edge
for v in edge::vertices(b, e, None)? {
let v = v.ok_or_else(|| {
SolidError::invalid_input(OP_NEW, "edge vertex use without vertex")
})?;
if marked.vertices.insert((id, v)) {
members.vertices.add_unique((id, v));
trees.add_vertex(b, (id, v))?;
}
}
} // end iter every edge loading their vertices and edge->curves to trees
// Now load vertex objects
for &(id, v) in vertices {
if marked.vertices.insert((id, v)) {
trees.add_vertex(brep_of(id)?, (id, v))?;
}
}
}
// init trees for each topology item to be stored; nothing to load
Ok(Self {
vertex_tree: Tree::new_spatial(&bbox_union),
curve_tree: Tree::new_spatial(&bbox_union),
surface_tree: Tree::new_spatial(&bbox_union),
faces: Vec::new(),
edges: Vec::new(),
vertices: Vec::new(),
vertex_tree: trees.vertices,
curve_tree: trees.curves,
surface_tree: trees.surfaces,
faces: members.faces.list,
edges: members.edges.list,
vertices: members.vertices.list,
})
}
@@ -512,6 +656,151 @@ impl Shape {
}
}
/// The B-reps behind a shape's `(BrepId, id)` keys (upstream's members are pointers
/// into their B-reps).
pub trait BrepStore {
/// The B-rep `id` names, if any.
fn brep(&self, id: BrepId) -> Option<&Brep>;
}
/// The store of a shape with no members (it resolves nothing).
#[derive(Debug, Clone, Copy, Default)]
pub struct NoBreps;
impl BrepStore for NoBreps {
fn brep(&self, _id: BrepId) -> Option<&Brep> {
None
}
}
/// One B-rep under its id.
impl BrepStore for (BrepId, &Brep) {
fn brep(&self, id: BrepId) -> Option<&Brep> {
(id == self.0).then_some(self.1)
}
}
const OP_NEW: &str = "geom::object::Shape::new";
/// The axis-aligned box only.
const NORMAL_BOX: BoundingBoxRequest = BoundingBoxRequest {
normal: true,
pseudo: false,
polar: false,
};
/// A member list with `AddUnique` (`list` in insertion order, `set` for lookups).
struct UniqueList<K> {
list: Vec<K>,
set: BTreeSet<K>,
}
impl<K> Default for UniqueList<K> {
fn default() -> Self {
Self {
list: Vec::new(),
set: BTreeSet::new(),
}
}
}
impl<K: Copy + Ord> UniqueList<K> {
/// `SmTArray::AddUnique`.
fn add_unique(&mut self, key: K) {
if self.set.insert(key) {
self.list.push(key);
}
}
}
/// `m_vFaces`, `m_vEdges` and `m_vVertices` while the shape is built.
#[derive(Default)]
struct ShapeMembers {
faces: UniqueList<FaceKey>,
edges: UniqueList<EdgeKey>,
vertices: UniqueList<VertexKey>,
}
/// The entities the constructor's `SmNewMarkAndLock` mark has marked.
#[derive(Default)]
struct ShapeMarks {
faces: BTreeSet<FaceKey>,
edges: BTreeSet<EdgeKey>,
vertices: BTreeSet<VertexKey>,
}
/// The shape's trees while they are loaded.
struct ShapeTrees {
vertices: Tree<VertexKey>,
curves: Tree<EdgeKey>,
surfaces: Tree<FaceKey>,
}
impl ShapeTrees {
/// `SE(m_pVertexTree->AddToSpatialTree(SmExtent3d(pV->GetPoint()), pV))`.
fn add_vertex(&mut self, b: &Brep, key: VertexKey) -> Result<()> {
let bbox = Extent3d::from_point(vertex::point(b, key.1)?);
ignore_status(
self.vertices
.add_to_spatial_tree(&bbox, Some(key), NodeGeomType::Default),
)
}
/// `SE(pCurve->CalculateBoundingBox(pEdge->GetInterval(), &sEBBox))`, then
/// `SE(m_pCurveTree->AddToSpatialTree(sEBBox, pCurve))` (the curve keyed by its
/// edge).
fn add_curve(&mut self, k: &Kernel, b: &Brep, key: EdgeKey) -> Result<()> {
let e = b.edge(key.1)?;
let curve = e
.curve
.as_ref()
.ok_or_else(|| SolidError::invalid_input(OP_NEW, "edge without curve"))?;
let bbox = ignored(curve.calculate_bounding_box(k, &e.interval, NORMAL_BOX, true))?
.and_then(|boxes| boxes.normal)
.unwrap_or_default();
ignore_status(
self.curves
.add_to_spatial_tree(&bbox, Some(key), NodeGeomType::Default),
)
}
/// `SE(pFace->CalculateBoundingBox(sFBBox))`, then
/// `SE(m_pSurfaceTree->AddToSpatialTree(sFBBox, pSurface))` (the surface keyed by its
/// face).
fn add_surface(&mut self, k: &Kernel, b: &Brep, key: FaceKey) -> Result<()> {
face_surface(b, key.1)?;
let bbox = ignored(qf1::calculate_bounding_box(k, b, key.1, false))?.unwrap_or_default();
ignore_status(
self.surfaces
.add_to_spatial_tree(&bbox, Some(key), NodeGeomType::Default),
)
}
}
/// The surface of face `f` (upstream dereferences a missing one).
fn face_surface(b: &Brep, f: FaceId) -> Result<()> {
b.face(f)?
.surface
.as_ref()
.map(|_| ())
.ok_or_else(|| SolidError::invalid_input(OP_NEW, "face without surface"))
}
/// `SE(((SmEdge*)pE->GetCurve()->GetOwner())->CalculateBoundingBox(&sBBox))`: the
/// edge's loose box (initialised first, so a failure leaves the empty box); upstream
/// dereferences a missing curve.
fn edge_box(k: &Kernel, b: &Brep, e: EdgeId) -> Result<Extent3d> {
b.edge(e)?
.curve
.as_ref()
.ok_or_else(|| SolidError::invalid_input(OP_NEW, "edge without curve"))?;
Ok(
ignored(qe::calculate_bounding_box(k, b, e, NORMAL_BOX, false))?
.and_then(|boxes| boxes.normal)
.unwrap_or_default(),
)
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
@@ -643,7 +932,7 @@ mod tests {
#[test]
fn empty_shape() {
let s = Shape::new(&[], &[], &[], &[]).unwrap();
let s = Shape::new(&Kernel::default(), &NoBreps, &[], &[], &[], &[]).unwrap();
let top = s.vertex_tree().node(NodeId(0)).unwrap();
assert_eq!(top.bbox.min(), Vec3::new(0.0, 0.0, 0.0));
assert_eq!(top.bbox.max(), Vec3::new(1.0, 1.0, 1.0));
@@ -658,27 +947,51 @@ mod tests {
}
#[test]
fn non_empty_shape_is_unported() {
fn shape_of_vertices_loads_each_once() {
let k = Kernel::default();
let mut brep = Brep::new_with_infinite_region().unwrap();
let v0 = brep.add_vertex(Vec3::new(0.0, 0.0, 0.0)).unwrap();
let v1 = brep.add_vertex(Vec3::new(2.0, 3.0, 4.0)).unwrap();
let b = BrepId::from_index(0);
let unit = |r: Result<Shape>| match r {
Err(SolidError::Unported { unit, .. }) => unit,
_ => "",
};
assert_eq!(
unit(Shape::new(&[b], &[(b, FaceId::from_index(0))], &[], &[])),
"query.brep"
);
assert_eq!(
unit(Shape::new(&[], &[(b, FaceId::from_index(0))], &[], &[])),
"query.face_p1"
);
assert_eq!(
unit(Shape::new(&[], &[], &[(b, EdgeId::from_index(0))], &[])),
"topology.edge"
);
assert_eq!(
unit(Shape::new(&[], &[], &[], &[(b, VertexId::from_index(0))])),
"topology.vertex"
);
let store = (b, &brep);
// a listed vertex comes first, then the B-rep's; each is in the tree once
let s = Shape::new(&k, &store, &[b], &[], &[], &[(b, v1)]).unwrap();
assert_eq!(s.vertices(), &[(b, v1), (b, v0)]);
assert_eq!(s.vertex_tree().object_list().unwrap().len(), 2);
assert!(s.faces().is_empty() && s.edges().is_empty());
let top = s.vertex_tree().top_node().unwrap();
let bbox = s.vertex_tree().node(top).unwrap().bbox;
assert!(Extent3d::from_point(Vec3::new(2.0, 3.0, 4.0)).is_contained_by(&bbox, 0.0));
assert!(s.is_in_shape(ShapeMember::Vertex((b, v0))));
}
#[test]
fn shape_keys_must_resolve() {
let k = Kernel::default();
let brep = Brep::new();
let b = BrepId::from_index(0);
let invalid = |r: Result<Shape>| matches!(r, Err(SolidError::InvalidInput { .. }));
// a key whose B-rep the store does not hold
let other = BrepId::from_index(1);
assert!(invalid(Shape::new(
&k,
&(b, &brep),
&[other],
&[],
&[],
&[]
)));
// the last object a B-rep without infinite region: upstream's null context
assert!(invalid(Shape::new(&k, &(b, &brep), &[b], &[], &[], &[])));
// a face that does not exist
assert!(Shape::new(
&k,
&(b, &brep),
&[],
&[(b, FaceId::from_index(0))],
&[],
&[]
)
.is_err());
}
}
+5 -4
View File
@@ -35,7 +35,7 @@ use crate::error::{ignored, EntityRef, Result, SolidError};
use crate::geom::extent1d::Extent1d;
use crate::geom::extent3d::Extent3d;
use crate::geom::global_solver::{SolutionRequested, SolverOperation};
use crate::geom::object::{FaceKey, ObjectRef, Shape, ShapeMember};
use crate::geom::object::{BrepStore, FaceKey, ObjectRef, Shape, ShapeMember};
use crate::geom::solution_array::{ObjectKind, Solution, SolutionArray, SolutionObject};
use crate::geom::tools::{self, EdgePick};
use crate::intersect::point_class::{PointClassType, PointClassification};
@@ -660,15 +660,16 @@ pub fn face_classify_uv(
Ok(classification.point_class())
}
/// Trim-aware closest point on one face (`SmApiFaceClosestPoint`).
/// Shape construction and solver errors propagate.
/// Trim-aware closest point on one face (`SmApiFaceClosestPoint`), the face's B-rep
/// held in `breps`. Shape construction and solver errors propagate.
pub fn face_closest_point<O: SolutionObject>(
k: &Kernel,
breps: &dyn BrepStore,
objects: &dyn TopologyObjects<O>,
face: FaceKey,
point: Point3,
) -> Result<ClosestPoint> {
let shape = Shape::new(&[], &[face], &[], &[])?;
let shape = Shape::new(k, breps, &[], &[face], &[], &[])?;
let mut options = solve_options(k, SolutionRequested::Single);
options.tolerance_3d =
objects.face_tolerance(objects.shape_member_object(ShapeMember::Face(face))?)?;
+8 -1
View File
@@ -2035,7 +2035,14 @@ fn faces_to_delete(
// Make a face shape with the face and its edges and vertices.
let edge_keys: Vec<_> = edges.iter().map(|&e| (env.brep_id, e)).collect();
let vertex_keys: Vec<_> = vertices.iter().map(|&v| (env.brep_id, v)).collect();
let shape = Shape::new(&[], &[(env.brep_id, face)], &edge_keys, &vertex_keys)?;
let shape = Shape::new(
k,
&(env.brep_id, b),
&[],
&[(env.brep_id, face)],
&edge_keys,
&vertex_keys,
)?;
// Find a ray to determine which face needs to be kept or deleted
let bbox = qbrep::calculate_bounding_box(k, b, BoundingBoxOptions::default())?;
+11 -6
View File
@@ -889,11 +889,16 @@ pub struct ShellRaySectorAttempt {
/// Actual geometry and owner context needed by shell ray queries.
/// No global owner registry or duplicate geometry metadata is introduced.
pub trait ShellRayWorld<O>: crate::intersect::topology_solver::p1::TopologyObjects<O> {
/// The shape of the shell's boundary faces (`SmShape(sBreps, sFaces, ...)`).
/// Worlds that cannot build the real shape (a harness without the B-rep's cached
/// geometry) defer here instead of failing later.
fn shell_ray_shape(&self, faces: &[crate::geom::object::FaceKey]) -> Result<Shape> {
Shape::new(&[], faces, &[], &[])
/// The shape of the shell's boundary faces (`SmShape(sBreps, sFaces, ...)`) of
/// `brep`. Worlds that cannot build the real shape (a harness without the B-rep's
/// cached geometry) defer here instead of failing later.
fn shell_ray_shape(
&self,
kernel: &Kernel,
brep: (crate::topology::BrepId, &Brep),
faces: &[crate::geom::object::FaceKey],
) -> Result<Shape> {
Shape::new(kernel, &brep, &[], faces, &[], &[])
}
/// The topology entity behind a handle the shape solve returned (a vertex, edge
@@ -1004,7 +1009,7 @@ where
}
Ok::<_, SolidError>(faces)
})??;
let shape = world.shell_ray_shape(&faces)?;
let shape = world.shell_ray_shape(kernel, (brep_id, brep), &faces)?;
let vectors = [direction];
let solutions = TopologySolver::shape_point_solve(
&*kernel,
+143 -80
View File
@@ -2,13 +2,16 @@
// difftest shim for unit `geom.object`. Wraps upstream SmObject (type system, Notify,
// AssertValid), SmTemporaryChangeValue<TYPE> (source/SMLib/inc/SmObject.h,
// source/SMLib/src/SmObject.cpp) and SmShape (source/SMLib/inc/SmShape.h,
// source/SMLib/src/SmShape.cpp). B-reps for shapes come from SmApiCreateBox /
// SmApiReadBrepFromFile (source/SM_API).
// source/SMLib/src/SmShape.cpp). B-reps for shapes come from the shared B-rep fixture
// (support/topology_fixture.h), whose walk numbers their entities as the Rust import does.
#include "sc_shim.h"
#include <SmApiImportExport.h>
#include <SmApiPrimitives.h>
#include "support/topology_fixture.h"
#include <SmBrep.h>
#include <SmContext.h>
#include <SmEdge.h>
#include <SmFace.h>
#include <SmRegion.h>
#include <SmVertex.h>
#include <SmObject.h>
#include <SmShape.h>
#include <SmTree.h>
@@ -125,9 +128,9 @@ void put_tree(std::vector<double>& res, const SmTree* pTree)
}
template <typename P>
int64_t index_of(const SmTArray<P*>& all, P* p)
int64_t index_of(const std::vector<P*>& all, P* p)
{
for (ULONG i = 0; i < all.GetSize(); ++i)
for (size_t i = 0; i < all.size(); ++i)
if (all[i] == p)
return (int64_t)i;
return -1;
@@ -264,49 +267,15 @@ SC_EXPORT int32_t sc_geom_object_tcv(int32_t type, double init, const int64_t* o
SC_CATCH
}
// SmShape. source 0 = no B-rep (all lists empty), 1 = SmApiCreateBox(box[0..3] origin,
// box[3..6] size), 2 = SmApiReadBrepFromFile(path, ascii = box[0] != 0). sel = (whole_brep, n_f, n_e,
// n_v, face indices..., edge indices..., vertex indices...) into the B-rep's GetFaces /
// GetEdges / GetVertices lists. Output: (#faces, #edges, #vertices of the shape, their
// B-rep list indices in shape order), each tree (node count, object count, has top,
// top box), then IsInShape of every B-rep face, edge, vertex.
SC_EXPORT int32_t sc_geom_object_shape(int32_t source, const double* box, const char* path,
const int64_t* sel, int64_t n_sel, double* out,
int64_t cap, int64_t* out_len)
namespace
{
// The shape of `sel` over `pBrep` (null: no B-rep) whose faces, edges and vertices
// are numbered by `vAllF` / `vAllE` / `vAllV`; see sc_geom_object_shape.
int32_t shape_of(SmBrep* pBrep, const std::vector<SmFace*>& vAllF,
const std::vector<SmEdge*>& vAllE, const std::vector<SmVertex*>& vAllV,
const int64_t* sel, int64_t nf, int64_t ne, int64_t nv,
std::vector<double>& res)
{
if (source < 0 || source > 2 || n_sel < 4)
return SC_ERR_ARG;
int64_t nf = sel[1], ne = sel[2], nv = sel[3];
if (sel[0] < 0 || sel[0] > 1 || nf < 0 || ne < 0 || nv < 0 || 4 + nf + ne + nv != n_sel)
return SC_ERR_ARG;
if (source == 0 && (sel[0] != 0 || nf + ne + nv != 0))
return SC_ERR_ARG;
if (source == 2 && path == nullptr)
return SC_ERR_ARG;
SC_TRY
SmBrep* pBrep = nullptr;
if (source == 1)
{
if (SmApiCreateBox(SmVector3d(box[0], box[1], box[2]), box[3], box[4], box[5], pBrep) !=
SM_SUCCESS ||
pBrep == nullptr)
return 1;
}
else if (source == 2)
{
if (SmApiReadBrepFromFile(path, box[0] != 0.0 ? TRUE : FALSE, FALSE, pBrep) != SM_SUCCESS || pBrep == nullptr)
return 1;
}
SmObjDelete sCleanBrep(pBrep);
SmTArray<SmFace*> vAllF;
SmTArray<SmEdge*> vAllE;
SmTArray<SmVertex*> vAllV;
if (pBrep)
{
pBrep->GetFaces(vAllF);
pBrep->GetEdges(vAllE);
pBrep->GetVertices(vAllV);
}
SmTArray<SmBrep*> sB;
SmTArray<SmFace*> sF;
SmTArray<SmEdge*> sE;
@@ -316,50 +285,144 @@ SC_EXPORT int32_t sc_geom_object_shape(int32_t source, const double* box, const
const int64_t* idx = sel + 4;
for (int64_t i = 0; i < nf; ++i, ++idx)
{
if (*idx < 0 || *idx >= (int64_t)vAllF.GetSize())
if (*idx < 0 || *idx >= (int64_t)vAllF.size())
return SC_ERR_ARG;
sF.Add(vAllF[(ULONG)*idx]);
sF.Add(vAllF[(size_t)*idx]);
}
for (int64_t i = 0; i < ne; ++i, ++idx)
{
if (*idx < 0 || *idx >= (int64_t)vAllE.GetSize())
if (*idx < 0 || *idx >= (int64_t)vAllE.size())
return SC_ERR_ARG;
sE.Add(vAllE[(ULONG)*idx]);
sE.Add(vAllE[(size_t)*idx]);
}
for (int64_t i = 0; i < nv; ++i, ++idx)
{
if (*idx < 0 || *idx >= (int64_t)vAllV.GetSize())
if (*idx < 0 || *idx >= (int64_t)vAllV.size())
return SC_ERR_ARG;
sV.Add(vAllV[(ULONG)*idx]);
sV.Add(vAllV[(size_t)*idx]);
}
std::vector<double> res;
// SmShape dereferences a face's surface and an edge's curve (a listed face's edges
// included)
for (ULONG i = 0; i < sF.GetSize(); ++i)
{
SmShape sShape(sB, sF, sE, sV);
SmTArray<SmFace*> vF;
SmTArray<SmEdge*> vE;
SmTArray<SmVertex*> vV;
sShape.GetFaces(vF);
sShape.GetEdges(vE);
sShape.GetVertices(vV);
res.push_back((double)vF.GetSize());
res.push_back((double)vE.GetSize());
res.push_back((double)vV.GetSize());
for (ULONG i = 0; i < vF.GetSize(); ++i)
res.push_back((double)index_of(vAllF, vF[i]));
for (ULONG i = 0; i < vE.GetSize(); ++i)
res.push_back((double)index_of(vAllE, vE[i]));
for (ULONG i = 0; i < vV.GetSize(); ++i)
res.push_back((double)index_of(vAllV, vV[i]));
put_tree(res, sShape.GetVertexTree());
put_tree(res, sShape.GetCurveTree());
put_tree(res, sShape.GetSurfaceTree());
for (ULONG i = 0; i < vAllF.GetSize(); ++i)
res.push_back(sShape.IsInShape(vAllF[i]) ? 1.0 : 0.0);
for (ULONG i = 0; i < vAllE.GetSize(); ++i)
res.push_back(sShape.IsInShape(vAllE[i]) ? 1.0 : 0.0);
for (ULONG i = 0; i < vAllV.GetSize(); ++i)
res.push_back(sShape.IsInShape(vAllV[i]) ? 1.0 : 0.0);
if (sF[i]->GetSurface() == nullptr)
return SC_ERR_ARG;
SmTArray<SmEdge*> vFaceE;
sF[i]->GetEdges(vFaceE);
for (ULONG j = 0; j < vFaceE.GetSize(); ++j)
if (vFaceE[j]->GetCurve() == nullptr)
return SC_ERR_ARG;
}
for (ULONG i = 0; i < sE.GetSize(); ++i)
if (sE[i]->GetCurve() == nullptr)
return SC_ERR_ARG;
if (sel[0])
{
for (SmFace* f : vAllF)
if (f->GetSurface() == nullptr)
return SC_ERR_ARG;
for (SmEdge* e : vAllE)
if (e->GetCurve() == nullptr)
return SC_ERR_ARG;
// the last listed object gives the trees their context
if (nf + ne + nv == 0 && pBrep->GetInfiniteRegion() == nullptr)
return SC_ERR_ARG;
}
SmShape sShape(sB, sF, sE, sV);
SmTArray<SmFace*> vF;
SmTArray<SmEdge*> vE;
SmTArray<SmVertex*> vV;
sShape.GetFaces(vF);
sShape.GetEdges(vE);
sShape.GetVertices(vV);
res.push_back((double)vF.GetSize());
res.push_back((double)vE.GetSize());
res.push_back((double)vV.GetSize());
for (ULONG i = 0; i < vF.GetSize(); ++i)
res.push_back((double)index_of(vAllF, vF[i]));
for (ULONG i = 0; i < vE.GetSize(); ++i)
res.push_back((double)index_of(vAllE, vE[i]));
for (ULONG i = 0; i < vV.GetSize(); ++i)
res.push_back((double)index_of(vAllV, vV[i]));
put_tree(res, sShape.GetVertexTree());
put_tree(res, sShape.GetCurveTree());
put_tree(res, sShape.GetSurfaceTree());
for (SmFace* f : vAllF)
res.push_back(sShape.IsInShape(f) ? 1.0 : 0.0);
for (SmEdge* e : vAllE)
res.push_back(sShape.IsInShape(e) ? 1.0 : 0.0);
for (SmVertex* v : vAllV)
res.push_back(sShape.IsInShape(v) ? 1.0 : 0.0);
return 0;
}
} // namespace
// SmShape over the B-rep of the shared fixture `path` / `block` (`n_block == 0`: no
// B-rep, all lists empty). sel = (whole_brep, n_f, n_e, n_v, face indices..., edge
// indices..., vertex indices...) into the fixture walk's face / edge / vertex lists.
// Output: (#faces, #edges, #vertices of the shape, their walk indices in shape order),
// each tree (node count, object count, has top, top box), then IsInShape of every
// face, edge and vertex of the B-rep.
SC_EXPORT int32_t sc_geom_object_shape(const char* path, const double* block,
int64_t n_block, const int64_t* sel, int64_t n_sel,
double* out, int64_t cap, int64_t* out_len)
{
if (n_block < 0 || (n_block > 0 && (path == nullptr || block == nullptr)) || sel == nullptr ||
n_sel < 4 || out_len == nullptr)
return SC_ERR_ARG;
int64_t nf = sel[1], ne = sel[2], nv = sel[3];
if (sel[0] < 0 || sel[0] > 1 || nf < 0 || ne < 0 || nv < 0 || 4 + nf + ne + nv != n_sel)
return SC_ERR_ARG;
if (n_block == 0 && (sel[0] != 0 || nf + ne + nv != 0))
return SC_ERR_ARG;
SC_TRY
std::vector<double> res;
int32_t rc = 0;
if (n_block == 0)
rc = shape_of(nullptr, {}, {}, {}, sel, nf, ne, nv, res);
else
rc = sc_topology_fixture::apply(path, block, n_block,
[&](const sc_topology_fixture::View& g) -> int32_t {
return shape_of(&g.brep, g.faces, g.edges,
g.vertices, sel, nf, ne, nv, res);
});
if (rc != 0)
return rc;
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
SC_CATCH
}
// The fixture B-rep's GetFaces, GetEdges and GetVertices orders as fixture walk indices:
// (n, indices...) per kind (the Rust import's list orders).
SC_EXPORT int32_t sc_geom_object_list_orders(const char* path, const double* block,
int64_t n_block, double* out, int64_t cap,
int64_t* out_len)
{
if (path == nullptr || block == nullptr || n_block < 1 || out_len == nullptr)
return SC_ERR_ARG;
SC_TRY
std::vector<double> res;
int32_t rc = sc_topology_fixture::apply(
path, block, n_block, [&](const sc_topology_fixture::View& g) -> int32_t {
SmTArray<SmFace*> vF;
SmTArray<SmEdge*> vE;
SmTArray<SmVertex*> vV;
g.brep.GetFaces(vF);
g.brep.GetEdges(vE);
g.brep.GetVertices(vV);
res.push_back((double)vF.GetSize());
for (ULONG i = 0; i < vF.GetSize(); ++i)
res.push_back((double)index_of(g.faces, vF[i]));
res.push_back((double)vE.GetSize());
for (ULONG i = 0; i < vE.GetSize(); ++i)
res.push_back((double)index_of(g.edges, vE[i]));
res.push_back((double)vV.GetSize());
for (ULONG i = 0; i < vV.GetSize(); ++i)
res.push_back((double)index_of(g.vertices, vV[i]));
return 0;
});
if (rc != 0)
return rc;
return sc_copy_out(res.data(), (int64_t)res.size(), out, cap, out_len);
SC_CATCH
}
+4 -3
View File
@@ -33,9 +33,10 @@ decimation attributes and `SmApiEvaluateSurface*`); `geom.a_object` and
The merge passes go through `boolean::merge::p1::classify_point_editing_disabled`.
- **Lone B-reps** use `BrepIdentity::default()`; B-reps in a store pass their own
`BrepIdentity`.
- **Open follow-ups** (no PR yet): `topology::brep::p10::merge_curves_world` is a
`DISPATCH GAP` until the merge-curves world can own the B-rep (`topology.brep_p2`), and
`geom.object`'s `Shape` cannot reach the B-rep box without a store.
- **Shapes:** `geom::object::Shape::new` takes a `BrepStore` (`(BrepId, &Brep)` for one
B-rep, `NoBreps` for an empty shape) and is fully ported; `geom.object` is green.
- **Open follow-up:** `topology::brep::p10::merge_curves_world` is a `DISPATCH GAP` until
the merge-curves world can own the B-rep (`topology.brep_p2`; PR #348).
## Port review note (2026-10-18, issue #210)
+8 -2
View File
@@ -13050,8 +13050,14 @@ guarded in `offset.offset_geometry_creation`; the fix belongs to the curve cache
world separately, so no world can read the B-rep's geometry during the call;
`topology::brep::p10::merge_curves_world` stays a `DISPATCH GAP` until the world can
own the B-rep (as `boolean::merge::p4::ArenaPair` does).
- `geom.object`: `Shape::new` gets B-rep keys but no store to resolve them, so the B-rep
box (and the face box, already a `DISPATCH GAP`) cannot be reached there.
- `geom.object` (done in the follow-up PR): `Shape::new` got B-rep keys but no store to
resolve them, so the B-rep and face boxes were `DISPATCH GAP`s (and the edge and vertex
lists stale `UNPORTED` stubs). It now takes a `BrepStore` and runs the whole
constructor; the runner's C++ shape is built on the shared fixture B-rep, and the Rust
import gets upstream's `GetFaces` / `GetVertices` orders (the walk numbers them
differently; whole-B-rep shapes list their members in those orders). `geom.object` is
green. `query.api_queries`' 31 `SmApiFaceClosestPoint` cases that stopped at the face
box now build their shape and defer later (`topology.face_p2` 23, `geom.tol` 8).
- **Wider seeds (2000 generated cases per unit, local macOS run).** `query.brep`,
`boolean.merge_p1`, `boolean.merge_p4` and `topology.brep_p9` show no failures. Two units
fail on generated cases this PR does not change; both are left to their owners:
+2 -2
View File
@@ -333,10 +333,10 @@ soft_depends_on = ["geom.attribute"]
est_lines = 673
layer = 3
tests = ["prog_test:my_test_polynomial", "SM_API_test:TestSmPrincipalDirectionPairing", "prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test:my_test_surface_closest_point", "prog_test:my_test_drop_point"]
status = "partial"
status = "green"
claimed_by = "swarm-geom.object-a1"
claimed_at = "2026-10-12T20:05:41Z"
notes = "difftest: 624 cases (24 fixture + 600 generated), 593 pass bit-identical, 31 deferred (query.face_p1 11, topology.brep_p1 10, topology.vertex 6, topology.edge 4: every non-empty SmShape); NotifyCallback::execute added to geom.context (stays green); UNPORTED(geom.object) note in geom.tol retagged to topology.topology (SmTol object overloads need topology/curve/surface object types). 38 fns; largest: SmShape::SmShape, SmObject::Notify, SmThreadLocalStorage::SetBeenThroughNew, SmThreadLocalStorage::GetBeenThroughNew; cycle back-edges (call into later units; stub or co-port): query.poly(24), topology.proto_topology_p1(20), blend.fillet_solver_p1(16), blend.fillet_standard_solver_p1(12), blend.fillet_geom_p1(10), query.brep(7), blend.fillet_custom_solver(6), blend.fillet_executive_p1(6), geom.a_object(6), geom.grid(6), intersect.point_class(6), intersect.ray_tracer(6), query.tess(6), topology.edge(5), topology.topology(5), curve.composite_curve_p1(4), surface.cone_p1(4), topology.brep_p1(4), topology.loopuse(4), topology.shell(4), curve.curve_p2(3), surface.surface_p1(3), surface.surface_p2(3), topology.vertex(3), blend.fillet_corner_p1(2), boolean.merge_p1(2), boolean.topology_intersector_p1(2), curve.conic(2), curve.crv_in_volume(2), curve.crv_on_surf(2), curve.curve_cache(2), curve.ellipse_p1(2), curve.ellipse_p2(2), curve.hermite_curve(2), curve.hyperbola(2), curve.iso_curve(2), curve.line(2), curve.offset_curve(2), curve.parabola(2), curve.projected_curve(2), curve.tangent_field(2), geom.solution_array(2), geom.transform(2), intersect.curve_class_p1(2), nurbs.b_spline_curve_p1(2), nurbs.b_spline_surface_p1(2), surface.b_spline_volume(2), surface.bend_volume(2), surface.curve_bounded_surface(2), surface.offset_surface(2), surface.plane_p1(2), surface.sphere(2), surface.srf_in_volume(2), surface.surf_of_extrusion_p1(2), surface.surf_of_revolution_p1(2), surface.surface_cache_p1(2), surface.surface_cache_p2(2), surface.torus(2), surface.trim_srf_cache(2), surface.twist_volume(2), surface.unbend_volume(2), surface.volume_p1(2), sweep.topology_sweep(2), topology.brep_cache(2), topology.brep_data(2), topology.brep_p4(2), topology.edgeuse_p1(2), topology.edgeuse_p2(2), topology.face_p1(2), topology.face_p4(2), topology.vertexuse(2), curve.curve_p6(1), query.edge(1), query.face_p1(1), topology.face_p2(1); unit-SCC #22 (220 units); hazards: thread_local; 321 upstream test entry points reach this unit PR #125: non-empty face Shape construction now names the actual first missing query, SmFace::CalculateBoundingBox (query.face_p1); 624 cases, 593 compared bit-identical, 31 deferred, perturb 593/593. UPDATE after topology.brep_p1 (PR #145): SmBrep::GetInfiniteRegion is ported, so the SmShape(breps) stub now names its first missing callee, SmBrep::CalculateBoundingBox (query.brep); re-run 624 cases, 593 pass, 0 fail, 31 deferred (query.face_p1 11, query.brep 10, topology.vertex 6, topology.edge 4)."
notes = "GREEN (shape follow-up to PR #329): SmShape::SmShape ported over a caller BrepStore (B-rep, face and edge boxes, marked tree loading); the runner builds shapes on the shared fixture B-rep (SmApiCreateBox solid) with the import given upstream's GetFaces/GetEdges/GetVertices orders. difftest 624 cases (24 fixture + 600 generated), 624 pass bit-identical, 0 deferred, --perturb SENSITIVE 624/624, --seeds 2000: 2024/2024. Before: 593 pass, 31 deferred (query.face_p1 11, topology.brep_p1 10, topology.vertex 6, topology.edge 4: every non-empty SmShape). NotifyCallback::execute added to geom.context (stays green); UNPORTED(geom.object) note in geom.tol retagged to topology.topology (SmTol object overloads need topology/curve/surface object types). 38 fns; largest: SmShape::SmShape, SmObject::Notify, SmThreadLocalStorage::SetBeenThroughNew, SmThreadLocalStorage::GetBeenThroughNew; cycle back-edges (call into later units; stub or co-port): query.poly(24), topology.proto_topology_p1(20), blend.fillet_solver_p1(16), blend.fillet_standard_solver_p1(12), blend.fillet_geom_p1(10), query.brep(7), blend.fillet_custom_solver(6), blend.fillet_executive_p1(6), geom.a_object(6), geom.grid(6), intersect.point_class(6), intersect.ray_tracer(6), query.tess(6), topology.edge(5), topology.topology(5), curve.composite_curve_p1(4), surface.cone_p1(4), topology.brep_p1(4), topology.loopuse(4), topology.shell(4), curve.curve_p2(3), surface.surface_p1(3), surface.surface_p2(3), topology.vertex(3), blend.fillet_corner_p1(2), boolean.merge_p1(2), boolean.topology_intersector_p1(2), curve.conic(2), curve.crv_in_volume(2), curve.crv_on_surf(2), curve.curve_cache(2), curve.ellipse_p1(2), curve.ellipse_p2(2), curve.hermite_curve(2), curve.hyperbola(2), curve.iso_curve(2), curve.line(2), curve.offset_curve(2), curve.parabola(2), curve.projected_curve(2), curve.tangent_field(2), geom.solution_array(2), geom.transform(2), intersect.curve_class_p1(2), nurbs.b_spline_curve_p1(2), nurbs.b_spline_surface_p1(2), surface.b_spline_volume(2), surface.bend_volume(2), surface.curve_bounded_surface(2), surface.offset_surface(2), surface.plane_p1(2), surface.sphere(2), surface.srf_in_volume(2), surface.surf_of_extrusion_p1(2), surface.surf_of_revolution_p1(2), surface.surface_cache_p1(2), surface.surface_cache_p2(2), surface.torus(2), surface.trim_srf_cache(2), surface.twist_volume(2), surface.unbend_volume(2), surface.volume_p1(2), sweep.topology_sweep(2), topology.brep_cache(2), topology.brep_data(2), topology.brep_p4(2), topology.edgeuse_p1(2), topology.edgeuse_p2(2), topology.face_p1(2), topology.face_p4(2), topology.vertexuse(2), curve.curve_p6(1), query.edge(1), query.face_p1(1), topology.face_p2(1); unit-SCC #22 (220 units); hazards: thread_local; 321 upstream test entry points reach this unit PR #125: non-empty face Shape construction now names the actual first missing query, SmFace::CalculateBoundingBox (query.face_p1); 624 cases, 593 compared bit-identical, 31 deferred, perturb 593/593. UPDATE after topology.brep_p1 (PR #145): SmBrep::GetInfiniteRegion is ported, so the SmShape(breps) stub now names its first missing callee, SmBrep::CalculateBoundingBox (query.brep); re-run 624 cases, 593 pass, 0 fail, 31 deferred (query.face_p1 11, query.brep 10, topology.vertex 6, topology.edge 4)."
[[unit]]
id = "geom.polynomial"
+97 -226
View File
@@ -154,64 +154,44 @@
"values_compared": 30
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "shape_whole_box_brep",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/geom.object/cases.json#19 (upstream tests/prog_test/src/smTopoTest.cpp:2647)",
"values_compared": 0
"values_compared": 82
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "shape_single_face",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/geom.object/cases.json#20 (upstream source/SM_API/src/SmApiQueries.cpp:1217)",
"values_compared": 0
"values_compared": 65
},
{
"deferred": {
"symbol": "SmEdge::GetCurve",
"unit": "topology.edge"
},
"max_rel_err": 0.0,
"name": "shape_edges_and_vertices",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/geom.object/cases.json#21 (upstream source/SMLib/src/SmShape.cpp:235)",
"values_compared": 0
"values_compared": 62
},
{
"deferred": {
"symbol": "SmVertex::GetPoint",
"unit": "topology.vertex"
},
"max_rel_err": 0.0,
"name": "shape_vertices_only",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/geom.object/cases.json#22 (upstream source/SMLib/src/SmShape.cpp:107)",
"values_compared": 0
"values_compared": 58
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "shape_brep_and_face",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "fixture:fixtures/geom.object/cases.json#23 (upstream source/SMLib/src/SmShape.cpp:137)",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -774,16 +754,12 @@
"values_compared": 14
},
{
"deferred": {
"symbol": "SmEdge::GetCurve",
"unit": "topology.edge"
},
"max_rel_err": 0.0,
"name": "gen0070",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407500",
"values_compared": 0
"values_compared": 63
},
{
"max_rel_err": 0.0,
@@ -1018,16 +994,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0100",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407530",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -1726,16 +1698,12 @@
"values_compared": 13
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0188",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407618",
"values_compared": 0
"values_compared": 74
},
{
"max_rel_err": 0.0,
@@ -2178,16 +2146,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0244",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407674",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -2246,16 +2210,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0252",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407682",
"values_compared": 0
"values_compared": 65
},
{
"max_rel_err": 0.0,
@@ -2362,16 +2322,12 @@
"values_compared": 30
},
{
"deferred": {
"symbol": "SmVertex::GetPoint",
"unit": "topology.vertex"
},
"max_rel_err": 0.0,
"name": "gen0266",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407696",
"values_compared": 0
"values_compared": 57
},
{
"max_rel_err": 0.0,
@@ -2510,16 +2466,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmEdge::GetCurve",
"unit": "topology.edge"
},
"max_rel_err": 0.0,
"name": "gen0284",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407714",
"values_compared": 0
"values_compared": 66
},
{
"max_rel_err": 0.0,
@@ -2682,16 +2634,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0305",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407735",
"values_compared": 0
"values_compared": 65
},
{
"max_rel_err": 0.0,
@@ -2870,16 +2818,12 @@
"values_compared": 16
},
{
"deferred": {
"symbol": "SmVertex::GetPoint",
"unit": "topology.vertex"
},
"max_rel_err": 0.0,
"name": "gen0328",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407758",
"values_compared": 0
"values_compared": 58
},
{
"max_rel_err": 0.0,
@@ -3050,16 +2994,12 @@
"values_compared": 9
},
{
"deferred": {
"symbol": "SmVertex::GetPoint",
"unit": "topology.vertex"
},
"max_rel_err": 0.0,
"name": "gen0350",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407780",
"values_compared": 0
"values_compared": 58
},
{
"max_rel_err": 0.0,
@@ -3126,16 +3066,12 @@
"values_compared": 10
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0359",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407789",
"values_compared": 0
"values_compared": 74
},
{
"max_rel_err": 0.0,
@@ -3146,16 +3082,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0361",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407791",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -3286,16 +3218,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0378",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407808",
"values_compared": 0
"values_compared": 65
},
{
"max_rel_err": 0.0,
@@ -3466,16 +3394,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0400",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407830",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -3646,16 +3570,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmEdge::GetCurve",
"unit": "topology.edge"
},
"max_rel_err": 0.0,
"name": "gen0422",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407852",
"values_compared": 0
"values_compared": 62
},
{
"max_rel_err": 0.0,
@@ -3690,16 +3610,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0427",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407857",
"values_compared": 0
"values_compared": 65
},
{
"max_rel_err": 0.0,
@@ -3718,16 +3634,12 @@
"values_compared": 36
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0430",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407860",
"values_compared": 0
"values_compared": 75
},
{
"max_rel_err": 0.0,
@@ -3746,16 +3658,12 @@
"values_compared": 28
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0433",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407863",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -3790,16 +3698,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmVertex::GetPoint",
"unit": "topology.vertex"
},
"max_rel_err": 0.0,
"name": "gen0438",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407868",
"values_compared": 0
"values_compared": 57
},
{
"max_rel_err": 0.0,
@@ -3986,28 +3890,20 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0462",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407892",
"values_compared": 0
"values_compared": 65
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0463",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407893",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -4098,16 +3994,12 @@
"values_compared": 18
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0475",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407905",
"values_compared": 0
"values_compared": 78
},
{
"max_rel_err": 0.0,
@@ -4302,16 +4194,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0500",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407930",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -4322,16 +4210,12 @@
"values_compared": 33
},
{
"deferred": {
"symbol": "SmBrep::CalculateBoundingBox",
"unit": "query.brep"
},
"max_rel_err": 0.0,
"name": "gen0502",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407932",
"values_compared": 0
"values_compared": 82
},
{
"max_rel_err": 0.0,
@@ -4366,16 +4250,12 @@
"values_compared": 0
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
},
"max_rel_err": 0.0,
"name": "gen0507",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407937",
"values_compared": 0
"values_compared": 77
},
{
"max_rel_err": 0.0,
@@ -4826,16 +4706,12 @@
"values_compared": 7
},
{
"deferred": {
"symbol": "SmVertex::GetPoint",
"unit": "topology.vertex"
},
"max_rel_err": 0.0,
"name": "gen0564",
"outcome": "deferred",
"pass": false,
"outcome": "pass",
"pass": true,
"source": "generated:seed=232855971862407994",
"values_compared": 0
"values_compared": 57
},
{
"max_rel_err": 0.0,
@@ -5119,20 +4995,15 @@
}
],
"cases_both_errored": 0,
"cases_deferred": 31,
"cases_deferred": 0,
"cases_failed": 0,
"cases_passed": 593,
"cases_passed": 624,
"cases_total": 624,
"deferred_by_unit": {
"query.brep": 10,
"query.face_p1": 11,
"topology.edge": 4,
"topology.vertex": 6
},
"deferred_by_unit": {},
"max_rel_err": 0.0,
"pass": true,
"rel_tol": 1e-9,
"seeds": 16,
"status": "partial",
"status": "green",
"unit": "geom.object"
}
+64 -65
View File
@@ -935,8 +935,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "face_kind4_op19",
@@ -1159,8 +1159,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "face_kind5_op19",
@@ -1371,8 +1371,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "face_kind6_op19",
@@ -1999,8 +1999,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "rational_kind8_op19",
@@ -2223,8 +2223,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "spherical_band_op19",
@@ -3715,8 +3715,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "gen0017",
@@ -3779,8 +3779,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "gen0024",
@@ -4807,8 +4807,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "gen0140",
@@ -5303,8 +5303,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0197",
@@ -5555,8 +5555,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0224",
@@ -5991,8 +5991,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0274",
@@ -6451,8 +6451,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "gen0324",
@@ -6539,8 +6539,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0333",
@@ -6975,8 +6975,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0381",
@@ -7451,8 +7451,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0434",
@@ -7539,8 +7539,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0443",
@@ -7619,8 +7619,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0451",
@@ -7915,8 +7915,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0485",
@@ -8127,8 +8127,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0508",
@@ -8275,8 +8275,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0523",
@@ -8619,8 +8619,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "gen0561",
@@ -9147,8 +9147,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0621",
@@ -9347,8 +9347,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0642",
@@ -9387,8 +9387,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0646",
@@ -9695,8 +9695,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmTol::GetZoneTol3d(const SmObject*)",
"unit": "geom.tol"
},
"max_rel_err": 0.0,
"name": "gen0679",
@@ -10211,8 +10211,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0737",
@@ -10271,8 +10271,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0743",
@@ -10419,8 +10419,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0760",
@@ -10643,8 +10643,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0786",
@@ -10667,8 +10667,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0788",
@@ -10711,8 +10711,8 @@
},
{
"deferred": {
"symbol": "SmFace::CalculateBoundingBox",
"unit": "query.face_p1"
"symbol": "SmFace::GetTolerance",
"unit": "topology.face_p2"
},
"max_rel_err": 0.0,
"name": "gen0793",
@@ -10783,11 +10783,10 @@
"curve.crv_on_surf": 10,
"curve.curve_p2": 110,
"curve.curve_p6": 31,
"geom.tol": 48,
"geom.tol": 56,
"intersect.surface_drop_lib_p3": 28,
"query.face_p1": 31,
"topology.edge": 1,
"topology.face_p2": 28
"topology.face_p2": 51
},
"max_rel_err": 0.0,
"pass": true,