diff --git a/crates/difftest/src/units/topology_edgeuse_p1.rs b/crates/difftest/src/units/topology_edgeuse_p1.rs index 0b69298b91..d0bbbb366e 100644 --- a/crates/difftest/src/units/topology_edgeuse_p1.rs +++ b/crates/difftest/src/units/topology_edgeuse_p1.rs @@ -748,6 +748,7 @@ pub(crate) fn import(ex: &[f64]) -> Result { mate, orientation, loopuses, + sibling_links: None, }); } let mut surfaces = Vec::new(); @@ -874,7 +875,7 @@ pub(crate) fn import(ex: &[f64]) -> Result { let tolerance = rd.num()?; vertices.push(Vertex { point, tolerance, vertexuses: Vec::new() }); } - if rd.idx()? != 2 { + if rd.idx()? != 3 { return Err(harness("export: unsupported shell extension version")); } let infinite_region = opt(rd.idx()?, RegionId::from_index)?; @@ -899,6 +900,10 @@ pub(crate) fn import(ex: &[f64]) -> Result { .map(|v| opt(v, VertexuseId::from_index)?.ok_or_else(|| harness("null vertex use"))) .collect::, _>>()?; } + for faceuse in &mut faceuses { + faceuse.sibling_links = Some((opt(rd.idx()?, FaceuseId::from_index)?, + opt(rd.idx()?, FaceuseId::from_index)?)); + } if rd.at != ex.len() { return Err(harness("export: trailing data")); } diff --git a/crates/difftest/src/units/topology_shell.rs b/crates/difftest/src/units/topology_shell.rs index 7eed0ba723..0c793cff42 100644 --- a/crates/difftest/src/units/topology_shell.rs +++ b/crates/difftest/src/units/topology_shell.rs @@ -14,12 +14,15 @@ use solidcore_ffi::units::{topology_edgeuse_p1 as graph, topology_shell as ffi}; pub struct Runner; #[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] struct Inputs { brep: Value, #[serde(default)] void_state: Option, #[serde(default)] wires: bool, + #[serde(default)] + transfer: Option, } const TYPES: [i64; 5] = [16013, 16012, 16000, 16999, -1]; @@ -36,6 +39,237 @@ fn list(out: &mut Vec, values: impl IntoIterator) { out.extend(values.into_iter().map(i64::from)); } +#[derive(Serialize, Deserialize)] +#[serde(deny_unknown_fields)] +struct TransferInputs { + #[serde(default)] + split_faceuse: bool, + #[serde(default)] + require_faceuse_pair: bool, + #[serde(default)] + extra_empty: usize, + source: usize, + recipient: usize, + candidates: Option>, +} + +struct ResolvedTransfer { + split_faceuse: bool, + require_faceuse_pair: bool, + extra_empty: usize, + source: usize, + recipient: usize, + candidates: Option>>, +} + +fn resolve_transfer( + input: &TransferInputs, + counts: [usize; 10], +) -> Result { + if input.extra_empty > 64 { + return Err(failure("too many empty shells")); + } + let shells = counts + .get(1) + .copied() + .and_then(|n| n.checked_add(input.extra_empty)) + .ok_or_else(|| failure("shell count overflow"))?; + if shells == 0 { + return Err(failure("transfer fixture has no shells")); + } + let edges = counts + .get(7) + .copied() + .ok_or_else(|| failure("missing edge count"))?; + let candidates = if let Some(list) = &input.candidates { + let mut resolved = Vec::new(); + for &entry in list { + resolved.push(if entry == -1 { + None + } else if entry < 0 || edges == 0 { + return Err(failure("invalid wire candidate selector")); + } else { + Some(usize::try_from(entry).map_err(failure)? % edges) + }); + } + Some(resolved) + } else { + None + }; + Ok(ResolvedTransfer { + split_faceuse: input.split_faceuse, + require_faceuse_pair: input.require_faceuse_pair, + extra_empty: input.extra_empty, + source: input.source % shells, + recipient: input.recipient % shells, + candidates, + }) +} + +fn rust_transfer_records( + brep: &mut solidcore::topology::Brep, + options: &ResolvedTransfer, +) -> Result { + use solidcore::topology::shell::{faceuse_sibling_links, transfer_children, ShellMembers}; + use solidcore::topology::{EdgeId, EdgeuseOwner, VertexuseOwner}; + use solidcore::SolidError; + let region = brep + .infinite_region() + .ok_or_else(|| failure("fixture has no infinite region"))?; + for _ in 0..options.extra_empty { + let shell = brep + .add_shell_record(Shell { + region: Some(region), + ..Shell::new() + }) + .map_err(failure)?; + brep.region_mut(region).map_err(failure)?.shells.push(shell); + } + if options.split_faceuse { + if options.extra_empty == 0 { + return Err(failure("split fixture needs an extra shell")); + } + let original = ShellId::from_index(0); + let extra_index = brep + .shells() + .len() + .checked_sub(options.extra_empty) + .ok_or_else(|| failure("extra shell index underflow"))?; + let extra = ShellId::from_index(u32::try_from(extra_index).map_err(failure)?); + let members = match &mut brep.shell_mut(original).map_err(failure)?.members { + ShellMembers::Faceuses(members) if members.len() >= 2 => members, + _ => return Err(failure("split fixture needs a face-use cycle")), + }; + let moved = members.remove(0); + let target = brep.shell_mut(extra).map_err(failure)?; + target.shell_type = solidcore::topology::shell::ShellType::Faceuse; + target.members = ShellMembers::Faceuses(vec![moved]); + brep.faceuse_mut(moved).map_err(failure)?.shell = Some(extra); + // Construct the two baseline cycles independently of the transfer port. + for id in [original, extra] { + let members = match &brep.shell(id).map_err(failure)?.members { + ShellMembers::Faceuses(members) => members.clone(), + _ => return Err(failure("split fixture lost its cycle")), + }; + let mut previous = members.last().copied(); + for (&member, &next) in members.iter().zip(members.iter().cycle().skip(1)) { + brep.faceuse_mut(member).map_err(failure)?.sibling_links = + Some((Some(next), previous)); + previous = Some(member); + } + } + } + let candidates = if let Some(list) = &options.candidates { + let mut ids = Vec::new(); + for &entry in list { + ids.push(match entry { + None => None, + Some(index) => Some(EdgeId::from_index(u32::try_from(index).map_err(failure)?)), + }); + } + Some(ids) + } else { + None + }; + let source = ShellId::from_index(u32::try_from(options.source).map_err(failure)?); + let recipient = ShellId::from_index(u32::try_from(options.recipient).map_err(failure)?); + if options.require_faceuse_pair { + let source_record = brep.shell(source).map_err(failure)?; + let target_record = brep.shell(recipient).map_err(failure)?; + if source == recipient + || source_record.shell_type.code() != 16022 + || target_record.shell_type.code() != 16022 + || !matches!(&source_record.members,ShellMembers::Faceuses(ids) if !ids.is_empty()) + || !matches!(&target_record.members,ShellMembers::Faceuses(ids) if !ids.is_empty()) + { + return Err(failure( + "fixture must contain two distinct nonempty face-use shells", + )); + } + } + let status = match transfer_children(brep, source, recipient, candidates.as_deref()) { + Ok(()) => 0, + Err(SolidError::Upstream { code, .. }) => i64::from(code), + Err(error) => return Output::unported(&error).ok_or_else(|| failure(error)), + }; + let mut out = vec![ + status, + brep.shells().len() as i64, + brep.faceuses().len() as i64, + brep.edgeuses().len() as i64, + brep.vertexuses().len() as i64, + brep.regions().len() as i64, + ]; + for (_, shell) in brep.shells().iter() { + let (size, kind, head) = match &shell.members { + ShellMembers::Empty => (0, 0, -1), + ShellMembers::Faceuses(ids) => ( + ids.len(), + if ids.is_empty() { 0 } else { 16022 }, + ids.first().map_or(-1, |id| i64::from(id.index())), + ), + ShellMembers::Edgeuse(id) => { + let edge = brep + .edgeuse(*id) + .map_err(failure)? + .edge + .ok_or_else(|| failure("wire head has no edge"))?; + ( + brep.edge(edge).map_err(failure)?.edgeuses.len(), + 16005, + i64::from(id.index()), + ) + } + ShellMembers::Vertexuse(id) => (1, 16006, i64::from(id.index())), + }; + out.extend([ + shell.shell_type.code(), + size as i64, + shell.region.map_or(-1, |id| i64::from(id.index())), + kind, + head, + ]); + } + for (id, faceuse) in brep.faceuses().iter() { + let (next, last) = faceuse_sibling_links(brep, id).map_err(failure)?; + out.extend([ + faceuse.shell.map_or(-1, |id| i64::from(id.index())), + next.map_or(-1, |id| i64::from(id.index())), + last.map_or(-1, |id| i64::from(id.index())), + ]); + } + for (_, edgeuse) in brep.edgeuses().iter() { + let (kind, id) = match edgeuse.owner { + EdgeuseOwner::Shell(id) => (16013, id.map_or(-1, |id| i64::from(id.index()))), + EdgeuseOwner::Loopuse(id) => (16004, id.map_or(-1, |id| i64::from(id.index()))), + EdgeuseOwner::Unknown => (16999, -1), + }; + out.extend([kind, id]); + } + for (_, vertexuse) in brep.vertexuses().iter() { + let (kind, id) = match vertexuse.owner { + VertexuseOwner::Shell(id) => (16013, id.map_or(-1, |id| i64::from(id.index()))), + VertexuseOwner::Loopuse(id) => (16004, id.map_or(-1, |id| i64::from(id.index()))), + VertexuseOwner::Edgeuse(id) => (16005, id.map_or(-1, |id| i64::from(id.index()))), + VertexuseOwner::Unknown => (16999, -1), + }; + out.extend([kind, id]); + } + for (_, region) in brep.regions().iter() { + out.extend([ + region.shells.len() as i64, + region.shells.first().map_or(-1, |id| i64::from(id.index())), + ]); + list(&mut out, region.shells.iter().copied().map(ShellId::index)); + } + for shell in brep.shells().ids() { + let wires = wire_edges(brep, shell).map_err(failure)?; + out.push(0); + list(&mut out, wires.into_iter().map(EdgeId::index)); + } + Ok(Output::new().ints("records", out)) +} + impl UnitDiff for Runner { fn id(&self) -> &'static str { "topology.shell" @@ -44,7 +278,24 @@ impl UnitDiff for Runner { fn run_cpp(&self, case: &Case) -> Result { let inputs: Inputs = case.parse()?; let (path, block) = fixture_input(inputs.brep)?; - let native = if inputs.wires { + if inputs.transfer.is_some() && (inputs.wires || inputs.void_state.is_some()) { + return Err(failure("transfer cannot be combined with query mutations")); + } + let native = if let Some(transfer) = inputs.transfer { + let export = graph::eval(graph::Op::Export, &path, &block).map_err(failure)?; + let counts = graph::counts(&export) + .ok_or_else(|| failure("native transfer fixture build failed"))?; + let options = resolve_transfer(&transfer, counts)?; + ffi::transfer( + &path, + &block, + options.extra_empty, + options.source, + options.recipient, + options.candidates.as_deref(), + options.split_faceuse, + ) + } else if inputs.wires { ffi::wire_edges(&path, &block) } else { ffi::query(&path, &block, inputs.void_state) @@ -67,6 +318,14 @@ impl UnitDiff for Runner { let (path, block) = fixture_input(inputs.brep)?; let export = graph::eval(graph::Op::Export, &path, &block).map_err(failure)?; let mut brep = import(&export)?; + if inputs.transfer.is_some() && (inputs.wires || inputs.void_state.is_some()) { + return Err(failure("transfer cannot be combined with query mutations")); + } + if let Some(transfer) = inputs.transfer { + let counts = graph::counts(&export) + .ok_or_else(|| failure("native transfer fixture build failed"))?; + return rust_transfer_records(&mut brep, &resolve_transfer(&transfer, counts)?); + } if inputs.wires { let mut out = vec![brep.shells().len() as i64]; for shell in brep.shells().ids() { @@ -156,9 +415,15 @@ impl UnitDiff for Runner { let scale = [1e-3, 1.0, 1e3].get(rng.below(3) as usize).copied()?; let origin = rng.point3(-2.0, 2.0).map(|v| v * scale); let normal = [rng.uniform(-1.0, 1.0), rng.uniform(-1.0, 1.0), 1.0]; + let wires = rng.chance(0.5); + let void_state = if wires { + None + } else { + Some(rng.below(3) as i32) + }; Some( json!({"brep": {"kind":"face", "surface":{"plane":{"o":origin,"n":normal}}, - "dom":[-scale,-scale,scale,scale]}, "void_state":rng.below(3) as i32, "wires":rng.chance(0.5)}), + "dom":[-scale,-scale,scale,scale]}, "void_state":void_state, "wires":wires}), ) } } diff --git a/crates/solidcore-ffi/src/units/topology_edgeuse_p1.rs b/crates/solidcore-ffi/src/units/topology_edgeuse_p1.rs index 713d40a091..575df2d838 100644 --- a/crates/solidcore-ffi/src/units/topology_edgeuse_p1.rs +++ b/crates/solidcore-ffi/src/units/topology_edgeuse_p1.rs @@ -33,10 +33,11 @@ //! | `Gaps` | `eu which flag want_out` | per `which` (`0` `GetMaxEdgeFaceGap`, `1` `GetEdgeCCWEdgeGap`, `2` `CalcMaxEdgeFaceGap`, `3` `CalcEdgeCCWEdgeGap`) | //! | `SetOrient` | `eu orientation` | `orient mate radial` | //! -//! The graph export ends with a versioned extension: `2 infinite_region`, then +//! The graph export ends with a versioned extension: `3 infinite_region`, then //! one raw void flag per region and one raw wire-edge-use/vertex-use head index //! per shell (`-1` for other member classes), then a vertex-use index list per -//! vertex in native list order. The original graph prefix and the +//! vertex in native list order, then literal next/last indices per face use. +//! The original graph prefix and the //! ten entity counts remain unchanged. This supplies shell state independently //! of the shell operations being compared. //! @@ -46,7 +47,8 @@ //! //! [`eval`] accepts only what upstream handles without undefined behaviour: existing //! `.smb` files, well-formed clamped B-splines, planes with a non-zero normal, -//! parameters within 100 domain lengths of a surface's domain, indices inside the +//! parameters within 100 domain lengths of a surface's domain, plane-face domains +//! bounded by 1e6 for graph export (1e4 for other operations), indices inside the //! B-rep's export (it builds the B-rep once more to count), finite inputs. use std::path::Path; @@ -325,7 +327,7 @@ fn uv(rd: &mut Reader<'_>, dom: Domain) -> Result<(), FfiError> { } /// A B-rep block. -fn brep_block(rd: &mut Reader<'_>, path: &str) -> Result<(), FfiError> { +fn brep_block(rd: &mut Reader<'_>, path: &str, plane_domain_limit: f64) -> Result<(), FfiError> { match rd.index(4)? { 0 => { let p = Path::new(path); @@ -359,7 +361,7 @@ fn brep_block(rd: &mut Reader<'_>, path: &str) -> Result<(), FfiError> { } } None => { - if d.iter().any(|x| x.abs() > 1e4) { + if d.iter().any(|x| x.abs() > plane_domain_limit) { return Err(bad("plane face domain too large")); } } @@ -550,7 +552,11 @@ pub fn eval(op: Op, path: &str, input: &[f64]) -> Result, FfiError> { rd.done()?; return raw(op, path, input); } - brep_block(&mut rd, path)?; + // Export constructs the graph but does not run an edge-use geometry + // operation. Actual native shell probes cover plane domains through 1e6; + // retain the existing 1e4 bound for the other edge-use operations. + let plane_domain_limit = if op == Op::Export { 1e6 } else { 1e4 }; + brep_block(&mut rd, path, plane_domain_limit)?; let block = input.get(..rd.at).ok_or_else(|| bad("input too short"))?; let export = raw(Op::Export, path, block)?; if op == Op::Export { diff --git a/crates/solidcore-ffi/src/units/topology_shell.rs b/crates/solidcore-ffi/src/units/topology_shell.rs index c1665ea19c..27b6012219 100644 --- a/crates/solidcore-ffi/src/units/topology_shell.rs +++ b/crates/solidcore-ffi/src/units/topology_shell.rs @@ -70,8 +70,103 @@ pub fn wire_edges(path: &str, block: &[f64]) -> Result, FfiError> { }) } +/// Native child transfer plus all affected raw topology state and wire queries. +/// Empty shells use the real native constructor in the B-rep's context. At most +/// 64 extra shells and 64 candidate entries are accepted by this fixture adapter. +/// +/// # Errors +/// Malformed fixture data, out-of-range identities, failed native setup or a +/// nullable candidate on a branch that would dereference it in native code. +pub fn transfer( + path: &str, + block: &[f64], + extra_empty: usize, + source: usize, + recipient: usize, + candidates: Option<&[Option]>, + split_faceuse: bool, +) -> Result, FfiError> { + let export = fixture::eval(Op::Export, path, block)?; + let counts = fixture::counts(&export) + .ok_or_else(|| invalid("native topology fixture construction failed"))?; + if split_faceuse && extra_empty == 0 { + return Err(invalid("split fixture needs an extra shell")); + } + if extra_empty > 64 { + return Err(invalid("too many extra shells")); + } + let shell_count = counts + .get(1) + .copied() + .and_then(|n| n.checked_add(extra_empty)) + .ok_or_else(|| invalid("shell count overflow"))?; + let edge_count = counts + .get(7) + .copied() + .ok_or_else(|| invalid("missing edge count"))?; + if source >= shell_count || recipient >= shell_count { + return Err(invalid("shell index out of range")); + } + let entries = candidates.unwrap_or(&[]); + if entries.len() > 64 { + return Err(invalid("too many wire candidates")); + } + let mut flat = Vec::new(); + for &entry in entries { + flat.push(match entry { + None => -1, + Some(index) if index < edge_count => { + i64::try_from(index).map_err(|_| invalid("edge index overflow"))? + } + Some(_) => return Err(invalid("edge index out of range")), + }); + } + let path = std::ffi::CString::new(path).map_err(|_| invalid("path contains NUL"))?; + let count = i64::try_from(block.len()).map_err(|_| invalid("fixture too long"))?; + let source = i64::try_from(source).map_err(|_| invalid("shell index overflow"))?; + let recipient = i64::try_from(recipient).map_err(|_| invalid("shell index overflow"))?; + call_with_buffer(4096, |buffer, len| { + // SAFETY: the existing fixture wrapper validated the entire block. + // All identities, lengths and flags are bounded above; the native shim + // guards nullable candidates before unsafe branches, keeps native objects + // inside its callback and honours the output capacity. + unsafe { + sys::sc_topology_shell_transfer( + path.as_ptr(), + block.as_ptr(), + count, + extra_empty as i32, + source, + recipient, + i32::from(candidates.is_some()), + flat.as_ptr(), + flat.len() as i64, + i32::from(split_faceuse), + buffer.as_mut_ptr(), + buffer.len() as i64, + &raw mut *len, + ) + } + }) +} + mod sys { unsafe extern "C" { + pub fn sc_topology_shell_transfer( + path: *const std::ffi::c_char, + input: *const f64, + count: i64, + extra_empty: i32, + source: i64, + recipient: i64, + has_candidates: i32, + candidates: *const i64, + candidate_count: i64, + split_faceuse: i32, + output: *mut f64, + capacity: i64, + output_len: *mut i64, + ) -> i32; pub fn sc_topology_shell_wire_edges( path: *const std::ffi::c_char, input: *const f64, diff --git a/crates/solidcore/src/topology.rs b/crates/solidcore/src/topology.rs index 32cf5ea1a0..f04faed08f 100644 --- a/crates/solidcore/src/topology.rs +++ b/crates/solidcore/src/topology.rs @@ -332,6 +332,9 @@ pub struct Faceuse { pub orientation: OrientType, /// `m_pList`: the loop uses, outer loop use first. pub loopuses: Vec, + /// Literal sibling links when present, even if the owner has no list head. + /// `None` uses the owner's canonical face-use vector during construction. + pub sibling_links: Option, } /// What a loop use starts from (`m_tLoopuseType` and `m_pEUorVU`). diff --git a/crates/solidcore/src/topology/edgeuse/p1.rs b/crates/solidcore/src/topology/edgeuse/p1.rs index cadddf87a7..ee4e57b4ce 100644 --- a/crates/solidcore/src/topology/edgeuse/p1.rs +++ b/crates/solidcore/src/topology/edgeuse/p1.rs @@ -1589,6 +1589,7 @@ mod tests { mate: Some(FaceuseId::from_index(1 - k)), orientation: o, loopuses: vec![LoopuseId::from_index(k)], + sibling_links: None, }) .unwrap(); b.add_loopuse_record(Loopuse { diff --git a/crates/solidcore/src/topology/shell.rs b/crates/solidcore/src/topology/shell.rs index 47ce3cb991..d20b691c4a 100644 --- a/crates/solidcore/src/topology/shell.rs +++ b/crates/solidcore/src/topology/shell.rs @@ -3,7 +3,9 @@ //! Upstream: `source/SMLib/inc/SmShell.h`, `source/SMLib/src/SmShell.cpp`, //! `source/SMLib/inc/SmRegion.h`, `source/SMLib/src/SmRegion.cpp`, //! and the minimum default-argument branches of `source/SMLib/src/SmEdge.cpp` -//! and `source/SMLib/src/SmVertex.cpp` used by wire-edge traversal. +//! and `source/SMLib/src/SmVertex.cpp` used by wire-edge traversal; +//! `source/SMLib/inc/SmTopology.h` supplies literal sibling links and +//! `source/SMLib/inc/SmObject.h` defines the checked `SM_CAST_PTR` semantics. //! //! | Rust | Upstream | //! |------|----------| @@ -14,6 +16,8 @@ //! | [`Shell::is_closed`], [`Shell::mated_shells`] | `IsClosed`, `GetMatedShells` | //! | [`shell_region`], [`shell_brep`] | `GetRegion`, `GetBrep` | //! | [`wire_edges`] | `SmShell::GetWireEdges` | +//! | [`transfer_children`] | `SmShell::TransferChildrenTo` | +//! | [`faceuse_sibling_links`] | typed `SmTopology::GetNext` / `GetLast` for face uses | //! | `edge_vertices_for_shell`, `edge_is_wire_for_shell` | `SmEdge::GetVertices` (no attribute filter), `IsWire` | //! | `vertex_edges_for_shell` | `SmVertex::GetVertexuses`, `GetEdges` (no face filter) | //! | [`Region::get_shells`], [`Region::is_void`], [`Region::set_is_void`] | `GetShells`, `IsVoid`, `SetIsVoid` | @@ -24,6 +28,7 @@ //! shared Brep arena model. Temporary marks use a local set without changing //! first-seen order. Invalid native pointer casts return `InvalidInput`. Native //! notification/logging side effects are not represented by these arena records. +//! Face-use sibling links survive a cleared owner head, as in native self-transfer. //! There is no recursion. This is WIP; remaining operations and native gates are pending. use crate::error::{Result, SolidError}; @@ -161,15 +166,25 @@ impl Shell { } /// First face use; native logging-only errors retain the NULL result. /// + /// A head-class mismatch retains native NULL rather than becoming an error. + /// /// # Errors - /// A native list head of an incompatible class. + /// This compatibility result succeeds; native logging-only errors yield `None`. pub fn first_faceuse(&self) -> Result> { - Ok(self.get_faceuses()?.first().copied()) + if !self.is_faceuse_shell() { + return Ok(None); + } + // SM_CAST_PTR checks the actual head class and returns NULL on mismatch. + // Unlike the reinterpret-cast GetFaceuses array, this is a defined path. + Ok(match &self.members { + ShellMembers::Faceuses(ids) => ids.first().copied(), + _ => None, + }) } /// First wire edge, preserving NULL for empty or other-kind shells. /// /// # Errors - /// Incompatible member class or dangling arena references. + /// Dangling arena references; a head-class mismatch retains native NULL. pub fn wire_edge(&self, brep: &Brep) -> Result> { if !self.is_wire_shell() { return Ok(None); @@ -177,13 +192,13 @@ impl Shell { match self.members { ShellMembers::Empty => Ok(None), ShellMembers::Edgeuse(id) => Ok(brep.edgeuse(id)?.edge), - _ => Err(invalid_members()), + _ => Ok(None), } } /// Vertex of a vertex shell, preserving NULL for empty or other-kind shells. /// /// # Errors - /// Incompatible member class or dangling arena references. + /// Dangling arena references; a head-class mismatch retains native NULL. pub fn vertex(&self, brep: &Brep) -> Result> { if !self.is_vertex_shell() { return Ok(None); @@ -191,7 +206,7 @@ impl Shell { match self.members { ShellMembers::Empty => Ok(None), ShellMembers::Vertexuse(id) => Ok(brep.vertexuse(id)?.vertex), - _ => Err(invalid_members()), + _ => Ok(None), } } /// Whether a face in this shell has a mate belonging to a different shell. @@ -485,6 +500,180 @@ fn vertex_edges_for_shell(brep: &Brep, vertex: VertexId) -> Result> Ok(output) } +/// Move a shell's vertex, wire edges and face uses to another shell. +/// +/// `wire_candidates` is the native optional list of B-rep wire edges. When it +/// is present, ownership filters it in caller order; otherwise `GetWireEdges` +/// gathers the source's wires. It is never replaced by an empty default list. +/// Nullable list entries are only rejected if the native branch reads them. +/// +/// # Errors +/// Native `SM_ERR` for an illegal vertex-shell move, dangling graph links, or +/// incompatible native list classes. The caller must supply connected topology. +pub fn transfer_children( + brep: &mut Brep, + source: ShellId, + recipient: ShellId, + wire_candidates: Option<&[Option]>, +) -> Result<()> { + use crate::topology::edgeuse::p1; + use crate::topology::{EdgeuseOwner, VertexuseOwner}; + const OP: &str = "topology::shell::transfer_children"; + brep.shell(source)?; + if shell_list_size(brep, recipient)? == 0 { + brep.shell_mut(recipient)?.shell_type = ShellType::Unknown; + if brep.shell(source)?.is_vertex_shell() { + let ShellMembers::Vertexuse(vertexuse) = brep.shell(source)?.members else { + return Err(invalid_members()); + }; + brep.vertexuse(vertexuse)?; + let target = brep.shell_mut(recipient)?; + target.shell_type = ShellType::Vertex; + target.members = ShellMembers::Vertexuse(vertexuse); + brep.vertexuse_mut(vertexuse)?.owner = VertexuseOwner::Shell(Some(recipient)); + clear_shell_members(brep, source)?; + return Ok(()); + } + } else { + if brep.shell(recipient)?.is_vertex_shell() { + return if shell_list_size(brep, source)? == 0 { + Ok(()) + } else { + Err(SolidError::sm_err(OP)) + }; + } + if brep.shell(source)?.is_vertex_shell() { + return Err(SolidError::sm_err(OP)); + } + } + + let wires = if let Some(candidates) = wire_candidates { + let mut wires = Vec::new(); + for &edge in candidates { + let edge = edge.ok_or(invalid_members())?; + let primary = p1::primary_edgeuse(brep, edge)?.ok_or(invalid_members())?; + if brep.edgeuse(primary)?.owner == EdgeuseOwner::Shell(Some(source)) { + wires.push(edge); + } + } + wires + } else { + wire_edges(brep, source)? + }; + if let Some(&first) = wires.first() { + if brep.shell(recipient)?.is_unknown_shell() { + let primary = p1::primary_edgeuse(brep, first)?.ok_or(invalid_members())?; + let target = brep.shell_mut(recipient)?; + target.members = ShellMembers::Edgeuse(primary); + target.shell_type = ShellType::Wire; + // UpdateListSize counts this edge's radial list; the shared arena + // model retains that list on Edge::edgeuses rather than separately. + } + for edge in wires { + // SmEdge::GetEdgeuses with neither optional filter. The shared edge + // record already retains its native circular radial list order. + let edgeuses = brep.edge(edge)?.edgeuses.clone(); + for edgeuse in edgeuses { + brep.edgeuse_mut(edgeuse)?.owner = EdgeuseOwner::Shell(Some(recipient)); + } + } + } + + let faceuses = brep.shell(source)?.get_faceuses()?; + if !faceuses.is_empty() { + if !brep.shell(recipient)?.is_faceuse_shell() { + let target = brep.shell_mut(recipient)?; + target.shell_type = ShellType::Faceuse; + target.members = ShellMembers::Empty; + } + for faceuse in faceuses { + if brep.faceuse(faceuse)?.shell != Some(source) { + return Err(invalid_members()); + } + // Remove, then PostInsert: the member moves to the recipient's tail + // without changing either list's remaining relative order. + match &mut brep.shell_mut(source)?.members { + ShellMembers::Faceuses(members) => { + let index = members + .iter() + .position(|id| *id == faceuse) + .ok_or(invalid_members())?; + members.remove(index); + } + _ => return Err(invalid_members()), + } + let target = &mut brep.shell_mut(recipient)?.members; + match target { + ShellMembers::Empty => *target = ShellMembers::Faceuses(vec![faceuse]), + ShellMembers::Faceuses(members) => members.push(faceuse), + _ => return Err(invalid_members()), + } + brep.faceuse_mut(faceuse)?.shell = Some(recipient); + } + // Self-transfer clears the shell head but leaves this face-use cycle + // connected. Preserve those native links independently of the head. + refresh_faceuse_sibling_links(brep, recipient)?; + } + clear_shell_members(brep, source) +} + +fn clear_shell_members(brep: &mut Brep, id: ShellId) -> Result<()> { + let shell = brep.shell_mut(id)?; + shell.shell_type = ShellType::Unknown; + shell.members = ShellMembers::Empty; + Ok(()) +} + +fn shell_list_size(brep: &Brep, id: ShellId) -> Result { + match &brep.shell(id)?.members { + ShellMembers::Empty => Ok(0), + ShellMembers::Faceuses(members) => Ok(members.len()), + ShellMembers::Vertexuse(_) => Ok(1), + ShellMembers::Edgeuse(id) => { + let edge = brep.edgeuse(*id)?.edge.ok_or(invalid_members())?; + Ok(brep.edge(edge)?.edgeuses.len()) + } + } +} + +/// Native next/previous face-use links, independently of a shell's list head. +pub type FaceuseSiblingLinks = (Option, Option); + +/// Typed adapter for a face use's native `GetNext` and `GetLast` links. +/// +/// # Errors +/// A dangling id or a constructed owner vector that does not contain its member. +pub fn faceuse_sibling_links(brep: &Brep, id: FaceuseId) -> Result { + let faceuse = brep.faceuse(id)?; + if let Some(links) = faceuse.sibling_links { + return Ok(links); + } + let Some(owner) = faceuse.shell else { + return Ok((None, None)); + }; + let ShellMembers::Faceuses(members) = &brep.shell(owner)?.members else { + return Err(invalid_members()); + }; + let mut previous = members.last().copied(); + for (&member, &next) in members.iter().zip(members.iter().cycle().skip(1)) { + if member == id { + return Ok((Some(next), previous)); + } + previous = Some(member); + } + Err(invalid_members()) +} + +fn refresh_faceuse_sibling_links(brep: &mut Brep, owner: ShellId) -> Result<()> { + let members = brep.shell(owner)?.get_faceuses()?; + let mut previous = members.last().copied(); + for (&member, &next) in members.iter().zip(members.iter().cycle().skip(1)) { + brep.faceuse_mut(member)?.sibling_links = Some((Some(next), previous)); + previous = Some(member); + } + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/fixtures/topology.shell/transfer.json b/fixtures/topology.shell/transfer.json new file mode 100644 index 0000000000..a280edb2fb --- /dev/null +++ b/fixtures/topology.shell/transfer.json @@ -0,0 +1,2315 @@ +{ + "cases": [ + { + "name": "self_vertex_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_vertex_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_vertex_1e-07_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "illegal_vertex_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "illegal_vertex_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "illegal_vertex_1e-07_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "vertex_to_empty_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": null + } + } + }, + { + "name": "vertex_to_empty_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": [] + } + } + }, + { + "name": "vertex_to_empty_1e-07_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "empty_to_vertex_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_vertex_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "empty_to_vertex_1e-07_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "empty_to_empty_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 2, + "source": 2, + "recipient": 3, + "candidates": null + } + } + }, + { + "name": "empty_to_empty_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1e-07, + -1e-07, + 1e-07 + ] + ] + }, + "transfer": { + "extra_empty": 2, + "source": 2, + "recipient": 3, + "candidates": [] + } + } + }, + { + "name": "self_face_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1e-07, + -1e-07, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1e-07, + -1e-07, + 1e-07, + 1e-07 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_face_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1e-07, + -1e-07, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1e-07, + -1e-07, + 1e-07, + 1e-07 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "face_to_empty_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1e-07, + -1e-07, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1e-07, + -1e-07, + 1e-07, + 1e-07 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "face_to_empty_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1e-07, + -1e-07, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1e-07, + -1e-07, + 1e-07, + 1e-07 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "empty_to_face_1e-07_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1e-07, + -1e-07, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1e-07, + -1e-07, + 1e-07, + 1e-07 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_face_1e-07_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1e-07, + -1e-07, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1e-07, + -1e-07, + 1e-07, + 1e-07 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_vertex_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_vertex_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_vertex_1_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "illegal_vertex_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "illegal_vertex_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "illegal_vertex_1_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "vertex_to_empty_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": null + } + } + }, + { + "name": "vertex_to_empty_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": [] + } + } + }, + { + "name": "vertex_to_empty_1_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "empty_to_vertex_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_vertex_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "empty_to_vertex_1_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "empty_to_empty_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 2, + "source": 2, + "recipient": 3, + "candidates": null + } + } + }, + { + "name": "empty_to_empty_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1, + -1, + 1 + ] + ] + }, + "transfer": { + "extra_empty": 2, + "source": 2, + "recipient": 3, + "candidates": [] + } + } + }, + { + "name": "self_face_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1, + -1, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1, + -1, + 1, + 1 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_face_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1, + -1, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1, + -1, + 1, + 1 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "face_to_empty_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1, + -1, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1, + -1, + 1, + 1 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "face_to_empty_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1, + -1, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1, + -1, + 1, + 1 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "empty_to_face_1_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1, + -1, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1, + -1, + 1, + 1 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_face_1_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1, + -1, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1, + -1, + 1, + 1 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_vertex_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_vertex_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_vertex_1000000.0_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 0, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "illegal_vertex_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "illegal_vertex_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "illegal_vertex_1000000.0_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "vertex_to_empty_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": null + } + } + }, + { + "name": "vertex_to_empty_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": [] + } + } + }, + { + "name": "vertex_to_empty_1000000.0_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 2, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "empty_to_vertex_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_vertex_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "empty_to_vertex_1000000.0_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 1, + "source": 2, + "recipient": 0, + "candidates": [ + -1 + ] + } + } + }, + { + "name": "empty_to_empty_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 2, + "source": 2, + "recipient": 3, + "candidates": null + } + } + }, + { + "name": "empty_to_empty_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "vertices", + "points": [ + [ + 0, + 0, + 0 + ], + [ + 1000000.0, + -1000000.0, + 1000000.0 + ] + ] + }, + "transfer": { + "extra_empty": 2, + "source": 2, + "recipient": 3, + "candidates": [] + } + } + }, + { + "name": "self_face_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1000000.0, + -1000000.0, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1000000.0, + -1000000.0, + 1000000.0, + 1000000.0 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_face_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1000000.0, + -1000000.0, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1000000.0, + -1000000.0, + 1000000.0, + 1000000.0 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "face_to_empty_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1000000.0, + -1000000.0, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1000000.0, + -1000000.0, + 1000000.0, + 1000000.0 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "face_to_empty_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1000000.0, + -1000000.0, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1000000.0, + -1000000.0, + 1000000.0, + 1000000.0 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "empty_to_face_1000000.0_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1000000.0, + -1000000.0, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1000000.0, + -1000000.0, + 1000000.0, + 1000000.0 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_face_1000000.0_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "face", + "surface": { + "plane": { + "o": [ + 1000000.0, + -1000000.0, + 0 + ], + "n": [ + 0, + 0, + 1 + ] + } + }, + "dom": [ + -1000000.0, + -1000000.0, + 1000000.0, + 1000000.0 + ] + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_wire_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "self_wire_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "self_wire_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [ + 0 + ] + } + } + }, + { + "name": "self_wire_3", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [ + 0, + 0 + ] + } + } + }, + { + "name": "self_wire_4", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [ + 4, + 2, + 0 + ] + } + } + }, + { + "name": "self_wire_5", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 0, + "candidates": [ + 4, + 3, + 2, + 1, + 0 + ] + } + } + }, + { + "name": "wire_to_empty_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": null + } + } + }, + { + "name": "wire_to_empty_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [] + } + } + }, + { + "name": "wire_to_empty_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [ + 0 + ] + } + } + }, + { + "name": "wire_to_empty_3", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [ + 0, + 0 + ] + } + } + }, + { + "name": "wire_to_empty_4", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [ + 4, + 2, + 0 + ] + } + } + }, + { + "name": "wire_to_empty_5", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [ + 4, + 3, + 2, + 1, + 0 + ] + } + } + }, + { + "name": "empty_to_wire_0", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": null + } + } + }, + { + "name": "empty_to_wire_1", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [] + } + } + }, + { + "name": "empty_to_wire_2", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [ + 0 + ] + } + } + }, + { + "name": "empty_to_wire_3", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [ + 0, + 0 + ] + } + } + }, + { + "name": "empty_to_wire_4", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [ + 4, + 2, + 0 + ] + } + } + }, + { + "name": "empty_to_wire_5", + "upstream_ref": "source/SMLib/src/SmShell.cpp:364 TransferChildrenTo; native self-transfer probe", + "inputs": { + "brep": { + "kind": "network" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [ + 4, + 3, + 2, + 1, + 0 + ] + } + } + }, + { + "name": "existing_faceuses_Mouse.smb_0_1_0", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "Mouse.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": null, + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_Mouse.smb_0_1_1", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "Mouse.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [], + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_Mouse.smb_1_0_0", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "Mouse.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 1, + "recipient": 0, + "candidates": null, + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_Mouse.smb_1_0_1", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "Mouse.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 1, + "recipient": 0, + "candidates": [], + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_Endcap.smb_0_1_0", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo; SmOwningTopology::Remove/PostInsert builds two nonempty baseline shells", + "inputs": { + "brep": { + "kind": "file", + "file": "Endcap.smb" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": null, + "require_faceuse_pair": true, + "split_faceuse": true + } + } + }, + { + "name": "existing_faceuses_Endcap.smb_0_1_1", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo; SmOwningTopology::Remove/PostInsert builds two nonempty baseline shells", + "inputs": { + "brep": { + "kind": "file", + "file": "Endcap.smb" + }, + "transfer": { + "extra_empty": 1, + "source": 0, + "recipient": 1, + "candidates": [], + "require_faceuse_pair": true, + "split_faceuse": true + } + } + }, + { + "name": "existing_faceuses_Endcap.smb_1_0_0", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo; SmOwningTopology::Remove/PostInsert builds two nonempty baseline shells", + "inputs": { + "brep": { + "kind": "file", + "file": "Endcap.smb" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": null, + "require_faceuse_pair": true, + "split_faceuse": true + } + } + }, + { + "name": "existing_faceuses_Endcap.smb_1_0_1", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo; SmOwningTopology::Remove/PostInsert builds two nonempty baseline shells", + "inputs": { + "brep": { + "kind": "file", + "file": "Endcap.smb" + }, + "transfer": { + "extra_empty": 1, + "source": 1, + "recipient": 0, + "candidates": [], + "require_faceuse_pair": true, + "split_faceuse": true + } + } + }, + { + "name": "existing_faceuses_box_with_large_fillet.smb_0_1_0", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "box_with_large_fillet.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": null, + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_box_with_large_fillet.smb_0_1_1", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "box_with_large_fillet.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 0, + "recipient": 1, + "candidates": [], + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_box_with_large_fillet.smb_1_0_0", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "box_with_large_fillet.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 1, + "recipient": 0, + "candidates": null, + "require_faceuse_pair": true + } + } + }, + { + "name": "existing_faceuses_box_with_large_fillet.smb_1_0_1", + "upstream_ref": "fixtures/topology.edgeuse_p1/upstream_files.json; source/SMLib/src/SmShell.cpp:TransferChildrenTo", + "inputs": { + "brep": { + "kind": "file", + "file": "box_with_large_fillet.smb" + }, + "transfer": { + "extra_empty": 0, + "source": 1, + "recipient": 0, + "candidates": [], + "require_faceuse_pair": true + } + } + } + ] +} diff --git a/harness/cpp/shim/topology_edgeuse_p1.cpp b/harness/cpp/shim/topology_edgeuse_p1.cpp index c7778689e8..a855bae1be 100644 --- a/harness/cpp/shim/topology_edgeuse_p1.cpp +++ b/harness/cpp/shim/topology_edgeuse_p1.cpp @@ -795,7 +795,7 @@ void export_graph(SmBrep* brep, Graph& g, std::vector& o) } // Versioned shell/region extension. Read raw state, never shell operations // under test. Keep the existing graph prefix and its entity counts stable. - o.push_back(2.0); + o.push_back(3.0); o.push_back((double)g.find(0, brep->GetInfiniteRegion())); for (SmRegion* p : g.regions) o.push_back((double)(p->*RegionPeek::is_void())); @@ -811,6 +811,11 @@ void export_graph(SmBrep* brep, Graph& g, std::vector& o) for (SmTopology* use : list_of(p->GetList())) uses.push_back(g.find(8, use)); put_list(o, uses); } + for (SmFaceuse* p : g.faceuses) + { + o.push_back((double)g.find(2, p->GetNext())); + o.push_back((double)g.find(2, p->GetLast())); + } } // Builds the B-rep of a B-rep block; nullptr (status in `st`) when construction fails. diff --git a/harness/cpp/shim/topology_shell.cpp b/harness/cpp/shim/topology_shell.cpp index 3e09d4ca60..279b57930e 100644 --- a/harness/cpp/shim/topology_shell.cpp +++ b/harness/cpp/shim/topology_shell.cpp @@ -98,3 +98,183 @@ SC_EXPORT int32_t sc_topology_shell_wire_edges(const char* path, const double* i }); SC_CATCH } +// Raw mutation snapshots: independent of shell operations being compared. +#include +#include +#include +#include +#include +#include + +namespace transfer_detail { +struct NodePeek : SmTopology { + static SmOwningTopology* SmTopology::*owner() { return &NodePeek::m_pListOwner; } + static SmTopology* SmTopology::*next() { return &NodePeek::m_pNext; } + static SmTopology* SmTopology::*last() { return &NodePeek::m_pLast; } +}; +struct OwnerPeek : SmOwningTopology { + static SmTopology* SmOwningTopology::*head() { return &OwnerPeek::m_pList; } + static ULONG SmOwningTopology::*size() { return &OwnerPeek::m_lListSize; } +}; +struct ShellPeek : SmShell { + static SM_TYPE SmShell::*type() { return &ShellPeek::m_tShellType; } +}; +struct EuPeek : SmEdgeuse { + static SmTopology* SmEdgeuse::*owner() { return &EuPeek::m_pSorLU; } +}; +struct VuPeek : SmVertexuse { + static SmTopology* SmVertexuse::*owner() { return &VuPeek::m_pSorLUorEU; } +}; +// Expose the real SmShell constructor, with its real context and region owner. +struct EmptyShell : SmShell { EmptyShell() : SmShell() {} }; + +struct Restore { + std::vector> actions; + Restore(const sc_topology_fixture::View& view, const std::vector& shells) { + for (SmShell* p : shells) { + const auto type=p->*ShellPeek::type(); + const auto head=p->*OwnerPeek::head(); + const auto size=p->*OwnerPeek::size(); + actions.push_back([p,type,head,size] { + p->*ShellPeek::type()=type; + p->*OwnerPeek::head()=head; + p->*OwnerPeek::size()=size; + }); + } + for (SmFaceuse* p : view.faceuses) { + const auto owner=p->*NodePeek::owner(); + const auto next=p->*NodePeek::next(); + const auto last=p->*NodePeek::last(); + actions.push_back([p,owner,next,last] { + p->*NodePeek::owner()=owner; p->*NodePeek::next()=next; p->*NodePeek::last()=last; + }); + } + for (SmEdgeuse* p : view.edgeuses) { + const auto owner=p->*EuPeek::owner(); + actions.push_back([p,owner] { p->*EuPeek::owner()=owner; }); + } + for (SmVertexuse* p : view.vertexuses) { + const auto owner=p->*VuPeek::owner(); + actions.push_back([p,owner] { p->*VuPeek::owner()=owner; }); + } + } + ~Restore() { for (const auto& action : actions) action(); } +}; + +void snapshot(std::vector& out, const sc_topology_fixture::View& view, + const std::vector& shells) { + out.insert(out.end(), {(double)shells.size(), (double)view.faceuses.size(), + (double)view.edgeuses.size(), (double)view.vertexuses.size(), (double)view.regions.size()}); + for (SmShell* p : shells) { + SmTopology* head=p->*OwnerPeek::head(); + const SM_TYPE kind=head ? head->GetType() : 0; + double id=-1; + if (kind==SmFaceuse_TYPE) id=identity(view.faceuses,(SmFaceuse*)head); + else if (kind==SmEdgeuse_TYPE) id=identity(view.edgeuses,(SmEdgeuse*)head); + else if (kind==SmVertexuse_TYPE) id=identity(view.vertexuses,(SmVertexuse*)head); + else if (head) throw 1; + out.insert(out.end(), {(double)(p->*ShellPeek::type()), (double)(p->*OwnerPeek::size()), + identity(view.regions,(SmRegion*)(p->*NodePeek::owner())), (double)kind, id}); + } + for (SmFaceuse* p : view.faceuses) { + out.insert(out.end(), {identity(shells,(SmShell*)(p->*NodePeek::owner())), + identity(view.faceuses,(SmFaceuse*)(p->*NodePeek::next())), + identity(view.faceuses,(SmFaceuse*)(p->*NodePeek::last()))}); + } + for (SmEdgeuse* p : view.edgeuses) { + const SM_TYPE kind=p->GetEdgeuseType(); + SmTopology* owner=p->*EuPeek::owner(); + const double id=kind==SmShell_TYPE ? identity(shells,(SmShell*)owner) + : kind==SmLoopuse_TYPE ? identity(view.loopuses,(SmLoopuse*)owner) : -1; + out.insert(out.end(), {(double)kind,id}); + } + for (SmVertexuse* p : view.vertexuses) { + const SM_TYPE kind=p->GetVertexuseType(); + SmTopology* owner=p->*VuPeek::owner(); + const double id=kind==SmShell_TYPE ? identity(shells,(SmShell*)owner) + : kind==SmLoopuse_TYPE ? identity(view.loopuses,(SmLoopuse*)owner) + : kind==SmEdgeuse_TYPE ? identity(view.edgeuses,(SmEdgeuse*)owner) : -1; + out.insert(out.end(), {(double)kind,id}); + } + for (SmRegion* p : view.regions) { + SmTopology* head=p->*OwnerPeek::head(); + out.insert(out.end(), {(double)(p->*OwnerPeek::size()), identity(shells,(SmShell*)head)}); + std::vector members; + SmTopology* node=head; + while (node) { + if (members.size()>=100000 || node->GetType()!=SmShell_TYPE) throw 1; + members.push_back(identity(shells,(SmShell*)node)); + node=node->*NodePeek::next(); + if (node==head) break; + } + out.push_back((double)members.size()); + out.insert(out.end(),members.begin(),members.end()); + } +} +} + +SC_EXPORT int32_t sc_topology_shell_transfer(const char* path, const double* input, + int64_t count, int32_t extra_empty, int64_t source, int64_t recipient, + int32_t has_candidates, const int64_t* candidates, int64_t candidate_count, + int32_t split_faceuse, double* output, int64_t cap, int64_t* output_len) { + if (!output_len || cap<0 || (cap && !output) || extra_empty<0 || extra_empty>64 + || source<0 || recipient<0 || (has_candidates!=0 && has_candidates!=1) + || candidate_count<0 || candidate_count>64 || (candidate_count && !candidates) + || (!has_candidates && candidate_count) || (split_faceuse!=0 && split_faceuse!=1)) return SC_ERR_ARG; + SC_TRY + return sc_topology_fixture::apply(path,input,count, + [&](const sc_topology_fixture::View& view)->int32_t { + std::vector shells=view.shells; + SmRegion* region=view.brep.GetInfiniteRegion(); + if (!region || !view.brep.GetContext()) return SC_ERR_ARG; + for (int32_t i=0;i shell( + new (&view.brep) transfer_detail::EmptyShell()); + shells.push_back(shell.get()); + if (region->PostInsert(shell.get())!=SM_SUCCESS) return SC_ERR_ARG; + shell.release(); + } + if (split_faceuse) { + if (view.shells.empty() || extra_empty<1) return SC_ERR_ARG; + SmShell* original=view.shells.front(); + SmShell* extra=shells[view.shells.size()]; + SmTopology* head=original->GetList(); + if (!original->IsFaceuseShell() || original->GetSize()<2 || !head + || head->GetType()!=SmFaceuse_TYPE) return SC_ERR_ARG; + // Fixture construction uses the already-existing base list + // operations, never TransferChildrenTo under test. + if (original->Remove(head)!=SM_SUCCESS) return SC_ERR_ARG; + extra->*transfer_detail::ShellPeek::type()=SmFaceuse_TYPE; + if (extra->PostInsert(head)!=SM_SUCCESS) return SC_ERR_ARG; + } + if (source>=(int64_t)shells.size() || recipient>=(int64_t)shells.size()) return SC_ERR_ARG; + SmShell* from=shells[(size_t)source]; + SmShell* to=shells[(size_t)recipient]; + const bool reads_candidates=!(to->GetSize()==0 && from->IsVertexShell()) + && !(to->GetSize()!=0 && (to->IsVertexShell() || from->IsVertexShell())); + SmTArray wires; + for (int64_t i=0;i= (int64_t)view.edges.size()) return SC_ERR_ARG; + SmEdge* edge=index==-1 ? nullptr : view.edges[(size_t)index]; + if (reads_candidates && (!edge || !edge->GetPrimaryEdgeuse())) return SC_ERR_ARG; + wires.Add(edge); + } + // Snapshot the result before restoring canonical links for deletion. + // Self-transfer is compared, including its detached face-use cycle. + transfer_detail::Restore restore(view,shells); + const auto status=from->TransferChildrenTo(to,has_candidates ? &wires : nullptr); + std::vector out{status==SM_SUCCESS ? 0.0 : (double)status}; + transfer_detail::snapshot(out,view,shells); + // Exercise both connected-wire traversal and global ownership + // filtering on the actual post-transfer topology. + for (SmShell* shell : shells) { + SmTArray after; + const auto query_status=shell->GetWireEdges(after); + out.push_back(query_status==SM_SUCCESS ? 0.0 : (double)query_status); + list(out,after,view.edges); + } + return sc_copy_out(out.data(),(int64_t)out.size(),output,cap,output_len); + }); + SC_CATCH +} diff --git a/plan/findings.md b/plan/findings.md index cca0ff7f66..56ffe5d348 100644 --- a/plan/findings.md +++ b/plan/findings.md @@ -3866,7 +3866,48 @@ unregistered pointer in its snapshot. Native GetWireEdges returns traversal IDs `0, 2, 4, 1, 3`; the order is intentionally neither sorted nor forced by the initial edge. No cases, tolerances or pointer checks were removed or weakened. -The complete Rust/C++ rerun is pending. The unit remains todo/draft while the -remaining owned operations and backedges are implemented. Issue #86 coordinates +The complete rerun passes all 144 shell comparisons and restores edge-use p1 to +2,449 passes with 273 genuine deferrals out of 2,722 cases, zero failures. Strict +C++ Clippy, formatting and the three existing kernel regressions pass. +Perturbation detects every compared case in both units. The unit remains +todo/draft while the remaining owned operations and backedges are implemented. Issue #86 coordinates the actual region lookup context needed by attribute operations; it is separate from this fixture correction and from the excluded SSI work. + + +## topology.shell WIP: child transfers and literal sibling links (PR #80) + +A native probe of self-transfer on a real plane-face B-rep returns SM_SUCCESS, +clears the shell head/count/type to NULL/0/unknown, and leaves each face use's +owner and next/previous cycle intact. Faceuse now retains literal sibling links +independently of the owner's head; a canonical construction vector remains the +fallback before literal links are supplied. TransferChildrenTo preserves this +observed behavior rather than treating self-transfer as a no-op or excluding it. + +The transfer adapter snapshots every affected shell head/count/type, face-use +owner/sibling link, edge-use/vertex-use owner and region list, then exercises +GetWireEdges on the post-transfer topology. A scope guard restores the native +links before B-rep destruction; this does not alter the observed result. Native +probes confirm partial/duplicate wire lists, nullable entries ignored by early +vertex branches, rejection before unsafe NULL dereferences, and real plane-face +domains at 1e-7/1/1e6. Graph export alone accepts plane domains through the native- +verified 1e6 bound; the existing edge-use-operation bound stays at 1e4. Comparison +tolerances are unchanged. + +The first 234-case run exposed four fixture precondition errors: Endcap.smb has +one face-use shell with two members, whereas Mouse and box_with_large_fillet have +two nonempty face-use shells (4/4 and 7/7 members). The Endcap setup now uses native +base Remove/PostInsert operations to form two nonempty shells, independently of +TransferChildrenTo. All four cases and the nonempty-recipient coverage assertion +are retained. The corrected run passes all 234 shell cases, with zero failures, +zero deferrals and exact integer/topology agreement; perturbation detects all 234. +Edge-use p1 retains 2449 passes/273 deferrals out of 2722, zero failures, and all +2449 comparisons are detected under perturbation. Strict C++ Clippy, formatting +and the three kernel regressions pass. + +Subsequent source audit found that SM_CAST_PTR explicitly returns NULL for a +head-class mismatch. GetVertex, GetWireEdge and GetFirstFaceuse now preserve that +defined NULL result rather than returning an incompatible-class error. Dedicated +metadata-override comparisons and a rerun after that correction remain pending. +The unit is still todo/draft: remaining owned operations, backedges, final gates +and the region-context coordination in #86 are not claimed complete. diff --git a/plan/modules.toml b/plan/modules.toml index 64ce4b4b38..896c5e9eeb 100644 --- a/plan/modules.toml +++ b/plan/modules.toml @@ -742,7 +742,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "green" claimed_by = "swarm-topology.topology-a1" claimed_at = "2026-10-12T21:22:31Z" -notes = "GREEN: difftest 657 cases (57 upstream-derived/edge-case fixtures + 600 generated scripts: marks under SmNewMarkAndLock, flags/user values, Notify propagation for every operation incl. the callback pass-through, tolerance, IsConnectedTo, destruction, SmTrackTopologyChanges start/stop/nesting/foreign callback), 0 deferred, bit-identical; wired SmSAGObject::Notify (geom.a_object 169 -> 168 deferred) and retargeted the SmTol::GetZoneTol3d(SmObject*) markers to the derived-kind units; SmTopologyChangeCallback::Execute is unused upstream and not ported (findings.md); 30 fns; largest: SmTopology::Notify, SmTrackTopologyChanges::StartTracking, SmTrackTopologyChanges::StopTracking, SmTrackTopologyChanges::SmTrackTopologyChanges; cycle back-edges (call into later units; stub or co-port): topology.proto_topology_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit" +notes = "GREEN: difftest 657 cases (57 upstream-derived/edge-case fixtures + 600 generated scripts: marks under SmNewMarkAndLock, flags/user values, Notify propagation for every operation incl. the callback pass-through, tolerance, IsConnectedTo, destruction, SmTrackTopologyChanges start/stop/nesting/foreign callback), 0 deferred, bit-identical; wired SmSAGObject::Notify (geom.a_object 169 -> 168 deferred) and retargeted the SmTol::GetZoneTol3d(SmObject*) markers to the derived-kind units; SmTopologyChangeCallback::Execute is unused upstream and not ported (findings.md); 30 fns; largest: SmTopology::Notify, SmTrackTopologyChanges::StartTracking, SmTrackTopologyChanges::StopTracking, SmTrackTopologyChanges::SmTrackTopologyChanges; cycle back-edges (call into later units; stub or co-port): topology.proto_topology_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.shell (PR #80): SmTopology face-use sibling links -> topology::Faceuse::sibling_links and topology::shell::faceuse_sibling_links, retaining literal next/last links after self-transfer clears the shell head" [[unit]] id = "nlib.crv_adv_p1" @@ -2534,7 +2534,7 @@ tests = ["prog_test:my_test_extent3d", "prog_test:my_test_vector2d", "prog_test: status = "todo" claimed_by = "" claimed_at = "" -notes = "60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}; CO-PORTED in topology.shell (PR #80): SmEdge::GetVertices default no-attribute branch and IsWire -> topology::shell::{edge_vertices_for_shell,edge_is_wire_for_shell}, using primary edge-use orientation and actual mate/vertex-use links" +notes = "60 fns; largest: SmEdge::IsTopological, SmEdge::FindRadialSector, SmEdge::GetBlendEdgeuse, SmEdge::OrientEdgeSequence; cycle back-edges (call into later units; stub or co-port): heal.gap(12), topology.edgeuse_p3(3), query.edge(2), topology.brep_p2(2), blend.fillet_geom_p1(1), topology.brep_p1(1), topology.face_p1(1); unit-SCC #22 (220 units); 319 upstream test entry points reach this unit; CO-PORTED in topology.edgeuse_p1 (PR #55): SmEdge members m_pList (edge use list, primary first), m_vInterval, m_sZoneTol3d, m_pCurve -> topology::Edge::{edgeuses, interval, tolerance, curve}; SmEdge::GetPrimaryEdgeuse, IsLamina, GetBrep -> topology::edgeuse::p1::{primary_edgeuse, edge_is_lamina, edge_brep}; CO-PORTED in topology.shell (PR #80): SmEdge::GetVertices default no-attribute branch and IsWire -> topology::shell::{edge_vertices_for_shell,edge_is_wire_for_shell}, using primary edge-use orientation and actual mate/vertex-use links; SmEdge::GetEdgeuses default no-filter branch -> topology::shell::transfer_children via the native radial Edge::edgeuses list" [[unit]] id = "blend.fillet_custom_solver"