mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
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:
@@ -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 ®ions {
|
||||
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(
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user