Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions crates/difftest/src/units/primitive_api_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,9 @@
//! produce), then on success `has` and the surface or B-rep record, reported as the values
//! `rec`.
//!
//! The surface overloads (plane, cylinder, cone, torus) are compared in full. Every B-rep
//! entry point but the pyramid is deferred on the Rust side once its inputs pass
//! validation (`SmPrimitiveCreation`, units `primitive.primitive_creation_p1..p3`); its
//! validation (box, sphere, cylinder, cone, cylindrical box, pole-free sphere, pyramid)
//! is compared. The pyramid builds its faces (`topology.brep_p8`) and stitches them;
//! a base whose plane fit hits UB-119 defers as `cpp-guard:ub-119` (see
//! [`pyramid_out`]). `SmApiCreateBlendPrimitive` takes a B-rep with faces and
//! edges, which the Rust side cannot build yet, and is not run. Every run has at least
//! [`MIN_GENERATED`] generated cases.
//! Surface overloads compare their parameterizations. Successful B-reps additionally
//! compare reachable vertices, sampled curves/surfaces, tolerances and UV trim counts.
//! A pyramid plane fit that reaches UB-119 remains deferred by the existing guard.

use serde::{Deserialize, Deserializer, Serialize, Serializer};
use serde_json::Value;
Expand Down Expand Up @@ -365,7 +359,23 @@ fn finish<T>(r: solidcore::Result<T>, put: impl Fn(&mut Vec<f64>, &T)) -> Output
}

fn brep_out(r: solidcore::Result<Brep>) -> Output {
finish(r, put_brep)
brep_record(r, false)
}

fn brep_record(r: solidcore::Result<Brep>, rays: bool) -> Output {
finish(
r.and_then(|mut brep| {
let mut record = Vec::new();
put_brep(&mut record, &brep);
let mut k = Kernel::default();
crate::util::primitive::append(&k, &brep, &mut record)?;
if rays {
crate::util::primitive::append_pyramid_rays(&mut k, &mut brep, &mut record)?;
}
Ok(record)
}),
|out, record| out.extend(record),
)
}

/// [`brep_out`] for the pyramid: a face whose plane fit hits `FindOuterLoops`' zero-area
Expand All @@ -379,7 +389,7 @@ fn pyramid_out(r: solidcore::Result<Brep>) -> Output {
operation: "topology::trimming_tools",
..
}) => Output::deferred("cpp-guard:ub-119", "SmTrimmingTools::FindOuterLoops"),
r => brep_out(r),
r => brep_record(r, true),
}
}

