topology.brep_p2: fix the mismatches the wiring exposed

Code that deferred on SmBrep::Notify before now runs and showed four
disagreements:

- topology.brep_p2: uv_trim_curve_copy accepts an SmLine UV trim curve
  (upstream an SmBSplineCurve whose NURB GetOrCreateGwNurbPointer makes
  through MakeNurb); 23 Mate cases failed on it.
- topology.brep_p9 / boolean.merge_p1: the shared graph export numbered
  records by arena index while the C++ shim numbers them in its walk's
  first-seen order. Canon::of replays that walk on the Rust B-rep; the
  graph and the runners' recorded ids map through it (97 + 3 cases).
- surface.surface_p11: LiftCurve returns sm_ExtractIsoCurve's iso curve
  with its own kind (SmLine / conic) as upstream; LiftedCurve::curve is
  a Curve and its four callers take it as is (6 offset cases).
- offset.offset_geometry_creation runner: ExtendTrimEdges and
  EdgeuseOffset's ExtendTrimEdges path defer
  (harness:analytic-geometry-export) when the lift's UV curve is an iso
  line and the offset edge use's face was imported as its B-spline part:
  the C++ runs the subclass's CreateIsoParametricCurve there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Xbnf2bFY8cTu4zUgAv9s85
This commit is contained in:
Christian Svensson
2026-10-20 04:13:25 +02:00
parent 685608d27e
commit 660b3270a9
8 changed files with 557 additions and 82 deletions
+405 -58
View File
@@ -15,10 +15,11 @@ use solidcore::geom::context::{Context, NotifyCallback};
use solidcore::geom::object::{NotifyOperation, ObjectRef};
use solidcore::kernel::Kernel;
use solidcore::math::Vec3;
use solidcore::topology::shell::ShellMembers;
use solidcore::topology::topology::Topology;
use solidcore::topology::{
ArenaId, Brep, EdgeId, EdgeuseOwner, FaceId, FaceuseId, LoopuseStart, RegionId, VertexId,
VertexuseOwner,
ArenaId, Brep, EdgeId, EdgeuseId, EdgeuseOwner, FaceId, FaceuseId, LoopId, LoopuseId,
LoopuseStart, RegionId, ShellId, VertexId, VertexuseId, VertexuseOwner,
};
use solidcore::SolidError;
use solidcore_ffi::units::{boolean_merge_p1 as ffi, topology_vertexuse as fixture};
@@ -108,7 +109,270 @@ fn obj(k: u64, t: i64) -> ObjectRef {
fn id<I: ArenaId>(v: Option<I>) -> i64 {
v.map_or(-1, |v| i64::from(v.index()))
}
/// One kind's records in first-seen order of [`Canon::of`] and each arena index's
/// position in it.
pub(crate) struct Order<I> {
items: Vec<I>,
index: BTreeMap<u32, i64>,
}
impl<I: ArenaId + Copy> Order<I> {
fn new() -> Self {
Self {
items: Vec::new(),
index: BTreeMap::new(),
}
}
fn add(&mut self, id: Option<I>) {
if let Some(id) = id {
if !self.index.contains_key(&id.index()) {
self.index.insert(id.index(), self.items.len() as i64);
self.items.push(id);
}
}
}
/// The canonical number of `id` (`-1` for none).
pub(crate) fn of(&self, id: Option<I>) -> i64 {
id.map_or(-1, |v| self.index.get(&v.index()).copied().unwrap_or(-3))
}
/// [`Order::of`] for a record slot holding an arena index (negative: none).
pub(crate) fn of_slot(&self, slot: f64) -> f64 {
if slot < 0.0 {
return slot;
}
self.index.get(&(slot as u32)).copied().unwrap_or(-3) as f64
}
}
/// Canonical record numbering: the shims export a B-rep by walking it from its regions
/// and edge list (`sc_edgeuse_graph.h` `walk`), so a C++ import numbers records in
/// first-seen order, while Rust arenas append new records. Both sides are renumbered
/// by the same walk (the shim's, from the B-rep's regions in arena order and the edge
/// list; on an imported C++ export it is the identity), so records created by the op
/// compare by position in the topology, not by allocation order. Records the walk
/// does not reach follow in arena order.
pub(crate) struct Canon {
pub(crate) regions: Order<RegionId>,
pub(crate) shells: Order<ShellId>,
pub(crate) faceuses: Order<FaceuseId>,
pub(crate) faces: Order<FaceId>,
pub(crate) loopuses: Order<LoopuseId>,
pub(crate) loops: Order<LoopId>,
pub(crate) edgeuses: Order<EdgeuseId>,
pub(crate) edges: Order<EdgeId>,
pub(crate) vertexuses: Order<VertexuseId>,
pub(crate) vertices: Order<VertexId>,
}
impl Canon {
#[allow(clippy::too_many_lines)]
pub(crate) fn of(b: &Brep) -> Self {
let mut g = Self {
regions: Order::new(),
shells: Order::new(),
faceuses: Order::new(),
faces: Order::new(),
loopuses: Order::new(),
loops: Order::new(),
edgeuses: Order::new(),
edges: Order::new(),
vertexuses: Order::new(),
vertices: Order::new(),
};
for (id, r) in b.regions().iter() {
if r.in_brep {
g.regions.add(Some(id));
}
}
for &e in b.edge_list() {
g.edges.add(Some(e));
}
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);
let mut more = true;
while more {
more = false;
while let Some(&id) = g.regions.items.get(r) {
if let Ok(rec) = b.region(id) {
for &x in &rec.shells {
g.shells.add(Some(x));
}
}
r += 1;
more = true;
}
while let Some(&id) = g.shells.items.get(s) {
if let Ok(rec) = b.shell(id) {
match &rec.members {
ShellMembers::Faceuses(list) => {
for &x in list {
g.faceuses.add(Some(x));
}
}
ShellMembers::Edgeuse(x) => g.edgeuses.add(Some(*x)),
ShellMembers::Vertexuse(x) => g.vertexuses.add(Some(*x)),
ShellMembers::Empty => {}
}
}
s += 1;
more = true;
}
while let Some(&id) = g.faceuses.items.get(fu) {
if let Ok(rec) = b.faceuse(id) {
g.faces.add(rec.face);
g.faceuses.add(rec.mate);
for &x in &rec.loopuses {
g.loopuses.add(Some(x));
}
}
fu += 1;
more = true;
}
while let Some(&id) = g.loopuses.items.get(lu) {
if let Ok(rec) = b.loopuse(id) {
g.loops.add(rec.loop_);
g.loopuses.add(rec.mate);
match rec.start {
LoopuseStart::Edgeuse(start) => {
g.edgeuses.add(start);
// the CCW walk of an edge loop
let mut q = start;
let mut k = 0;
while let Some(x) = q {
if k > b.edgeuses().len() {
break;
}
g.edgeuses.add(Some(x));
q = b.edgeuse(x).ok().and_then(|x| x.ccw);
if q == start {
break;
}
k += 1;
}
}
LoopuseStart::Vertexuse(x) => g.vertexuses.add(x),
LoopuseStart::Unknown => {}
}
}
lu += 1;
more = true;
}
while let Some(&id) = g.edgeuses.items.get(eu) {
if let Ok(rec) = b.edgeuse(id) {
g.edges.add(rec.edge);
match rec.owner {
EdgeuseOwner::Loopuse(x) => g.loopuses.add(x),
EdgeuseOwner::Shell(x) => g.shells.add(x),
EdgeuseOwner::Unknown => {}
}
g.edgeuses.add(rec.cw);
g.edgeuses.add(rec.ccw);
g.vertexuses.add(rec.vertexuse);
// GetNext / GetLast: the neighbours in the edge's circular list
let list = rec
.edge
.and_then(|x| b.edge(x).ok())
.map_or(&[][..], |x| x.edgeuses.as_slice());
if let Some(at) = list.iter().position(|&x| x == id) {
let n = list.len();
g.edgeuses.add(list.get((at + 1) % n).copied());
g.edgeuses.add(list.get((at + n - 1) % n).copied());
}
}
eu += 1;
more = true;
}
while let Some(&id) = g.edges.items.get(e) {
if let Ok(rec) = b.edge(id) {
for &x in &rec.edgeuses {
g.edgeuses.add(Some(x));
}
}
e += 1;
more = true;
}
while let Some(&id) = g.vertexuses.items.get(vu) {
if let Ok(rec) = b.vertexuse(id) {
g.vertices.add(rec.vertex);
match rec.owner {
VertexuseOwner::Edgeuse(x) => g.edgeuses.add(x),
VertexuseOwner::Loopuse(x) => g.loopuses.add(x),
VertexuseOwner::Shell(x) => g.shells.add(x),
VertexuseOwner::Unknown => {}
}
}
vu += 1;
more = true;
}
while let Some(&id) = g.vertices.items.get(v) {
if let Ok(rec) = b.vertex(id) {
for &x in &rec.vertexuses {
g.vertexuses.add(Some(x));
}
}
v += 1;
more = true;
}
while let Some(&id) = g.loops.items.get(l) {
if let Ok(rec) = b.loop_(id) {
g.loopuses.add(rec.loopuse);
}
l += 1;
more = true;
}
if !more {
// unreachable records, in arena order, then walk on from them
let before = g.len();
for (id, _) in b.regions().iter() {
g.regions.add(Some(id));
}
for (id, _) in b.shells().iter() {
g.shells.add(Some(id));
}
for (id, _) in b.faceuses().iter() {
g.faceuses.add(Some(id));
}
for (id, _) in b.faces().iter() {
g.faces.add(Some(id));
}
for (id, _) in b.loopuses().iter() {
g.loopuses.add(Some(id));
}
for (id, _) in b.loops().iter() {
g.loops.add(Some(id));
}
for (id, _) in b.edgeuses().iter() {
g.edgeuses.add(Some(id));
}
for (id, _) in b.edges().iter() {
g.edges.add(Some(id));
}
for (id, _) in b.vertexuses().iter() {
g.vertexuses.add(Some(id));
}
for (id, _) in b.vertices().iter() {
g.vertices.add(Some(id));
}
more = g.len() != before;
}
}
g
}
fn len(&self) -> usize {
self.regions.items.len()
+ self.shells.items.len()
+ self.faceuses.items.len()
+ self.faces.items.len()
+ self.loopuses.items.len()
+ self.loops.items.len()
+ self.edgeuses.items.len()
+ self.edges.items.len()
+ self.vertexuses.items.len()
+ self.vertices.items.len()
}
}
/// The B-rep's records and links in [`Canon`] numbering, after `record` (which the
/// caller has already put in that numbering, see [`Canon::of`]).
#[allow(clippy::too_many_lines)]
pub(crate) fn graph(b: &Brep, record: Vec<f64>) -> Output {
let c = Canon::of(b);
let mut ints = vec![
b.regions().len() as i64,
b.shells().len() as i64,
@@ -121,97 +385,162 @@ pub(crate) fn graph(b: &Brep, record: Vec<f64>) -> Output {
b.vertexuses().len() as i64,
b.vertices().len() as i64,
];
for (_, r) in b.regions().iter() {
let regions: Vec<_> = c
.regions
.items
.iter()
.filter_map(|&x| b.region(x).ok())
.collect();
let shells: Vec<_> = c
.shells
.items
.iter()
.filter_map(|&x| b.shell(x).ok())
.collect();
let faceuses: Vec<_> = c
.faceuses
.items
.iter()
.filter_map(|&x| b.faceuse(x).ok())
.collect();
let faces: Vec<_> = c
.faces
.items
.iter()
.filter_map(|&x| b.face(x).ok())
.collect();
let loopuses: Vec<_> = c
.loopuses
.items
.iter()
.filter_map(|&x| b.loopuse(x).ok())
.collect();
let loops: Vec<_> = c
.loops
.items
.iter()
.filter_map(|&x| b.loop_(x).ok())
.collect();
let edgeuses: Vec<_> = c
.edgeuses
.items
.iter()
.filter_map(|&x| b.edgeuse(x).ok())
.collect();
let edges: Vec<_> = c
.edges
.items
.iter()
.filter_map(|&x| b.edge(x).ok())
.collect();
let vertexuses: Vec<_> = c
.vertexuses
.items
.iter()
.filter_map(|&x| b.vertexuse(x).ok())
.collect();
let vertices: Vec<_> = c
.vertices
.items
.iter()
.filter_map(|&x| b.vertex(x).ok())
.collect();
for r in &regions {
ints.push(i64::from(r.in_brep));
}
for (_, f) in b.faces().iter() {
for f in &faces {
ints.extend([
i64::from(f.shell.index()),
id(f.faceuse),
c.shells.of(Some(f.shell)),
c.faceuses.of(f.faceuse),
i64::from(f.rectangular_trim),
f.loops.len() as i64,
]);
ints.extend(f.loops.iter().map(|v| i64::from(v.index())));
ints.extend(f.loops.iter().map(|&v| c.loops.of(Some(v))));
}
for (_, l) in b.loops().iter() {
for l in &loops {
ints.extend([
i64::from(l.face.index()),
id(l.loopuse),
c.faces.of(Some(l.face)),
c.loopuses.of(l.loopuse),
l.edges.len() as i64,
]);
for e in &l.edges {
ints.extend([i64::from(e.edge.index()), i64::from(e.forward)]);
ints.extend([c.edges.of(Some(e.edge)), i64::from(e.forward)]);
}
}
for (_, l) in b.loopuses().iter() {
for l in &loopuses {
ints.extend(match l.start {
LoopuseStart::Unknown => [0, -1],
LoopuseStart::Edgeuse(e) => [1, id(e)],
LoopuseStart::Vertexuse(v) => [2, id(v)],
LoopuseStart::Edgeuse(e) => [1, c.edgeuses.of(e)],
LoopuseStart::Vertexuse(v) => [2, c.vertexuses.of(v)],
});
}
for (_, e) in b.edgeuses().iter() {
for e in &edgeuses {
ints.extend(match e.owner {
EdgeuseOwner::Unknown => [0, -1],
EdgeuseOwner::Loopuse(l) => [1, id(l)],
EdgeuseOwner::Shell(sh) => [2, id(sh)],
EdgeuseOwner::Loopuse(l) => [1, c.loopuses.of(l)],
EdgeuseOwner::Shell(sh) => [2, c.shells.of(sh)],
});
}
for (_, v) in b.vertexuses().iter() {
ints.push(id(v.vertex));
for v in &vertexuses {
ints.push(c.vertices.of(v.vertex));
ints.extend(match v.owner {
VertexuseOwner::Unknown => [0, -1],
VertexuseOwner::Edgeuse(e) => [1, id(e)],
VertexuseOwner::Loopuse(l) => [2, id(l)],
VertexuseOwner::Shell(sh) => [3, id(sh)],
VertexuseOwner::Edgeuse(e) => [1, c.edgeuses.of(e)],
VertexuseOwner::Loopuse(l) => [2, c.loopuses.of(l)],
VertexuseOwner::Shell(sh) => [3, c.shells.of(sh)],
});
}
for (_, s) in b.shells().iter() {
ints.push(id(s.region));
for s in &shells {
ints.push(c.regions.of(s.region));
ints.push(s.faces.len() as i64);
ints.extend(s.faces.iter().map(|v| i64::from(v.index())));
ints.extend(s.faces.iter().map(|&v| c.faces.of(Some(v))));
}
for (_, f) in b.faceuses().iter() {
for f in &faceuses {
ints.extend([
id(f.shell),
id(f.face),
id(f.mate),
c.shells.of(f.shell),
c.faces.of(f.face),
c.faceuses.of(f.mate),
f.orientation as i64,
f.loopuses.len() as i64,
]);
ints.extend(f.loopuses.iter().map(|v| i64::from(v.index())));
ints.extend(f.loopuses.iter().map(|&v| c.loopuses.of(Some(v))));
}
for (_, l) in b.loopuses().iter() {
ints.extend([id(l.faceuse), id(l.loop_), id(l.mate), l.orientation as i64]);
}
for (_, e) in b.edges().iter() {
ints.push(e.edgeuses.len() as i64);
ints.extend(e.edgeuses.iter().map(|v| i64::from(v.index())));
}
for (_, e) in b.edgeuses().iter() {
for l in &loopuses {
ints.extend([
id(e.edge),
id(e.ccw),
id(e.cw),
id(e.vertexuse),
c.faceuses.of(l.faceuse),
c.loops.of(l.loop_),
c.loopuses.of(l.mate),
l.orientation as i64,
]);
}
for e in &edges {
ints.push(e.edgeuses.len() as i64);
ints.extend(e.edgeuses.iter().map(|&v| c.edgeuses.of(Some(v))));
}
for e in &edgeuses {
ints.extend([
c.edges.of(e.edge),
c.edgeuses.of(e.ccw),
c.edgeuses.of(e.cw),
c.vertexuses.of(e.vertexuse),
e.orientation as i64,
]);
}
let mut values = Vec::new();
for (_, v) in b.vertexuses().iter() {
for v in &vertexuses {
values.extend([v.uv_point.x, v.uv_point.y]);
}
for (_, v) in b.vertices().iter() {
for v in &vertices {
values.extend([v.point.x, v.point.y, v.point.z, v.tolerance]);
ints.push(v.vertexuses.len() as i64);
ints.extend(v.vertexuses.iter().map(|v| i64::from(v.index())));
ints.extend(v.vertexuses.iter().map(|&x| c.vertexuses.of(Some(x))));
}
for (_, f) in b.faces().iter() {
for f in &faces {
let a = f.uv_domain.min();
let z = f.uv_domain.max();
values.extend([a.x, a.y, z.x, z.y, f.tolerance]);
}
for (_, e) in b.edges().iter() {
for e in &edges {
values.extend([e.interval.min(), e.interval.max(), e.tolerance]);
}
Output::new()
@@ -375,6 +704,16 @@ fn run_rust(i: &Inputs, b: &mut Brep, other: &Brep) -> Result<Vec<f64>, SolidErr
dst.set_all_flags(1);
records.insert(ObjectKey(1), src);
records.insert(ObjectKey(2), dst);
// a copy's new vertex: its own (default) record, as an upstream object's
let fresh = 300 + b.vertices().len() as u64;
records.insert(ObjectKey(fresh), Topology::new());
let vertex_key = |v: VertexId| {
if v == dest {
2
} else {
300 + u64::from(v.index())
}
};
let h = attrs.insert(Attribute::long(555, 29, Behavior::Copy))?;
attrs.add_attribute(ObjectKey(1), h)?;
let result = port::merge_vertex(
@@ -388,7 +727,7 @@ fn run_rust(i: &Inputs, b: &mut Brep, other: &Brep) -> Result<Vec<f64>, SolidErr
attributes: &mut attrs,
source_key: &|_| obj(1, 16021),
destination_key: &|e| match e {
solidcore::EntityRef::Vertex(_) => obj(2, 16021),
solidcore::EntityRef::Vertex(v) => obj(vertex_key(v), 16021),
solidcore::EntityRef::Vertexuse(v) => {
obj(100 + u64::from(v.index()), 16006)
}
@@ -397,23 +736,35 @@ fn run_rust(i: &Inputs, b: &mut Brep, other: &Brep) -> Result<Vec<f64>, SolidErr
},
Some(&mut context),
)?;
out.push(result.map_or(-1.0, |v| f64::from(v.index())));
if result.is_some() {
// the shim's `fresh.find(9, result)` of a walk after the op
out.push(result.map_or(-1.0, |v| Canon::of(b).vertices.of(Some(v)) as f64));
if let Some(v) = result {
let key = ObjectKey(vertex_key(v));
out.push(
records
.get(&ObjectKey(2))
.get(&key)
.ok_or_else(|| SolidError::null_pointer("difftest::merge_copy"))?
.all_flags() as f64,
);
out.push(
attrs
.find_attribute(ObjectKey(2), 555)
.find_attribute(key, 555)
.and_then(|h| attrs.get(h))
.and_then(Attribute::long_value)
.unwrap_or(-1) as f64,
);
}
// the shim's recorder knows the source, the destination and the two B-reps
// only (`-99` for any other object)
let start = out.len();
append_events(&context, &mut out)?;
if let Some(events) = out.get_mut(start + 1..) {
for (j, x) in events.iter_mut().enumerate() {
if j % 5 != 0 && *x >= 100.0 {
*x = -99.0;
}
}
}
}
4 => {
let mut attrs = AttributeStore::new();
@@ -490,12 +841,8 @@ fn run_rust(i: &Inputs, b: &mut Brep, other: &Brep) -> Result<Vec<f64>, SolidErr
port::temporary_brep_transform(b, &mut other)?;
}
8 | 9 => {
let sec = solidcore::topology::EdgeuseId::from_index(
u32::from(a) % b.edgeuses().len() as u32,
);
let cls = solidcore::topology::EdgeuseId::from_index(
u32::from(c) % other.edgeuses().len() as u32,
);
let sec = EdgeuseId::from_index(u32::from(a) % b.edgeuses().len() as u32);
let cls = EdgeuseId::from_index(u32::from(c) % other.edgeuses().len() as u32);
let edge = b
.edgeuse(sec)?
.edge
@@ -712,6 +712,32 @@ impl Two {
.copied()
}
/// Whether `ExtendTrimEdges` of `eu` onto `off` lifts through `sm_ExtractIsoCurve` on a
/// face imported as its B-spline part: the lift's UV curve (the edge use's or its
/// radial's UV trim curve) is an iso line and `off`'s face is not exact. The C++ iso
/// curve then keeps the subclass's kind (an `SmCircle` on a surface of revolution),
/// which becomes the offset edge's curve, while the Rust lifts a B-spline.
fn lifts_iso_on_inexact_face(&self, eu: H, off: H) -> solidcore::Result<bool> {
match self.edgeuse_face(off)? {
Some(face) if self.face_exact(face) != Some(true) => {}
_ => return Ok(false),
}
let mut uses = vec![eu];
if let Some(rad) = self.edgeuse_radial(eu)? {
uses.push(rad);
}
for u in uses {
let curve = self.edgeuse_uv_trim_curve(u)?;
if curve
.and_then(|h| ClassGeometry::curve(self, h))
.is_some_and(is_uv_iso_segment)
{
return Ok(true);
}
}
Ok(false)
}
/// Whether `face`'s surface is an `SmSphere` (see `types`).
fn face_is_sphere(&self, face: H) -> bool {
usize::try_from(face.b)
@@ -2241,6 +2267,51 @@ fn would_reverse(two: &Two, eu: H, c3: H, orig: Option<H>) -> Result<bool, Fail>
Ok(t1.dot(t2) < 0.0)
}
/// Whether a UV curve is a degree-1 B-spline (or line) along a constant `u` or `v`: the
/// UV curves `sm_ExtractIsoCurve` lifts to the surface's iso curve (their extensions in
/// `ExtendTrimEdges` stay such lines). The bound is generous, so a borderline curve
/// is counted as iso.
fn is_uv_iso_segment(c: &SmCurve) -> bool {
let nurb = match c.kind() {
CurveKind::BSpline(b) => b.gw_nurb().cloned(),
CurveKind::Line(l) => {
let mut l = l.clone();
if l.base().gw_nurb().is_none() && l.make_nurb().is_err() {
return false;
}
l.base().gw_nurb().cloned()
}
_ => return false,
};
let Some(n) = nurb else {
return false;
};
if n.degree() > 1 {
return false;
}
let pts: Vec<(f64, f64)> = n
.cpts()
.iter()
.map(|p| {
if p.w == NOW {
(p.x, p.y)
} else {
(p.x / p.w, p.y / p.w)
}
})
.collect();
let flat = |f: fn(&(f64, f64)) -> f64| {
let (lo, hi) = pts
.iter()
.map(f)
.fold((f64::INFINITY, f64::NEG_INFINITY), |(l, h), x| {
(l.min(x), h.max(x))
});
hi - lo <= 1e-9 * (1.0 + lo.abs().max(hi.abs()))
};
flat(|p| p.0) || flat(|p| p.1)
}
/// Whether the curve is of a B-spline kind (`IsKindOf(SmBSplineCurve_TYPE)`).
fn is_bspline_kind(c: &SmCurve) -> bool {
c.is_kind_of(BSPLINE_CURVE_TYPE)
@@ -2646,6 +2717,13 @@ fn run_rust_script(i: &Inputs, p: Prepared) -> Result<Vec<f64>, Fail> {
o.push(NULL_DEREF);
continue;
}
// LiftCurve's iso curve keeps the subclass's kind (lifts_iso_on_inexact_face)
if two.lifts_iso_on_inexact_face(eu, off)? {
return Err(Fail::Solid(SolidError::Unported {
upstream: "SmSurface::LiftCurve iso curve of a subclass (face surface imported as its B-spline part)",
unit: ANALYTIC_EXPORT,
}));
}
let r = geom.extend_trim_edges(
&k,
&mut two,
@@ -2758,6 +2836,15 @@ fn run_rust_script(i: &Inputs, p: Prepared) -> Result<Vec<f64>, Fail> {
o.push(NULL_DEREF);
continue;
}
// ExtendTrimEdges' LiftCurve (lifts_iso_on_inexact_face)
if let Some(off) = off {
if two.lifts_iso_on_inexact_face(orig, off)? {
return Err(Fail::Solid(SolidError::Unported {
upstream: "SmSurface::LiftCurve iso curve of a subclass (face surface imported as its B-spline part)",
unit: ANALYTIC_EXPORT,
}));
}
}
}
}
let r = geom.edgeuse_offset(
+34 -2
View File
@@ -3,7 +3,7 @@
//! Initial graphs are exported before either operation. Split refusals compare
//! their retained endpoint as well as the complete semantic topology/geometry
//! snapshot. Successful native mutations on deferred paths are not parity passes.
use super::boolean_merge_p1::graph;
use super::boolean_merge_p1::{graph, Canon};
use super::topology_edgeuse_p1::{self as spec, BrepIn};
use super::topology_vertexuse::import;
use crate::{Case, CaseContext, HarnessError, Output, Rng, UnitDiff};
@@ -18,7 +18,7 @@ use solidcore::kernel::Kernel;
use solidcore::math::Vec3;
use solidcore::nurbs::b_spline_surface_n_lib::p2::OrientType;
use solidcore::topology::brep::p9 as port;
use solidcore::topology::{ArenaId, EdgeId, FaceId, RegionId, VertexuseId};
use solidcore::topology::{ArenaId, Brep, EdgeId, FaceId, RegionId, VertexuseId};
use solidcore::SolidError;
use solidcore_ffi::units::topology_brep_p9 as ffi;
@@ -405,6 +405,7 @@ fn rust(i: &Inputs, initial: &[f64]) -> Result<Output, HarnessError> {
match result {
Ok(code) => {
record.insert(0, f64::from(code));
canon_record(&b, i.op, &mut record);
Ok(graph(&b, record))
}
Err(SolidError::Upstream { code, .. }) if i.op <= 4 => {
@@ -419,6 +420,7 @@ fn rust(i: &Inputs, initial: &[f64]) -> Result<Output, HarnessError> {
};
record.resize(slots, -1.0);
record.insert(0, f64::from(code));
canon_record(&b, i.op, &mut record);
Ok(graph(&b, record))
}
Err(e) => status(&e),
@@ -439,6 +441,35 @@ impl solidcore::intersect::point_class::ClassObject for Handle {
}
}
}
/// Puts the B-rep ids of `record` (status first) in [`Canon`] numbering, the shim's
/// `fresh.find` of a walk after the op.
fn canon_record(b: &Brep, op: u8, record: &mut [f64]) {
let c = Canon::of(b);
let mut map = |at: usize, f: &dyn Fn(f64) -> f64| {
if let Some(x) = record.get_mut(at) {
*x = f(*x);
}
};
match op {
0 => map(1, &|x| c.regions.of_slot(x)),
2 => {
map(1, &|x| c.shells.of_slot(x));
map(2, &|x| c.vertices.of_slot(x));
}
3 => {
map(1, &|x| c.loops.of_slot(x));
map(2, &|x| c.vertices.of_slot(x));
}
4 => {
map(1, &|x| c.edges.of_slot(x));
map(2, &|x| c.edges.of_slot(x));
map(3, &|x| c.vertices.of_slot(x));
}
6 | 7 => map(2, &|x| c.vertices.of_slot(x)),
_ => {}
}
}
fn object_ref(e: solidcore::EntityRef) -> solidcore::geom::object::ObjectRef {
super::boolean_topology_intersector_p1::p9_entity_ref(e)
}
@@ -469,6 +500,7 @@ impl UnitDiff for Runner {
raw.get(2 + n..)
.ok_or_else(|| error("short native graph"))?,
)?;
canon_record(&b, i.op, &mut record);
Ok(graph(&b, record))
}
fn run_rust(&self, case: &Case) -> Result<Output, HarnessError> {
+1 -1
View File
@@ -3501,7 +3501,7 @@ pub fn join_verts_by_capping(
// add the curves to the output arrays
uv_curves.push(uv_curve);
curves_3d.push(Curve::from(lifted.curve));
curves_3d.push(lifted.curve);
orients.push(OrientType::Same);
}
}
@@ -2285,7 +2285,7 @@ impl OffsetGeometryCreation {
let dropped = surface_of(brep, other_ext_h)?.drop_and_trim_curve::<O>(
k,
&other_ext_domain,
&Curve::from(lifted_curve.clone()),
&lifted_curve,
&lifted_curve.natural_interval(),
off_tol / 3.0,
true,
@@ -2358,7 +2358,7 @@ impl OffsetGeometryCreation {
// Now that we have created one or two UV curves and a 3D curve find trimming
// interval and if we find a good one replace the geometry.
let lifted_h = store_curve(brep, Curve::from(lifted_curve.clone()))?;
let lifted_h = store_curve(brep, lifted_curve.clone())?;
let trim_ivl = self.trim_extended_edge(
k,
brep,
+15 -15
View File
@@ -82,7 +82,6 @@ use crate::geom::vector2d::{Point2, Vec2};
use crate::geom::vector3d::{sqrt, Point3, Vec3};
use crate::kernel::Kernel;
use crate::math::{max2, BIG_DOUBLE, EFF_ZERO, EFF_ZERO_SQ, EFF_ZERO_SQRT, PI};
use crate::nurbs::b_spline_curve::BSplineCurve;
use crate::nurbs::b_spline_curve_n_lib::TrimMode;
use crate::nurbs::b_spline_surface::SurfParamType;
use crate::nurbs::knots::Side;
@@ -1758,7 +1757,7 @@ fn extract_iso_curve(
uv_curve: &Curve,
interval: &Extent1d,
approx_tol_3d: f64,
) -> Result<Option<(BSplineCurve, f64)>> {
) -> Result<Option<(Curve, f64)>> {
// `sm_ExtractIsoCurve` takes an `SmBSplineCurve`: every `Curve` kind derived from it
// has one as base. Other kinds cannot reach upstream's signature, so they are not
// iso curves here.
@@ -1776,23 +1775,24 @@ fn extract_iso_curve(
else {
return Ok(None);
};
// rp3dCurve is an SmBSplineCurve
let bspline = match iso.into_kind() {
CurveKind::BSpline(b) => b,
CurveKind::Line(l) => l.base().clone(),
CurveKind::Ellipse(e) => e.base().clone(),
CurveKind::Hyperbola(h) => h.base().clone(),
CurveKind::Parabola(p) => p.base().clone(),
_ => return Err(SolidError::sm_err(OP_LIFT)),
};
Ok(Some((bspline, 0.0)))
// rp3dCurve is an SmBSplineCurve: the iso curve keeps its own kind (an SmLine or
// conic is one; the caller's virtual calls dispatch on it)
match iso.kind() {
CurveKind::BSpline(_)
| CurveKind::Line(_)
| CurveKind::Ellipse(_)
| CurveKind::Hyperbola(_)
| CurveKind::Parabola(_) => Ok(Some((iso, 0.0))),
_ => Err(SolidError::sm_err(OP_LIFT)),
}
}
/// The lifted curve of [`Surface::lift_curve`].
#[derive(Debug, Clone)]
pub struct LiftedCurve {
/// `rp3dCurve`: the 3D curve `surface(uv_curve(interval))`.
pub curve: BSplineCurve,
/// `rp3dCurve`: the 3D curve `surface(uv_curve(interval))`, an `SmBSplineCurve`:
/// a B-spline, or the analytic iso curve (line or conic) `sm_ExtractIsoCurve` made.
pub curve: Curve,
/// `rdMaxApproxDev`: the largest distance from the curve to the surface.
pub max_approx_dev: f64,
}
@@ -2020,7 +2020,7 @@ impl Surface {
};
Ok(LiftedCurve {
curve,
curve: Curve::from(curve),
max_approx_dev,
})
}
+12 -3
View File
@@ -2533,7 +2533,7 @@ pub fn mate_lamina_edge_to_surface(
true,
)?
};
let new_3d = Curve::from(lifted.curve);
let new_3d = lifted.curve;
let new_ivl = new_3d.natural_interval();
// side effect: delete current UVTrimCurves
give_edge_curve(b, env, e_side, new_3d)?;
@@ -2571,8 +2571,10 @@ fn set_vertex_point(b: &mut Brep, env: &mut NotifyEnv<'_>, v: VertexId, p: Point
/// `pEU->GetOrCreateUVTrimCurve(pUV)` (status ignored; header defaults `NULL`, `NULL`,
/// `TRUE`), then `SER(pUV->Copy(..))`: a copy of the edge use's UV trim curve. A
/// missing curve is dereferenced upstream (after its `SM_ASSERT`). UV trim curves are
/// made as B-splines (the `SmBSplineCurve*&` overload casts); a curve of another kind
/// is [`SolidError::InvalidInput`].
/// made as B-splines (the `SmBSplineCurve*&` overload casts). An `SmLine` is an
/// `SmBSplineCurve`: its copy serves `CreateExtendedCurve` through its B-spline part,
/// whose NURB `GetOrCreateGwNurbPointer` makes (`MakeNurb`, the `SmLine` override; status
/// ignored) when missing. A curve of another kind is [`SolidError::InvalidInput`].
fn uv_trim_curve_copy(k: &Kernel, b: &mut Brep, eu: EdgeuseId) -> Result<BSplineCurve> {
let _ = eu3::get_or_create_uv_trim_curve(k, b, eu, false, false, true)?;
let curve = eu1::uv_trim_curve(b, eu)?
@@ -2580,6 +2582,13 @@ fn uv_trim_curve_copy(k: &Kernel, b: &mut Brep, eu: EdgeuseId) -> Result<BSpline
.ok_or(null_deref("edge use without UV trim curve"))?;
match curve.kind() {
CurveKind::BSpline(c) => Ok(c.clone()),
CurveKind::Line(l) => {
let mut l = l.clone();
if l.base.gw_nurb().is_none() {
let _ = l.make_nurb();
}
Ok(l.base)
}
_ => Err(SolidError::InvalidInput {
operation: OP,
reason: "UV trim curve is not a B-spline",
+1 -1
View File
@@ -759,7 +759,7 @@ pub fn make_face_with_curves(
zone_tol / 4.0,
false,
))?
.map(|l| Curve::from(l.curve))
.map(|l| l.curve)
};
if new3d.is_some() {
to_check.push(s3d.len());