Expand Down
39 changes: 27 additions & 12 deletions crates/difftest/src/units/primitive_primitive_creation_p1.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,14 +279,17 @@ fn rust(i: &Inputs) -> solidcore::Result<Vec<f64>> {
};
match made {
Err(e) if e.is_unported() => return Err(e),
Ok(b) => record.extend([
0.0,
f64::from(b.editing_enabled()),
0.0,
solidcore::topology::brep::p1::faces(&b, None)?.len() as f64,
solidcore::topology::brep::p1::edges(&b, None)?.len() as f64,
solidcore::topology::brep::p1::vertices(&b, None)?.len() as f64,
]),
Ok(b) => {
record.extend([
0.0,
f64::from(b.editing_enabled()),
0.0,
solidcore::topology::brep::p1::faces(&b, None)?.len() as f64,
solidcore::topology::brep::p1::edges(&b, None)?.len() as f64,
solidcore::topology::brep::p1::vertices(&b, None)?.len() as f64,
]);
crate::util::primitive::append(&k, &b, &mut record)?;
}
Err(_) => record.extend([
f64::from(solidcore::error::SM_ERR),
-1.0,
Expand Down Expand Up @@ -426,6 +429,9 @@ fn rust(i: &Inputs) -> solidcore::Result<Vec<f64>> {
solidcore::topology::brep::p1::edges(&brep, None)?.len() as f64,
solidcore::topology::brep::p1::vertices(&brep, None)?.len() as f64,
]);
if status == 0 {
crate::util::primitive::append(&k, &brep, &mut record)?;
}
}
5 => {
let mut brep = solidcore::topology::brep::p3::new_brep(
Expand Down Expand Up @@ -506,8 +512,17 @@ fn rust(i: &Inputs) -> solidcore::Result<Vec<f64>> {
}
Ok(record)
}
fn output(op: i32, data: Vec<f64>) -> Result<Output, HarnessError> {
if op == 0 || op == 4 || op == 5 || op == 6 {
fn output(input: &Inputs, data: Vec<f64>) -> Result<Output, HarnessError> {
let op = input.op;
if op == 4 {
let prefix = if value(&input.data, 0) == 10.0 { 7 } else { 6 };
let head = data
.get(..prefix)
.ok_or_else(|| HarnessError::new("missing primitive topology record"))?;
Ok(Output::new()
.ints("maps", head.iter().map(|v| *v as i64).collect::<Vec<_>>())
.values("geometry", data.get(prefix..).unwrap_or_default().to_vec()))
} else if op == 0 || op == 5 || op == 6 {
Ok(Output::new().ints(
"maps",
data.into_iter().map(|v| v as i64).collect::<Vec<_>>(),
Expand Down Expand Up @@ -539,7 +554,7 @@ impl UnitDiff for Runner {
fn run_cpp(&self, case: &Case) -> Result<Output, HarnessError> {
let i: Inputs = case.parse()?;
match ffi::eval(i.op, &i.data) {
Ok(data) => output(i.op, data),
Ok(data) => output(&i, data),
Err(solidcore_ffi::FfiError::Status(-1010)) => Ok(Output::rejected(
"native fixture preflight rejected undefined construction",
)),
Expand All @@ -550,7 +565,7 @@ impl UnitDiff for Runner {
let i: Inputs = case.parse()?;
ffi::validate(i.op, &i.data).map_err(|e| HarnessError::new(e.to_string()))?;
match rust(&i) {
Ok(data) => output(i.op, data),
Ok(data) => output(&i, data),
Err(e) => Ok(Output::unported(&e).unwrap_or_else(|| Output::err(e.to_string()))),
}
}
Expand Down
9 changes: 4 additions & 5 deletions crates/difftest/src/units/sweep_api_brep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1087,8 +1087,7 @@ mod tests {
.any(|(o, k)| o == op && k.starts_with("status:"));
assert!(failed, "{op} never compared");
}
// The linear sweep runs to completion now (`topology.brep_p4` is ported); the
// other ops still reach an unported callee.
// Valid inputs must reach a successful kernel call or an unported callee.
for op in [
"rotational",
"draft",
Expand All @@ -1101,10 +1100,10 @@ mod tests {
"linear_reps",
"rotational_reps",
] {
let deferred = tally
let reached_kernel = tally
.keys()
.any(|(o, k)| o == op && k.starts_with("deferred:"));
assert!(deferred, "{op} never reaches the kernel");
.any(|(o, k)| o == op && (k == "status:0" || k.starts_with("deferred:")));
assert!(reached_kernel, "{op} never reaches the kernel");
}
let compared: usize = tally
.iter()
Expand Down
8 changes: 4 additions & 4 deletions crates/difftest/src/units/topology_brep_p10.rs
Original file line number Diff line number Diff line change
Expand Up @@ -693,10 +693,10 @@ impl Graph {
vertexuses: Reg::new(),
vertices: Reg::new(),
};
for (id, r) in b.regions().iter() {
if r.in_brep {
g.regions.add(Some(id), RegionId::index);
}
// GetRegions starts at the owning list head, which ResetInfiniteRegion
// changes without reallocating the region records.
for id in solidcore::topology::brep::p1::regions(b, None)? {
g.regions.add(Some(id), RegionId::index);
}
let (mut r, mut s, mut fu, mut lu, mut eu, mut vu, mut l, mut e, mut v) =
(0, 0, 0, 0, 0, 0, 0, 0, 0);
Expand Down
2 changes: 2 additions & 0 deletions crates/difftest/src/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Small conversions shared by the unit runners in [`crate::units`].

pub mod primitive;

use solidcore::geom::object::ObjectRef;
use solidcore::math::{Point2, Vec3};
use solidcore::topology::brep_cache::{arena_identity, CacheObject};
Expand Down
163 changes: 163 additions & 0 deletions crates/difftest/src/util/primitive.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
//! Geometry and trim records shared by the primitive API and p1 runners.

use solidcore::kernel::Kernel;
use solidcore::math::{Point2, Vec3};
use solidcore::topology::brep::p1;
use solidcore::topology::Brep;
use solidcore::{Result, SolidError};

fn point(out: &mut Vec<f64>, value: Vec3) {
out.extend([value.x, value.y, value.z]);
}

fn sample(out: &mut Vec<f64>, value: Result<Vec3>) -> Result<()> {
match value {
Ok(p) => {
out.push(0.0);
point(out, p);
}
Err(e) if e.is_unported() => return Err(e),
Err(SolidError::Upstream { code, .. }) => out.push(f64::from(code)),
Err(_) => out.push(-1.0),
}
Ok(())
}

/// Appends reachable vertex positions/tolerances, edge samples and trim counts,
/// then face domains/tolerances and surface samples, in native owning-list order.
/// Mirrors `harness/cpp/shim/support/primitive_record.h`.
pub fn append(k: &Kernel, brep: &Brep, out: &mut Vec<f64>) -> Result<()> {
for id in p1::vertices(brep, None)? {
let vertex = brep.vertex(id)?;
point(out, vertex.point);
out.push(vertex.tolerance);
}
for id in p1::edges(brep, None)? {
let edge = brep.edge(id)?;
out.extend([edge.tolerance, edge.interval.min(), edge.interval.max()]);
out.push(f64::from(edge.curve.is_some()));
if let Some(curve) = &edge.curve {
for fraction in [0.17, 0.37, 0.83] {
sample(
out,
curve.evaluate_point(k, edge.interval.evaluate(fraction)),
)?;
}
}
out.push(edge.edgeuses.len() as f64);
let mut trims = 0;
for &eu in &edge.edgeuses {
trims += usize::from(brep.edgeuse(eu)?.uv_trim_curve.is_some());
}
out.push(trims as f64);
}
for id in p1::faces(brep, None)? {
let face = brep.face(id)?;
let domain = face.uv_domain;
out.extend([
face.tolerance,
domain.min().x,
domain.min().y,
domain.max().x,
domain.max().y,
]);
out.push(f64::from(face.surface.is_some()));
if let Some(surface) = &face.surface {
for uv in [
Point2::new(0.17, 0.37),
Point2::new(0.37, 0.61),
Point2::new(0.83, 0.23),
] {
sample(out, surface.evaluate_point(k, domain.evaluate(uv.x, uv.y)))?;
}
}
}
Ok(())
}

/// Fires a miss, a side hit and an apex hit on a constructed pyramid, preserving
/// the lazily created UV trims between rays, as the native record does.
pub fn append_pyramid_rays(k: &mut Kernel, brep: &mut Brep, out: &mut Vec<f64>) -> Result<()> {
use solidcore::geom::extent3d::Extent3d;
use solidcore::intersect::point_class::PointClassification;
use solidcore::query::brep::{BrepIdentity, BrepQuery};
use solidcore::topology::shell::{ray_fire, ShellRayInput, ShellRayState};
use solidcore::topology::BrepId;
let mut bounds = Extent3d::new();
for id in p1::vertices(brep, None)? {
bounds.add_point(brep.vertex(id)?.point);
}
let size = bounds.max() - bounds.min();
let mid = bounds.mid();
let mut notifications = solidcore::topology::brep::p2::ArenaNotifyState::new(brep);
let env = notifications.env(None);
let keys = |object| match object {
solidcore::topology::brep_cache::CacheObject::Edge(e)
| solidcore::topology::brep_cache::CacheObject::Curve(e) => {
(env.key_of)(solidcore::EntityRef::Edge(e))
}
solidcore::topology::brep_cache::CacheObject::Face(f)
| solidcore::topology::brep_cache::CacheObject::Surface(f) => {
(env.key_of)(solidcore::EntityRef::Face(f))
}
solidcore::topology::brep_cache::CacheObject::Vertex(v) => {
(env.key_of)(solidcore::EntityRef::Vertex(v))
}
other => solidcore::topology::brep_cache::arena_identity(other),
};
let id = BrepId::from_index(0);
let world = BrepQuery::new(BrepIdentity {
brep_id: id,
keys: &keys,
owner_of: &|_| None,
});
let shells = p1::shells(k, brep, None)?;
let Some(&shell) = shells.first() else {
return Ok(());
};
for (start, direction) in [
(bounds.max() + size * 1000.0, Vec3::X),
(
Vec3::new(mid.x, mid.y, bounds.min().z + size.z * 0.25),
Vec3::X,
),
(Vec3::new(mid.x, mid.y, bounds.max().z + size.z), -Vec3::Z),
] {
let attempt = ray_fire(
k,
brep,
id,
shell,
&world,
ShellRayInput {
start,
direction,
zone_tolerance: 1e-5,
sphere_bound: None,
state: ShellRayState {
region: None,
classification: PointClassification::new(1e-5),
},
},
);
let status = match attempt.status {
Ok(()) => 0.0,
Err(e) if e.is_unported() => return Err(e),
Err(SolidError::Upstream { code, .. }) => f64::from(code),
Err(e) => return Err(e),
};
let mut trims = 0;
for edge in p1::edges(brep, None)? {
for &eu in &brep.edge(edge)?.edgeuses {
trims += usize::from(brep.edgeuse(eu)?.uv_trim_curve.is_some());
}
}
out.extend([
status,
f64::from(attempt.state.region.is_some()),
f64::from(attempt.state.classification.point_class().code()),
trims as f64,
]);
}
Ok(())
}
3 changes: 2 additions & 1 deletion crates/solidcore-ffi/src/units/primitive_api_primitives.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@
//! The swung curves are `SmApiCreateArc(C, r, a0, a1)` B-splines.
//!
//! The output is the API status (`0` = `SM_SUCCESS`, else the `SmStatus`), then on success
//! `has` and the result record. A B-rep's record is its face, edge and vertex counts. A
//! `has` and the result record. A B-rep's record contains its face, edge and vertex counts, then the geometry/trim
//! record in `crates/difftest/src/util/primitive.rs`. A
//! surface's record is its `GetType`, position (`O X Y Z`), STEP UV domain, the kind's
//! values (plane: the UV scale; cylinder / cone: bottom and top radius, inside-out and
//! swap-UV flags; torus: major and minor radius, inside-out and swap-UV flags), its NURB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
//! curve count and seven-number curve blocks. Op 6 builds up to four faces per
//! operand (planes or unit spheres) for `Boolean2D` validation; its layout is in
//! `fixtures/primitive.primitive_creation_p1/README.md`.
//! Op 4 returns status, edit/boolean flags and reachable face/edge/vertex counts
//! (selector 10 adds its output flag), followed on success by the geometry record
//! in `support/primitive_record.h`: vertices and tolerances, per-edge intervals,
//! samples and stored trim counts, then face domains, tolerances and samples.

use crate::{call_with_buffer, FfiError};

Expand Down
Loading
Loading