mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 04:52:10 +00:00
Merge pull request #1650 from brett19/fix/caller-keyed-ops-owner-identity
fix(topology): one owner identity for binders and check_brep, so ops on caller-keyed copies check valid (#1631)
This commit is contained in:
@@ -1687,15 +1687,15 @@ fn notification_key(entity: EntityRef, namespace: u64) -> ObjectRef {
|
||||
}
|
||||
}
|
||||
|
||||
fn original_notification_key(entity: EntityRef) -> ObjectRef {
|
||||
pub(super) fn original_notification_key(entity: EntityRef) -> ObjectRef {
|
||||
notification_key(entity, 1)
|
||||
}
|
||||
|
||||
fn result_notification_key(entity: EntityRef) -> ObjectRef {
|
||||
pub(super) fn result_notification_key(entity: EntityRef) -> ObjectRef {
|
||||
notification_key(entity, 3)
|
||||
}
|
||||
|
||||
fn original_curve_key(edge: EdgeId, type_code: i64) -> ObjectRef {
|
||||
pub(super) fn original_curve_key(edge: EdgeId, type_code: i64) -> ObjectRef {
|
||||
ObjectRef {
|
||||
key: ObjectKey((5_u64 << 60) | ((type_code as u64) << 32) | u64::from(edge.index())),
|
||||
type_code,
|
||||
@@ -1703,7 +1703,7 @@ fn original_curve_key(edge: EdgeId, type_code: i64) -> ObjectRef {
|
||||
}
|
||||
|
||||
// Source lists are opaque notification handles, disjoint from topology and curves.
|
||||
fn boolean_source_lists(result: bool, region: RegionId) -> [ObjectRef; 2] {
|
||||
pub(super) fn boolean_source_lists(result: bool, region: RegionId) -> [ObjectRef; 2] {
|
||||
let namespace = if result { 7_u64 } else { 6_u64 };
|
||||
[0_u64, 1].map(|list| ObjectRef {
|
||||
key: ObjectKey((namespace << 60) | (list << 32) | u64::from(region.index())),
|
||||
@@ -1711,7 +1711,7 @@ fn boolean_source_lists(result: bool, region: RegionId) -> [ObjectRef; 2] {
|
||||
})
|
||||
}
|
||||
|
||||
fn result_curve_key(edge: EdgeId, type_code: i64) -> ObjectRef {
|
||||
pub(super) fn result_curve_key(edge: EdgeId, type_code: i64) -> ObjectRef {
|
||||
ObjectRef {
|
||||
key: ObjectKey((4_u64 << 60) | ((type_code as u64) << 32) | u64::from(edge.index())),
|
||||
type_code,
|
||||
@@ -1754,7 +1754,7 @@ fn copy_source_lists(region: RegionId) -> [ObjectRef; 2] {
|
||||
/// and below the kernel's reserved bit 63: bits 58-59 tell topology (1), curves (2)
|
||||
/// and source lists (3) apart, bits 46-57 hold the merge-store handle, bits 32-45 the
|
||||
/// type code.
|
||||
struct MergeBodies;
|
||||
pub(super) struct MergeBodies;
|
||||
impl MergeBodies {
|
||||
fn key(class: u64, brep: BrepId, kind: u64, index: u32) -> ObjectKey {
|
||||
ObjectKey(
|
||||
@@ -1811,10 +1811,10 @@ impl SweepIdentities for MergeBodies {
|
||||
}
|
||||
|
||||
/// The notification namespace of the source fixture's identities.
|
||||
const NAMESPACE_SOURCE: u64 = 1;
|
||||
pub(super) const NAMESPACE_SOURCE: u64 = 1;
|
||||
/// The notification namespace of the foreign fixture's identities.
|
||||
const NAMESPACE_FOREIGN: u64 = 2;
|
||||
fn register_notification_brep(
|
||||
pub(super) fn register_notification_brep(
|
||||
records: &mut solidcore::topology::topology::NotifyRecords,
|
||||
brep: &mut Brep,
|
||||
namespace: u64,
|
||||
|
||||
@@ -61,6 +61,12 @@ struct Inputs {
|
||||
/// ([`copy_summary`]): the copy's report identities are its caller keys.
|
||||
#[serde(default)]
|
||||
copy: bool,
|
||||
/// With `copy`: check the result of an operation on the copy instead (shim op 4,
|
||||
/// `copy_operation`; #1631): 1 union with a 10 mm box at `value` on each axis, 2
|
||||
/// the same with that box's copy, 3 `fillet_edges` of edge `index` with radius
|
||||
/// `value`, 4 a shell of distance `value` with face `index` open.
|
||||
#[serde(default)]
|
||||
copy_op: u8,
|
||||
#[serde(default)]
|
||||
mutation: u8,
|
||||
#[serde(default)]
|
||||
@@ -174,7 +180,11 @@ fn args(i: &Inputs) -> [f64; 5] {
|
||||
[
|
||||
f64::from(i.level),
|
||||
f64::from(i.walk),
|
||||
f64::from(i.mutation),
|
||||
f64::from(if i.copy && i.copy_op != 0 {
|
||||
i.copy_op
|
||||
} else {
|
||||
i.mutation
|
||||
}),
|
||||
i.index as f64,
|
||||
i.value,
|
||||
]
|
||||
@@ -600,7 +610,7 @@ fn copy_check(k: &mut Kernel, src: &Brep, i: &Inputs) -> Result<Output, HarnessE
|
||||
let mut state = solidcore::topology::brep::p2::ArenaNotifyState::new(&Brep::new());
|
||||
let mut env = state.env(None);
|
||||
env.key_of = ©_key;
|
||||
let mut copy =
|
||||
let copy =
|
||||
match solidcore::topology::brep::p3::api_brep_copy(k, &mut env, Some(src), &key) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
@@ -609,7 +619,108 @@ fn copy_check(k: &mut Kernel, src: &Brep, i: &Inputs) -> Result<Output, HarnessE
|
||||
))
|
||||
}
|
||||
};
|
||||
capi_output(validation::check_brep(k, &mut copy, level)).map(copy_summary)
|
||||
let mut result = match copy_operation(k, copy, i) {
|
||||
Ok(Ok(b)) => b,
|
||||
Ok(Err(e)) => {
|
||||
return Ok(copy_summary(
|
||||
Output::unported(&e).unwrap_or_else(|| Output::err(e.to_string())),
|
||||
))
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
};
|
||||
capi_output(validation::check_brep(k, &mut result, level)).map(copy_summary)
|
||||
}
|
||||
/// The operation of a copy case on the copy (`copy_operation` of the shim, through the
|
||||
/// Rust API): the copy itself without one; the operation's error inside.
|
||||
fn copy_operation(
|
||||
k: &mut Kernel,
|
||||
mut copy: Brep,
|
||||
i: &Inputs,
|
||||
) -> Result<solidcore::Result<Brep>, HarnessError> {
|
||||
use solidcore::boolean::api_brep::boolean_union;
|
||||
use solidcore::primitive::api_primitives::create_box;
|
||||
let v = i.value;
|
||||
Ok(match i.copy_op {
|
||||
0 => Ok(copy),
|
||||
1 | 2 => (|| {
|
||||
let mut other = create_box(k, Vec3::new(v, v, v), 10.0, 10.0, 10.0)?;
|
||||
if i.copy_op == 2 {
|
||||
let other_key = |e: EntityRef| {
|
||||
let r = key(e);
|
||||
ObjectRef {
|
||||
type_code: r.type_code,
|
||||
key: ObjectKey(r.key.0 | (2 << 40)),
|
||||
}
|
||||
};
|
||||
let mut state =
|
||||
solidcore::topology::brep::p2::ArenaNotifyState::new(&Brep::new());
|
||||
let mut env = state.env(None);
|
||||
env.key_of = &other_key;
|
||||
other = solidcore::topology::brep::p3::api_brep_copy(k, &mut env, Some(&other), &key)?;
|
||||
}
|
||||
boolean_union(k, copy, other)
|
||||
})(),
|
||||
3 => (|| {
|
||||
let edges = copy.edge_list().to_vec();
|
||||
let edge = *edges
|
||||
.get(i.index % edges.len().max(1))
|
||||
.ok_or_else(|| solidcore::SolidError::invalid_input("difftest", "no edge"))?;
|
||||
solidcore::blend::fillet_executive::api::fillet_edges(k, &mut copy, &[edge], v, 1, 1, 1.0)?;
|
||||
Ok(copy)
|
||||
})(),
|
||||
4 => shell_copy(k, copy, i),
|
||||
_ => return Err(herr("copy_op takes 0 to 4")),
|
||||
})
|
||||
}
|
||||
/// `SmApiShellBrepFull` of a copy case: the copy shelled by `value` (extended convex
|
||||
/// edges, merged results, no offset solid) with face `index` of its face list open.
|
||||
fn shell_copy(k: &mut Kernel, mut copy: Brep, i: &Inputs) -> solidcore::Result<Brep> {
|
||||
use super::offset_offset_executive_p2 as off;
|
||||
use solidcore::geom::attribute::AttributeStore;
|
||||
use solidcore::offset::offset_executive::p1::OffsetExecutive;
|
||||
use solidcore::offset::offset_executive::p2::{OffsetNotifications, OffsetOptions, ShellOptions};
|
||||
let faces = copy.face_list().to_vec();
|
||||
let open = *faces
|
||||
.get(i.index % faces.len().max(1))
|
||||
.ok_or_else(|| solidcore::SolidError::invalid_input("difftest", "no face"))?;
|
||||
let mut records = solidcore::topology::topology::NotifyRecords::new();
|
||||
let mut attributes = AttributeStore::default();
|
||||
off::register_notification_brep(&mut records, &mut copy, off::NAMESPACE_SOURCE);
|
||||
let result_brep =
|
||||
off::result_notification_key(EntityRef::Brep(solidcore::topology::BrepId::from_index(0)));
|
||||
records.insert(result_brep.key, solidcore::topology::topology::Topology::new());
|
||||
let mut notifications = OffsetNotifications {
|
||||
records: &mut records,
|
||||
attributes: &mut attributes,
|
||||
original_key_of: &off::original_notification_key,
|
||||
result_key_of: &off::result_notification_key,
|
||||
original_curve_ref: &off::original_curve_key,
|
||||
result_curve_ref: &off::result_curve_key,
|
||||
boolean_source_lists: &off::boolean_source_lists,
|
||||
result_brep,
|
||||
merge_identities: Some(&off::MergeBodies),
|
||||
};
|
||||
OffsetExecutive::shell_brep(
|
||||
k,
|
||||
copy,
|
||||
ShellOptions {
|
||||
offset: OffsetOptions {
|
||||
distance: i.value,
|
||||
extend_convex_edges: true,
|
||||
merge_results: true,
|
||||
},
|
||||
create_offset_solid: false,
|
||||
merge_sheet_side_walls: false,
|
||||
},
|
||||
&[open],
|
||||
&mut notifications,
|
||||
)
|
||||
.result
|
||||
.map(|mut b| {
|
||||
// as `api_shell_brep_full` does for its caller: the port's cached topology fields
|
||||
b.refresh_cached_topology();
|
||||
b
|
||||
})
|
||||
}
|
||||
const NULL_LINK_TESTS: &str = "the level-2 tests dereference the nulled link (crash in C++)";
|
||||
|
||||
@@ -625,6 +736,8 @@ impl UnitDiff for Runner {
|
||||
let (path, block) = block(&i.brep)?;
|
||||
let op = if i.tests {
|
||||
2
|
||||
} else if i.copy && i.copy_op != 0 {
|
||||
4
|
||||
} else if i.copy {
|
||||
3
|
||||
} else {
|
||||
@@ -743,6 +856,7 @@ impl UnitDiff for Runner {
|
||||
capi,
|
||||
tests: false,
|
||||
copy: false,
|
||||
copy_op: 0,
|
||||
mutation,
|
||||
index: rng.below(100) as usize,
|
||||
value,
|
||||
|
||||
@@ -33,7 +33,9 @@ fn primitive_len(kind: f64) -> Option<usize> {
|
||||
/// `AssertValid` (op 0), the capi sequence (op 1) or the level-2 tests one by one (op
|
||||
/// 2: `sm_AssertTestFace32/33/38` per face, `sm_AssertTestEdge13` per edge,
|
||||
/// `sm_AssertTestVertex1` per vertex) or the capi sequence on the body's
|
||||
/// `SmApiBrepCopy` (op 3, without mutation) of the B-rep built from `block` (a
|
||||
/// `SmApiBrepCopy` (op 3, without mutation) or on the result of an operation on that
|
||||
/// copy (op 4, `args[2]` its kind 1-4: union with a box, union with a box's copy,
|
||||
/// `SmApiFilletEdges`, `SmApiShellBrepFull`) of the B-rep built from `block` (a
|
||||
/// `sc_edgeuse_graph.h` source or a primitive), after mutation `args[2]` (kinds 1-10 of
|
||||
/// `topology_brep_validation`). Arguments: level (0-4), walk, mutation kind, index,
|
||||
/// value. Op 2 refuses the null-pointer mutations 8-10: the tests dereference the face
|
||||
@@ -41,9 +43,11 @@ fn primitive_len(kind: f64) -> Option<usize> {
|
||||
/// `AssertValid` runs first.
|
||||
pub fn eval(op: u8, path: &str, block: &[f64], args: [f64; 5]) -> Result<Vec<f64>, FfiError> {
|
||||
let bad = || FfiError::InvalidArgument("invalid AssertValid arguments".into());
|
||||
if op > 3
|
||||
if op > 4
|
||||
|| (op == 2 && args[2] >= 8.0)
|
||||
|| (op == 3 && args[2] != 0.0)
|
||||
|| (op == 4 && !(1.0..=4.0).contains(&args[2]))
|
||||
|| (op == 4 && args[2] >= 3.0 && args[4] == 0.0)
|
||||
|| !args.iter().all(|x| x.is_finite())
|
||||
|| args[0].fract() != 0.0
|
||||
|| !(0.0..=4.0).contains(&args[0])
|
||||
@@ -56,7 +60,7 @@ pub fn eval(op: u8, path: &str, block: &[f64], args: [f64; 5]) -> Result<Vec<f64
|
||||
{
|
||||
return Err(bad());
|
||||
}
|
||||
if (args[2] == 4.0 || args[2] == 5.0)
|
||||
if op != 4 && (args[2] == 4.0 || args[2] == 5.0)
|
||||
&& (!(0.0..=2.0).contains(&args[4]) || args[4].fract() != 0.0)
|
||||
{
|
||||
return Err(bad());
|
||||
|
||||
@@ -566,6 +566,9 @@ impl FilletSession<'_> {
|
||||
// DoPiecewiseMerge deletes m_pFilletBrep inside its mark scope
|
||||
return self.do_piecewise_merge(k);
|
||||
}
|
||||
// the keys a caller-named target recorded before the merge name its entities
|
||||
// afterwards (`Brep::owner_identity`)
|
||||
let recorded = self.objects.target.geometry_owner_keys().clone();
|
||||
let result = self.merge_fillet_in_place(k);
|
||||
// (not upstream: owners are pointers there) the insertion names the geometry
|
||||
// it binds to the target's edges and faces by the session's kernel identity
|
||||
@@ -578,7 +581,8 @@ impl FilletSession<'_> {
|
||||
let session_owner = |e| p3::topology_ref(serial, e);
|
||||
let restored =
|
||||
super::piecewise::normalize_session_owners(self.objects.target, session_owner, |e| {
|
||||
super::piecewise::naming_owner(naming, e).unwrap_or_else(|| session_owner(e))
|
||||
super::piecewise::naming_owner(naming, recorded.get(&e).copied(), e)
|
||||
.unwrap_or_else(|| session_owner(e))
|
||||
});
|
||||
result.and(restored)
|
||||
}
|
||||
|
||||
+5
-3
@@ -97,7 +97,7 @@ impl InsertionGeometry<'_> {
|
||||
for (eu, record) in body.edgeuses().iter() {
|
||||
let canonical = topology_ref(serial, EntityRef::Edgeuse(eu)) == link;
|
||||
let named = *scope == Some(role)
|
||||
&& crate::topology::edgeuse::p1::owner_ref_in(body, eu) == Some(link);
|
||||
&& crate::topology::edgeuse::p1::owner_ref_in(body, eu) == link;
|
||||
if (canonical || named)
|
||||
&& !take(
|
||||
Some(role),
|
||||
@@ -339,8 +339,10 @@ mod tests {
|
||||
// A UV trim curve named by its B-rep's naming scheme (`SetUVTrimCurve`): the
|
||||
// intermediate box has an edge use of the same index and notify identity, so
|
||||
// the name resolves only in the walked curve's own body.
|
||||
let named = crate::topology::edgeuse::p1::owner_ref(NamingScheme::Notify, eu);
|
||||
assert!(named.is_some());
|
||||
let named = Some(crate::topology::scheme_owner_identity(
|
||||
NamingScheme::Notify,
|
||||
EntityRef::Edgeuse(eu),
|
||||
));
|
||||
assert_ne!(named, Some(eu_ref));
|
||||
view.target.edgeuse_mut(eu).unwrap().uv_trim_curve = Some(line_owned_by(named));
|
||||
assert_eq!(
|
||||
|
||||
@@ -56,8 +56,6 @@ use crate::sweep::topology_sweep::access::SweepAccess;
|
||||
use crate::sweep::topology_sweep::store::{SweepIdentities, SweepStore};
|
||||
use crate::topology::brep::p3::{seed_records, topology_ref};
|
||||
use crate::topology::brep::{p1, p2, p5};
|
||||
use crate::topology::brep_cache::{arena_identity, CacheObject};
|
||||
use crate::topology::loopuse::notify_ref;
|
||||
use crate::topology::{
|
||||
edgeuse, face, vertex, Brep, BrepId, EdgeId, FaceId, NamingScheme, RegionId,
|
||||
};
|
||||
@@ -241,14 +239,16 @@ pub(super) fn normalize_session_owners(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The identity a B-rep of naming `naming` owns an edge's or face's geometry under,
|
||||
/// `None` for a caller's or an unknown scheme.
|
||||
pub(super) fn naming_owner(naming: NamingScheme, e: EntityRef) -> Option<ObjectRef> {
|
||||
match (naming, e) {
|
||||
(NamingScheme::Arena, EntityRef::Edge(x)) => Some(arena_identity(CacheObject::Edge(x))),
|
||||
(NamingScheme::Arena, EntityRef::Face(x)) => Some(arena_identity(CacheObject::Face(x))),
|
||||
(NamingScheme::Notify, e) => Some(notify_ref(e)),
|
||||
_ => None,
|
||||
/// The identity a B-rep of naming `naming` that records `recorded` for `e` owns `e`'s
|
||||
/// geometry under ([`Brep::owner_identity`]), `None` for an unknown scheme.
|
||||
pub(super) fn naming_owner(
|
||||
naming: NamingScheme,
|
||||
recorded: Option<ObjectRef>,
|
||||
e: EntityRef,
|
||||
) -> Option<ObjectRef> {
|
||||
match naming {
|
||||
NamingScheme::Unknown => None,
|
||||
naming => Some(crate::topology::owner_identity_of(naming, recorded, e)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ fn surface_of(brep: &Brep, face: FaceId) -> Result<Option<ObjectRef>> {
|
||||
/// identity with an equal raw key names a different owner and is not accepted.
|
||||
fn own_identities(b: &Brep, e: EntityRef) -> Vec<ObjectRef> {
|
||||
let mut ids = Vec::new();
|
||||
ids.extend(naming_owner(b.naming(), e));
|
||||
ids.extend(naming_owner(b.naming(), b.geometry_owner_key(e), e));
|
||||
ids.extend(b.geometry_owner_key(e));
|
||||
ids
|
||||
}
|
||||
@@ -539,6 +539,9 @@ impl FilletSession<'_> {
|
||||
// the target's session identities, which return to its naming afterwards.
|
||||
let target_serial = self.objects.target_serial;
|
||||
let naming = target.naming();
|
||||
// The keys a caller-named target recorded before the merge, its entities'
|
||||
// names afterwards (a merge-made entity has none and takes the scheme's).
|
||||
let recorded = target.geometry_owner_keys().clone();
|
||||
let session_owner = |e| topology_ref(target_serial, e);
|
||||
let issued = issue_actual_curve_identities(&mut k.context, &mut target)
|
||||
.and_then(|()| issue_actual_curve_identities(&mut k.context, &mut intermediate));
|
||||
@@ -594,7 +597,7 @@ impl FilletSession<'_> {
|
||||
drop(store);
|
||||
let mut target = target.ok_or_else(|| invalid("target lost in the merge"))?;
|
||||
let restored = normalize_session_owners(&mut target, session_owner, |e| {
|
||||
naming_owner(naming, e).unwrap_or_else(|| session_owner(e))
|
||||
naming_owner(naming, recorded.get(&e).copied(), e).unwrap_or_else(|| session_owner(e))
|
||||
});
|
||||
target.set_naming(naming);
|
||||
*self.objects.target = target;
|
||||
|
||||
@@ -131,12 +131,12 @@ pub(crate) fn entity_of_notification_key(
|
||||
/// own naming: a face's surface (an edge's curve) owned under that face's (edge's)
|
||||
/// [`notification_key`] in `namespace` is the record's own geometry (upstream's owner
|
||||
/// is the record), so it takes the record's name in `b`'s recorded scheme
|
||||
/// ([`Brep::naming`](crate::topology::Brep::naming): notification or arena identity)
|
||||
/// and the face's tolerance is re-recorded on it. An edge use's UV trim curve owned
|
||||
/// under the edge use's key takes the edge use's notification identity (upstream's owner
|
||||
/// is the edge use; `SmEdgeuse::AssertValid` test #6). Called when the namespace ends with
|
||||
/// the merge (its records and attributes go with it); a B-rep of another or unknown
|
||||
/// scheme is left as it is.
|
||||
/// ([`Brep::owner_identity`]: notification or arena identity, or a caller's recorded
|
||||
/// key) and the face's tolerance is re-recorded on it. An edge use's UV trim curve
|
||||
/// owned under the edge use's key likewise takes the edge use's name (upstream's owner
|
||||
/// is the edge use; `SmEdgeuse::AssertValid` test #6). Called when the namespace ends
|
||||
/// with the merge (its records and attributes go with it); a B-rep of unknown scheme
|
||||
/// is left as it is.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
@@ -145,13 +145,8 @@ pub(crate) fn adopt_namespace_owners(b: &mut Brep, namespace: u32, other: bool)
|
||||
use crate::topology::NamingScheme;
|
||||
let name = |e: EntityRef| -> Option<ObjectRef> {
|
||||
match b.naming() {
|
||||
NamingScheme::Notify => Some(crate::topology::loopuse::notify_ref(e)),
|
||||
NamingScheme::Arena => Some(match e {
|
||||
EntityRef::Face(f) => arena_identity(CacheObject::Face(f)),
|
||||
EntityRef::Edge(x) => arena_identity(CacheObject::Edge(x)),
|
||||
_ => return None,
|
||||
}),
|
||||
_ => None,
|
||||
NamingScheme::Unknown => None,
|
||||
_ => Some(b.owner_identity(e)),
|
||||
}
|
||||
};
|
||||
let faces: Vec<(FaceId, ObjectRef)> = bp::faces(b, None)?
|
||||
@@ -191,16 +186,16 @@ pub(crate) fn adopt_namespace_owners(b: &mut Brep, namespace: u32, other: bool)
|
||||
}
|
||||
}
|
||||
// An edge use's UV trim curve is owned by the edge use (`SmEdgeuse` test #6); one
|
||||
// the merge set under the namespace takes the edge use's own name. Both schemes
|
||||
// name edge uses by their notification identity (the check's keys for an arena
|
||||
// B-rep fall back to it too).
|
||||
if matches!(b.naming(), NamingScheme::Notify | NamingScheme::Arena) {
|
||||
// the merge set under the namespace takes the edge use's own name
|
||||
// ([`Brep::owner_identity`]: its notification identity under the notification and
|
||||
// arena schemes, its recorded key or else that identity under a caller's).
|
||||
if b.naming() != NamingScheme::Unknown {
|
||||
let uses: Vec<_> = b.edgeuses().iter().map(|(id, _)| id).collect();
|
||||
for eu in uses {
|
||||
let merged = notification_key(namespace, other, EntityRef::Edgeuse(eu));
|
||||
let own = b.owner_identity(EntityRef::Edgeuse(eu));
|
||||
if let Some(trim) = b.edgeuse_mut(eu)?.uv_trim_curve.as_mut() {
|
||||
if trim.owner() == Some(merged) {
|
||||
let own = crate::topology::loopuse::notify_ref(EntityRef::Edgeuse(eu));
|
||||
trim.set_owner(Some(own), None);
|
||||
}
|
||||
}
|
||||
@@ -221,7 +216,6 @@ struct GeometryOwnerScope<'a, 'keys> {
|
||||
}
|
||||
impl<'a, 'keys> GeometryOwnerScope<'a, 'keys> {
|
||||
fn new(body: &'a mut Brep, keys: NotificationKeys<'keys>, other: bool) -> Result<Self> {
|
||||
use crate::topology::NamingScheme;
|
||||
let naming = body.naming();
|
||||
let mut scope = Self {
|
||||
body,
|
||||
@@ -232,11 +226,8 @@ impl<'a, 'keys> GeometryOwnerScope<'a, 'keys> {
|
||||
};
|
||||
if matches!(keys, NotificationKeys::Namespace(_)) {
|
||||
for edge in crate::topology::brep::p1::edges(scope.body, None)? {
|
||||
let expected = if naming == NamingScheme::Notify {
|
||||
crate::topology::loopuse::notify_ref(EntityRef::Edge(edge))
|
||||
} else {
|
||||
arena_identity(CacheObject::Edge(edge))
|
||||
};
|
||||
let expected =
|
||||
crate::topology::scheme_owner_identity(naming, EntityRef::Edge(edge));
|
||||
if let Some(curve) = scope.body.edge_mut(edge)?.curve.as_mut() {
|
||||
if curve.owner() == Some(expected) {
|
||||
scope.edges.push((edge, expected));
|
||||
@@ -245,11 +236,8 @@ impl<'a, 'keys> GeometryOwnerScope<'a, 'keys> {
|
||||
}
|
||||
}
|
||||
for face in crate::topology::brep::p1::faces(scope.body, None)? {
|
||||
let expected = if naming == NamingScheme::Notify {
|
||||
crate::topology::loopuse::notify_ref(EntityRef::Face(face))
|
||||
} else {
|
||||
arena_identity(CacheObject::Face(face))
|
||||
};
|
||||
let expected =
|
||||
crate::topology::scheme_owner_identity(naming, EntityRef::Face(face));
|
||||
let rebound = scope
|
||||
.body
|
||||
.face_mut(face)?
|
||||
|
||||
@@ -700,6 +700,43 @@ pub enum NamingScheme {
|
||||
Arena,
|
||||
}
|
||||
|
||||
/// The identity `entity` owns geometry under in a B-rep of naming `naming` that
|
||||
/// records `recorded` for it: [`Brep::owner_identity`] without the B-rep.
|
||||
#[must_use]
|
||||
pub(crate) fn owner_identity_of(
|
||||
naming: NamingScheme,
|
||||
recorded: Option<crate::geom::object::ObjectRef>,
|
||||
entity: EntityRef,
|
||||
) -> crate::geom::object::ObjectRef {
|
||||
match (naming, recorded) {
|
||||
(NamingScheme::Caller, Some(key)) => key,
|
||||
_ => scheme_owner_identity(naming, entity),
|
||||
}
|
||||
}
|
||||
|
||||
/// The identity a B-rep of naming `naming` names `entity` by when it records none:
|
||||
/// the Euler operators' notification identity ([`loopuse::notify_ref`]) under
|
||||
/// [`NamingScheme::Notify`], else the arena identity of the crate's imported B-reps
|
||||
/// ([`brep_cache::arena_identity`]) for edges, faces, vertices and the B-rep, and the
|
||||
/// notification identity for the other entities (edge uses among them).
|
||||
#[must_use]
|
||||
pub(crate) fn scheme_owner_identity(
|
||||
naming: NamingScheme,
|
||||
entity: EntityRef,
|
||||
) -> crate::geom::object::ObjectRef {
|
||||
use brep_cache::{arena_identity, CacheObject};
|
||||
if naming == NamingScheme::Notify {
|
||||
return loopuse::notify_ref(entity);
|
||||
}
|
||||
match entity {
|
||||
EntityRef::Edge(x) => arena_identity(CacheObject::Edge(x)),
|
||||
EntityRef::Face(x) => arena_identity(CacheObject::Face(x)),
|
||||
EntityRef::Vertex(x) => arena_identity(CacheObject::Vertex(x)),
|
||||
EntityRef::Brep(_) => arena_identity(CacheObject::Brep),
|
||||
other => loopuse::notify_ref(other),
|
||||
}
|
||||
}
|
||||
|
||||
/// A boundary representation (`SmBrep`): arenas of shells, faces, loops, edges and
|
||||
/// vertices linked by typed ids.
|
||||
#[derive(Debug)]
|
||||
@@ -1015,6 +1052,20 @@ impl Brep {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The identity `entity` owns geometry under (upstream: the entity itself,
|
||||
/// `pGeom->GetOwner() == this`): under [`NamingScheme::Caller`] the key recorded
|
||||
/// for it where it was built or its geometry bound, else, and under every other
|
||||
/// scheme, the scheme's own identity (the notification identity under
|
||||
/// [`NamingScheme::Notify`], otherwise the arena identity of an edge, face or
|
||||
/// vertex and the notification identity of any other entity). Geometry binders name new
|
||||
/// geometry by it and [`check_brep`](crate::topology::brep::validation::check_brep)
|
||||
/// checks owners against it, so the two always agree (upstream's identity exists
|
||||
/// from the moment the entity does).
|
||||
#[must_use]
|
||||
pub fn owner_identity(&self, entity: EntityRef) -> crate::geom::object::ObjectRef {
|
||||
owner_identity_of(self.naming, self.geometry_owner_key(entity), entity)
|
||||
}
|
||||
|
||||
/// Producer-supplied owner identity recorded when geometry was bound to `entity`.
|
||||
#[must_use]
|
||||
pub(crate) fn geometry_owner_key(
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
//! reported by [`regions`].
|
||||
//! - [`surfaces`] / [`curves`]: upstream's `GetOwner() == pFace` / `pEdge` test compares
|
||||
//! the geometry's owner with the face's / edge's
|
||||
//! [`arena_identity`] (the kernel's
|
||||
//! [`arena_identity`](crate::topology::brep_cache::arena_identity) (the kernel's
|
||||
//! identity of arena entities; a B-rep whose geometry carries no owner reports no
|
||||
//! surfaces or curves, as upstream for unowned geometry); the attribute filter
|
||||
//! ([`GeometryAttributeFilter`]) looks the geometry up under its own keys.
|
||||
@@ -141,7 +141,7 @@ use crate::query::brep::{self as qbrep, BoundingBoxOptions};
|
||||
use crate::surface::surface::p2::{BlendEndType, CurvatureType, DerivSurfDefinition};
|
||||
use crate::surface::surface::p4::NoObject;
|
||||
use crate::surface::surface::Surface;
|
||||
use crate::topology::brep_cache::{arena_identity, CacheObject};
|
||||
use crate::topology::brep_cache::CacheObject;
|
||||
use crate::topology::edge::{self, AttributeFilter};
|
||||
use crate::topology::edgeuse::p1 as eu1;
|
||||
use crate::topology::edgeuse::p2::BinormalEval;
|
||||
@@ -155,8 +155,7 @@ use crate::topology::topology_traverser::{Entity, Marks};
|
||||
use crate::topology::vertex;
|
||||
use crate::topology::vertexuse;
|
||||
use crate::topology::{
|
||||
Brep, EdgeId, EdgeuseId, FaceId, LoopId, NamingScheme, RegionId, ShellId, VertexId, Vertexuse,
|
||||
VertexuseId,
|
||||
Brep, EdgeId, EdgeuseId, FaceId, LoopId, RegionId, ShellId, VertexId, Vertexuse, VertexuseId,
|
||||
};
|
||||
|
||||
/// `SmBoolean` `TRUE` / `FALSE`.
|
||||
@@ -671,42 +670,36 @@ impl core::fmt::Debug for GeometryAttributeFilter<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The default expected owner of `f`'s surface under `b`'s recorded naming scheme,
|
||||
/// falling back to a proven caller owner recorded on `b`.
|
||||
/// The default expected owner of `f`'s surface: its identity in `b`'s recorded naming
|
||||
/// scheme ([`Brep::owner_identity`]), falling back to a proven owner recorded on `b`.
|
||||
pub(crate) fn default_face_owner(b: &Brep, f: FaceId) -> ObjectRef {
|
||||
let fallback = if b.naming() == NamingScheme::Notify {
|
||||
loopuse::notify_ref(EntityRef::Face(f))
|
||||
} else {
|
||||
arena_identity(CacheObject::Face(f))
|
||||
};
|
||||
let entity = EntityRef::Face(f);
|
||||
let expected = b.owner_identity(entity);
|
||||
let actual = b.face(f).ok().and_then(|x| x.surface.as_ref()?.owner());
|
||||
if actual != Some(fallback) {
|
||||
if let Some(proven) = b.geometry_owner_key(EntityRef::Face(f)) {
|
||||
if actual != Some(expected) {
|
||||
if let Some(proven) = b.geometry_owner_key(entity) {
|
||||
if actual == Some(proven) {
|
||||
return proven;
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback
|
||||
expected
|
||||
}
|
||||
|
||||
/// The default expected owner of `e`'s curve under `b`'s recorded naming scheme,
|
||||
/// falling back to a proven caller owner recorded on `b`.
|
||||
/// The default expected owner of `e`'s curve: its identity in `b`'s recorded naming
|
||||
/// scheme ([`Brep::owner_identity`]), falling back to a proven owner recorded on `b`.
|
||||
pub(crate) fn default_edge_owner(b: &Brep, e: EdgeId) -> ObjectRef {
|
||||
let fallback = if b.naming() == NamingScheme::Notify {
|
||||
loopuse::notify_ref(EntityRef::Edge(e))
|
||||
} else {
|
||||
arena_identity(CacheObject::Edge(e))
|
||||
};
|
||||
let entity = EntityRef::Edge(e);
|
||||
let expected = b.owner_identity(entity);
|
||||
let actual = b.edge(e).ok().and_then(|x| x.curve.as_ref()?.owner());
|
||||
if actual != Some(fallback) {
|
||||
if let Some(proven) = b.geometry_owner_key(EntityRef::Edge(e)) {
|
||||
if actual != Some(expected) {
|
||||
if let Some(proven) = b.geometry_owner_key(entity) {
|
||||
if actual == Some(proven) {
|
||||
return proven;
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback
|
||||
expected
|
||||
}
|
||||
|
||||
/// `GetSurfaces`: the surfaces owned by their faces (`pSurface->GetOwner() == pFace`,
|
||||
|
||||
@@ -2472,19 +2472,27 @@ fn find_and_set_infinite_region(
|
||||
/// Completes the native `SetUVTrimCurve` owner binding (`pUVTrimCurve->SetOwner(eu)`)
|
||||
/// under the caller's names `key_of` for the UV trim curves the B-rep's own routines
|
||||
/// left without owner (a B-rep under [`crate::topology::NamingScheme::Caller`]: lazy
|
||||
/// face queries, edge splits). Existing owners are kept so a foreign or incorrect
|
||||
/// owner remains detectable. An edge use the B-rep's routines added (edge splits) gets
|
||||
/// face queries, edge splits), or named by the default identity of an edge use without
|
||||
/// a recorded key (`Brep::owner_identity`, the same owner). Other existing owners are
|
||||
/// kept so a foreign or incorrect owner remains detectable. An edge use the B-rep's routines added (edge splits) gets
|
||||
/// its caller key recorded (`Brep::geometry_owner_key`) so trims made later are named
|
||||
/// alike.
|
||||
fn bind_uv_trim_owners(b: &mut Brep, key_of: &dyn Fn(EntityRef) -> ObjectRef) -> Result<()> {
|
||||
for edge in p1::edges(b, None)? {
|
||||
for id in b.edge(edge)?.edgeuses.clone() {
|
||||
let entity = EntityRef::Edgeuse(id);
|
||||
if b.geometry_owner_key(entity).is_none() {
|
||||
// a trim bound before the key was recorded is named by the edge use's
|
||||
// default identity (`Brep::owner_identity`); it is the same owner
|
||||
let unrecorded = b
|
||||
.geometry_owner_key(entity)
|
||||
.is_none()
|
||||
.then(|| b.owner_identity(entity));
|
||||
if unrecorded.is_some() {
|
||||
b.set_geometry_owner_key(entity, Some(key_of(entity)));
|
||||
}
|
||||
if let Some(curve) = b.edgeuse_mut(id)?.uv_trim_curve.as_mut() {
|
||||
if curve.owner().is_none() {
|
||||
if curve.owner().is_none() || (unrecorded.is_some() && curve.owner() == unrecorded)
|
||||
{
|
||||
curve.set_owner(Some(key_of(EntityRef::Edgeuse(id))), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1632,7 +1632,11 @@ pub fn make_edge_in_face_with_vu_with_curve_identity(
|
||||
} else {
|
||||
eu2
|
||||
};
|
||||
trim.set_owner(Some((env.key_of)(EntityRef::Edgeuse(top))), None);
|
||||
// SetOwner(pEu): named by the caller's key, recorded as for the edge's curve
|
||||
// (`set_curve_owner`) so the B-rep's checks name the edge use alike
|
||||
let top_owner = (env.key_of)(EntityRef::Edgeuse(top));
|
||||
trim.set_owner(Some(top_owner), None);
|
||||
b.set_geometry_owner_key(EntityRef::Edgeuse(top), Some(top_owner));
|
||||
b.edgeuse_mut(top)?.uv_trim_curve = Some(trim);
|
||||
b.edgeuse_mut(top)?.drop_curve_fail = drop_fail;
|
||||
let mut copy =
|
||||
@@ -1661,7 +1665,9 @@ pub fn make_edge_in_face_with_vu_with_curve_identity(
|
||||
} else {
|
||||
eu3
|
||||
};
|
||||
copy.set_owner(Some((env.key_of)(EntityRef::Edgeuse(bottom))), None);
|
||||
let bottom_owner = (env.key_of)(EntityRef::Edgeuse(bottom));
|
||||
copy.set_owner(Some(bottom_owner), None);
|
||||
b.set_geometry_owner_key(EntityRef::Edgeuse(bottom), Some(bottom_owner));
|
||||
b.edgeuse_mut(bottom)?.uv_trim_curve = Some(copy);
|
||||
b.edgeuse_mut(bottom)?.drop_curve_fail = fail_copy;
|
||||
original_trim_owner = Some(top);
|
||||
|
||||
@@ -48,7 +48,6 @@ mod shell;
|
||||
mod vertex;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::{object_info, validate_pointers, Object, SUCCESS};
|
||||
use crate::error::{EntityRef, Result};
|
||||
@@ -58,7 +57,7 @@ use crate::geom::assert_array::{
|
||||
use crate::geom::object::ObjectRef;
|
||||
use crate::kernel::Kernel;
|
||||
use crate::math::UNDEF_DOUBLE;
|
||||
use crate::topology::{Brep, BrepId, NamingScheme};
|
||||
use crate::topology::{Brep, BrepId};
|
||||
|
||||
/// `SM_LEVEL_GIVEN`: every test, no base classes.
|
||||
const LEVEL_GIVEN: u8 = 4;
|
||||
@@ -174,63 +173,24 @@ fn report_line(r: &AssertReport) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// The arena identities of the crate's imported B-reps
|
||||
/// ([`crate::topology::brep_cache::arena_identity`]).
|
||||
fn arena(e: EntityRef) -> ObjectRef {
|
||||
use crate::topology::brep_cache::{arena_identity, CacheObject};
|
||||
match e {
|
||||
EntityRef::Edge(x) => arena_identity(CacheObject::Edge(x)),
|
||||
EntityRef::Face(x) => arena_identity(CacheObject::Face(x)),
|
||||
EntityRef::Vertex(x) => arena_identity(CacheObject::Vertex(x)),
|
||||
EntityRef::Brep(_) => arena_identity(CacheObject::Brep),
|
||||
other => crate::topology::loopuse::notify_ref(other),
|
||||
}
|
||||
}
|
||||
|
||||
/// The caller keys a [`NamingScheme::Caller`] B-rep recorded for every edge, face and
|
||||
/// edge use (`Brep::geometry_owner_keys`, the only entities the check names as
|
||||
/// geometry owners); `None` when one of them has none.
|
||||
fn recorded_caller_keys(b: &Brep) -> Option<BTreeMap<EntityRef, ObjectRef>> {
|
||||
let entities = b
|
||||
.edges()
|
||||
.ids()
|
||||
.map(EntityRef::Edge)
|
||||
.chain(b.faces().ids().map(EntityRef::Face))
|
||||
.chain(b.edgeuses().ids().map(EntityRef::Edgeuse));
|
||||
let mut keys = BTreeMap::new();
|
||||
for e in entities {
|
||||
keys.insert(e, b.geometry_owner_key(e)?);
|
||||
}
|
||||
Some(keys)
|
||||
}
|
||||
|
||||
/// [`check_brep_with_keys`] with the geometry-owner identities of `b`'s naming scheme:
|
||||
/// the Euler operators' notification identities for a [`NamingScheme::Notify`]
|
||||
/// B-rep, the caller keys recorded where a [`NamingScheme::Caller`] B-rep was built
|
||||
/// (an imported body, [`crate::topology::brep::p3::api_brep_copy`]), else the arena
|
||||
/// identities ([`crate::topology::brep_cache::arena_identity`]).
|
||||
/// [`check_brep_with_keys`] with the geometry-owner identities of `b`'s naming scheme
|
||||
/// ([`Brep::owner_identity`]): the Euler operators' notification identities for a
|
||||
/// [`NamingScheme::Notify`](crate::topology::NamingScheme::Notify) B-rep; for a
|
||||
/// [`NamingScheme::Caller`](crate::topology::NamingScheme::Caller) B-rep the caller
|
||||
/// key recorded where the entity was built or its geometry bound (an imported body,
|
||||
/// [`crate::topology::brep::p3::api_brep_copy`], an operation on either), else the
|
||||
/// arena identities ([`crate::topology::brep_cache::arena_identity`]), which the
|
||||
/// binders name an entity with no recorded key by too.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// [`crate::SolidError::InvalidInput`] for a [`NamingScheme::Caller`] B-rep without a
|
||||
/// recorded key for each edge, face and edge use (pass the maker's keys to
|
||||
/// [`check_brep_with_keys`]); those of [`check_brep_with_keys`].
|
||||
/// Those of [`check_brep_with_keys`].
|
||||
pub fn check_brep(k: &mut Kernel, b: &mut Brep, level: CheckLevel) -> Result<BrepCheck> {
|
||||
match b.naming() {
|
||||
NamingScheme::Notify => {
|
||||
check_brep_with_keys(k, b, level, &crate::topology::loopuse::notify_ref)
|
||||
}
|
||||
NamingScheme::Caller => {
|
||||
let recorded = recorded_caller_keys(b).ok_or(crate::SolidError::invalid_input(
|
||||
"topology::brep::validation::check_brep",
|
||||
"body names geometry owners by its maker's keys and does not record them \
|
||||
all; use check_brep_with_keys",
|
||||
))?;
|
||||
let keys = |e: EntityRef| recorded.get(&e).copied().unwrap_or_else(|| arena(e));
|
||||
check_brep_with_keys(k, b, level, &keys)
|
||||
}
|
||||
_ => check_brep_with_keys(k, b, level, &arena),
|
||||
}
|
||||
let naming = b.naming();
|
||||
let recorded = b.geometry_owner_keys().clone();
|
||||
let keys =
|
||||
|e: EntityRef| crate::topology::owner_identity_of(naming, recorded.get(&e).copied(), e);
|
||||
check_brep_with_keys(k, b, level, &keys)
|
||||
}
|
||||
|
||||
/// The capi `smlib_brep_check(brep, level)`: [`validate_pointers`] at `level` (its
|
||||
|
||||
@@ -113,7 +113,7 @@ use crate::surface::surface::p5::DropPointOptions;
|
||||
use crate::surface::surface::Surface;
|
||||
use crate::topology::{
|
||||
topology as topo, Brep, EdgeId, EdgeuseId, EdgeuseOwner, FaceId, FaceuseId, LoopId, LoopuseId,
|
||||
NamingScheme, ObjectHandle, RegionId, ShellId, VertexId, VertexuseId, VertexuseOwner,
|
||||
ObjectHandle, RegionId, ShellId, VertexId, VertexuseId, VertexuseOwner,
|
||||
};
|
||||
|
||||
const OP: &str = "topology::edgeuse::p1";
|
||||
@@ -800,9 +800,7 @@ pub fn set_uv_trim_curve(
|
||||
};
|
||||
// pUVTrimCurve->SetOwner(holder), under the B-rep's naming (a UV trim curve
|
||||
// notifies nothing)
|
||||
if let Some(owner) = owner_ref_in(b, holder) {
|
||||
let _ = c.set_owner(Some(owner), None);
|
||||
}
|
||||
let _ = c.set_owner(Some(owner_ref_in(b, holder)), None);
|
||||
let r = b.edgeuse_mut(holder)?;
|
||||
r.uv_trim_curve = Some(c);
|
||||
r.drop_curve_fail = copy;
|
||||
@@ -829,34 +827,16 @@ pub fn set_uv_trim_curve(
|
||||
Ok(detached)
|
||||
}
|
||||
|
||||
/// The identity edge use `eu` of `b` owns geometry under: for a
|
||||
/// [`NamingScheme::Caller`] B-rep the caller's key recorded for it where the B-rep was
|
||||
/// built (`Brep::geometry_owner_key`; `None` when none is recorded), else
|
||||
/// [`owner_ref`] of `b`'s naming.
|
||||
pub(crate) fn owner_ref_in(b: &Brep, eu: EdgeuseId) -> Option<ObjectRef> {
|
||||
match b.naming() {
|
||||
NamingScheme::Caller => b.geometry_owner_key(EntityRef::Edgeuse(eu)),
|
||||
naming => owner_ref(naming, eu),
|
||||
}
|
||||
}
|
||||
|
||||
/// The identity edge use `eu` owns geometry under in a B-rep with naming `naming`
|
||||
/// ([`NamingScheme`]); `None` when the B-rep names its owners by a caller's keys (see
|
||||
/// [`owner_ref_in`]). Both the notify and the arena scheme name an edge use by its
|
||||
/// notification identity: [`Brep::use_arena_naming`] rebinds edge uses' UV trim curves
|
||||
/// to it, and the arena keys of `check_brep` and the merge's `adopt_namespace_owners`
|
||||
/// fall back to it for edge uses. A B-rep that records no scheme names its edge uses as the
|
||||
/// arena scheme does: upstream `SetUVTrimCurve` always owns the new curve by its holder
|
||||
/// (`pUVTrimCurve->SetOwner(eu)`), so leaving it without owner would differ from
|
||||
/// upstream for every B-rep made by `Brep::new` / `Brep::new_with_infinite_region`
|
||||
/// (`plan/findings/topology.edgeuse_p1--uv-trim-owner-under-unknown-naming.md`).
|
||||
pub(crate) fn owner_ref(naming: NamingScheme, eu: EdgeuseId) -> Option<ObjectRef> {
|
||||
match naming {
|
||||
NamingScheme::Caller => None,
|
||||
NamingScheme::Unknown | NamingScheme::Notify | NamingScheme::Arena => {
|
||||
Some(crate::topology::loopuse::notify_ref(EntityRef::Edgeuse(eu)))
|
||||
}
|
||||
}
|
||||
/// The identity edge use `eu` of `b` owns geometry under ([`Brep::owner_identity`]:
|
||||
/// for a [`NamingScheme::Caller`](crate::topology::NamingScheme::Caller) B-rep the key recorded for it, else its notification
|
||||
/// identity, the identity `check_brep` checks the owner against), a B-rep that
|
||||
/// records no scheme included: upstream `SetUVTrimCurve` always owns the
|
||||
/// new curve by its holder (`pUVTrimCurve->SetOwner(eu)`), so leaving it without owner
|
||||
/// would differ from upstream for every B-rep made by `Brep::new`
|
||||
/// (`plan/findings/topology.edgeuse_p1--uv-trim-owner-under-unknown-naming.md`); the
|
||||
/// arena scheme names edge uses alike (`Brep::use_arena_naming` rebinds their trims).
|
||||
pub(crate) fn owner_ref_in(b: &Brep, eu: EdgeuseId) -> ObjectRef {
|
||||
b.owner_identity(EntityRef::Edgeuse(eu))
|
||||
}
|
||||
|
||||
/// `DeleteUVTrimCurve`: `SetUVTrimCurve(NULL, 0.0, TRUE, NULL)`.
|
||||
@@ -893,9 +873,7 @@ pub fn set_member_uv_trim_curve(
|
||||
};
|
||||
// pUVTrimCurve->SetOwner(pOwner), under the B-rep's naming (a UV trim curve notifies
|
||||
// nothing), as `set_uv_trim_curve`
|
||||
if let Some(key) = owner_ref_in(b, owner) {
|
||||
let _ = curve.set_owner(Some(key), None);
|
||||
}
|
||||
let _ = curve.set_owner(Some(owner_ref_in(b, owner)), None);
|
||||
let r = b.edgeuse_mut(owner)?;
|
||||
let old_curve = r.uv_trim_curve.replace(curve);
|
||||
let old_fail = match drop_curve_fail {
|
||||
@@ -2015,14 +1993,14 @@ mod tests {
|
||||
|
||||
// the SAME member owns the curve it is given, whichever use of the pair placed
|
||||
// it (SmEdgeuse::SetMemberUVTrimCurve's pUVTrimCurve->SetOwner(pOwner))
|
||||
b.set_naming(NamingScheme::Notify);
|
||||
b.set_naming(crate::topology::NamingScheme::Notify);
|
||||
let mut stale = uv_line([0.0, 1.0], [1.0, 1.0]);
|
||||
let _ = stale.set_owner(owner_ref_in(&b, b0), None);
|
||||
let _ = stale.set_owner(Some(owner_ref_in(&b, b0)), None);
|
||||
set_member_uv_trim_curve(&mut b, b0, stale, None).unwrap();
|
||||
let owner = uv_trim_curve_pointer(&b, a0)
|
||||
.unwrap()
|
||||
.and_then(Curve::owner);
|
||||
assert_eq!(owner, owner_ref_in(&b, a0));
|
||||
assert_eq!(owner, Some(owner_ref_in(&b, a0)));
|
||||
assert!(owner.is_some());
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ use solidcore::topology::brep_data_write::FromBrepEnv;
|
||||
use solidcore::topology::edgeuse::p3::get_or_create_uv_trim_curve;
|
||||
use solidcore::topology::topology::NotifyRecords;
|
||||
use solidcore::topology::{EdgeId, EdgeuseId, FaceId, NamingScheme};
|
||||
use solidcore::{Brep, EntityRef, SolidError};
|
||||
use solidcore::{Brep, EntityRef};
|
||||
|
||||
/// A caller naming: the notification layout shifted by `base`.
|
||||
fn named(e: EntityRef, base: u64) -> ObjectRef {
|
||||
@@ -204,11 +204,14 @@ fn foreign_trim_owner_still_fails_test_6() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn caller_body_without_keys_is_refused() {
|
||||
fn caller_body_without_keys_is_checked_under_the_default_identities() {
|
||||
let mut k = Kernel::default();
|
||||
// a body built through the Euler operators records no caller keys
|
||||
// a body that records no caller keys is not refused (#1631): the check names each
|
||||
// entity by the scheme's default identity, as the binders name an entity without
|
||||
// a recorded key, and the box's geometry is owned by those
|
||||
let mut b = create_box(&mut k, Vec3::new(0.0, 0.0, 0.0), 10.0, 20.0, 30.0).unwrap();
|
||||
b.set_naming(NamingScheme::Caller);
|
||||
let err = check_brep(&mut k, &mut b, CheckLevel::Level2).unwrap_err();
|
||||
assert!(matches!(err, SolidError::InvalidInput { .. }), "{err:?}");
|
||||
let r = check_brep(&mut k, &mut b, CheckLevel::Level2).unwrap();
|
||||
assert!(r.is_valid(), "{:?}", r.lines());
|
||||
assert!(r.lines().is_empty(), "{:?}", r.lines());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,331 @@
|
||||
//! tangency-labs/solidcore#1631: an operation on a `NamingScheme::Caller` body (an
|
||||
//! `SmApiBrepCopy` that records its caller's keys, #1585) keeps that scheme, and
|
||||
//! `check_brep` checks the result as the C++ `smlib_brep_check` does: every geometry
|
||||
//! owner is its entity (`pGeom->GetOwner() == this`). The edges, faces and edge uses
|
||||
//! an operation creates record no caller key; they are named by the scheme's default
|
||||
//! identity, by the binders and by the check alike (`Brep::owner_identity`), so the
|
||||
//! check finds the result valid instead of refusing it with `InvalidInput`.
|
||||
#![allow(clippy::unwrap_used, clippy::panic, clippy::indexing_slicing)] // test helpers
|
||||
|
||||
#[path = "support/offset_fixture.rs"]
|
||||
mod offset_fixture;
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use solidcore::blend::fillet_executive::api::fillet_edges;
|
||||
use solidcore::blend::fillet_executive::session::FilletSession;
|
||||
use solidcore::boolean::api_brep::{boolean, boolean_union};
|
||||
use solidcore::curve::curve::Curve;
|
||||
use solidcore::curve::line::Line;
|
||||
use solidcore::geom::a_object::BooleanOperation;
|
||||
use solidcore::geom::attribute::{AttributeStore, ObjectKey};
|
||||
use solidcore::geom::object::ObjectRef;
|
||||
use solidcore::mass::integrator::properties::{brep_volume, PropertyAttributes};
|
||||
use solidcore::math::{Point3, Vec3};
|
||||
use solidcore::nurbs::b_spline_curve::BSplineCurve;
|
||||
use solidcore::primitive::api_primitives::create_box;
|
||||
use solidcore::sweep::api_brep::create_linear_sweep;
|
||||
use solidcore::topology::brep::p10::{
|
||||
make_topology_from_data, project_and_trim, MakeTopologyOptions, ProjectAndTrimOptions,
|
||||
ProjectEnv, TopologyEnv, TrimType,
|
||||
};
|
||||
use solidcore::topology::brep::p2::ArenaNotifyState;
|
||||
use solidcore::topology::brep::p3::{api_brep_copy, topology_ref};
|
||||
use solidcore::topology::brep::p5::{stitch_and_orient, StitchAndOrientOptions};
|
||||
use solidcore::topology::brep::validation::{check_brep, CheckLevel};
|
||||
use solidcore::topology::brep_cache::CacheObject;
|
||||
use solidcore::topology::brep_data::BrepData;
|
||||
use solidcore::topology::database_io::FileType;
|
||||
use solidcore::topology::edge::NotifyEnv;
|
||||
use solidcore::topology::{edge, vertex, Brep, BrepId, EdgeId, NamingScheme};
|
||||
use solidcore::{EntityRef, Kernel};
|
||||
|
||||
/// Each copy names its objects in its own key domain (as SolidCraft's copies do).
|
||||
fn named(e: EntityRef, domain: u64) -> ObjectRef {
|
||||
let r = topology_ref(0, e);
|
||||
ObjectRef {
|
||||
type_code: r.type_code,
|
||||
key: ObjectKey(domain + r.type_code as u64 * 1_000_000 + (r.key.0 & 0xffff_ffff)),
|
||||
}
|
||||
}
|
||||
|
||||
/// `SmApiBrepCopy` of `b` named in domain `n`: a `NamingScheme::Caller` body with its
|
||||
/// keys recorded (#1585).
|
||||
fn copy(k: &mut Kernel, b: &Brep, n: u64) -> Brep {
|
||||
let target = move |e| named(e, n * 100_000_000);
|
||||
let mut state = ArenaNotifyState::new(&Brep::new());
|
||||
let mut env = state.env(None);
|
||||
env.key_of = ⌖
|
||||
let c = api_brep_copy(k, &mut env, Some(b), &|e| named(e, 0)).unwrap();
|
||||
assert_eq!(c.naming(), NamingScheme::Caller);
|
||||
c
|
||||
}
|
||||
|
||||
fn volume(k: &Kernel, b: &mut Brep) -> f64 {
|
||||
let store = AttributeStore::default();
|
||||
let attrs = PropertyAttributes {
|
||||
store: &store,
|
||||
key_of: &|_| ObjectKey(0),
|
||||
};
|
||||
brep_volume(k, b, 1e-9, &attrs).unwrap()
|
||||
}
|
||||
|
||||
/// `check_brep` at Level2 finds `b` valid, with no failed test.
|
||||
fn assert_checks_valid(k: &mut Kernel, b: &mut Brep, what: &str) {
|
||||
let c = check_brep(k, b, CheckLevel::Level2)
|
||||
.unwrap_or_else(|e| panic!("{what}: check_brep refused the body: {e}"));
|
||||
assert!(c.is_valid(), "{what}: {:#?}", c.lines());
|
||||
assert!(c.lines().is_empty(), "{what}: {:#?}", c.lines());
|
||||
}
|
||||
|
||||
fn assert_volume(k: &Kernel, b: &mut Brep, analytic: f64, what: &str) {
|
||||
let v = volume(k, b);
|
||||
assert!(
|
||||
(v - analytic).abs() <= 1e-9 * analytic,
|
||||
"{what}: volume {v}, analytic {analytic}"
|
||||
);
|
||||
}
|
||||
|
||||
fn boxes(k: &mut Kernel) -> (Brep, Brep) {
|
||||
let a = create_box(k, Point3::new(0.0, 0.0, 0.0), 10.0, 10.0, 10.0).unwrap();
|
||||
let b = create_box(k, Point3::new(5.0, 5.0, 5.0), 10.0, 10.0, 10.0).unwrap();
|
||||
(a, b)
|
||||
}
|
||||
|
||||
/// Two 10 mm cubes overlapping in a 5 mm cube: 2 * 1000 - 125.
|
||||
const UNION_VOLUME: f64 = 1875.0;
|
||||
|
||||
/// The issue's first repro: the union of two caller-keyed copies.
|
||||
#[test]
|
||||
fn union_of_two_keyed_copies_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let (a, b) = boxes(&mut k);
|
||||
let (mut ca, cb) = (copy(&mut k, &a, 1), copy(&mut k, &b, 2));
|
||||
assert_checks_valid(&mut k, &mut ca, "copy");
|
||||
let mut u = boolean(&mut k, ca, cb, BooleanOperation::Union)
|
||||
.unwrap()
|
||||
.result;
|
||||
assert_eq!(u.naming(), NamingScheme::Caller);
|
||||
assert_volume(&k, &mut u, UNION_VOLUME, "union(copy, copy)");
|
||||
assert_checks_valid(&mut k, &mut u, "union(copy, copy)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_of_keyed_copy_with_box_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let (a, b) = boxes(&mut k);
|
||||
let ca = copy(&mut k, &a, 1);
|
||||
let mut u = boolean_union(&mut k, ca, b).unwrap();
|
||||
assert_volume(&k, &mut u, UNION_VOLUME, "union(copy, box)");
|
||||
assert_checks_valid(&mut k, &mut u, "union(copy, box)");
|
||||
}
|
||||
|
||||
/// Side finding: the union of two plain boxes (a notification-named result) left the
|
||||
/// original box's geometry without owner (`SmBrep` test #9, `SmEdge` test #61).
|
||||
#[test]
|
||||
fn union_of_two_boxes_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let (a, b) = boxes(&mut k);
|
||||
let mut u = boolean_union(&mut k, a, b).unwrap();
|
||||
assert_volume(&k, &mut u, UNION_VOLUME, "union(box, box)");
|
||||
assert_checks_valid(&mut k, &mut u, "union(box, box)");
|
||||
}
|
||||
|
||||
/// The box's top front edge, along x at y = 0, z = `top`.
|
||||
fn top_front_edge(b: &Brep, top: f64) -> EdgeId {
|
||||
b.edges()
|
||||
.ids()
|
||||
.find(|&e| {
|
||||
let (s, t) = edge::start_end_vertices(b, e).unwrap();
|
||||
let (p, q) = (
|
||||
vertex::point(b, s.unwrap()).unwrap(),
|
||||
vertex::point(b, t.unwrap()).unwrap(),
|
||||
);
|
||||
p.y.abs() < 1e-9 && q.y.abs() < 1e-9 && p.z == top && q.z == top
|
||||
})
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// The issue's second repro: a 30 x 20 x 10 copy with its top front edge rounded r2,
|
||||
/// 6000 - (1 - pi/4) * 2^2 * 30.
|
||||
#[test]
|
||||
fn fillet_session_on_keyed_copy_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let a = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 30.0, 20.0, 10.0).unwrap();
|
||||
let mut c = copy(&mut k, &a, 1);
|
||||
let e = top_front_edge(&c, 10.0);
|
||||
{
|
||||
let mut s = FilletSession::new(&mut k, &mut c).unwrap();
|
||||
s.prepare_constant_radius_edges(&mut k, &[e], 2.0, 1, 1, 1.0)
|
||||
.unwrap();
|
||||
s.create_corners(&k, None).unwrap();
|
||||
s.do_filleting(&mut k, None).unwrap();
|
||||
}
|
||||
stitch_and_orient(&mut k, &mut c, &StitchAndOrientOptions::default()).unwrap();
|
||||
assert_eq!(c.naming(), NamingScheme::Caller);
|
||||
let analytic = 6000.0 - (1.0 - PI / 4.0) * 4.0 * 30.0;
|
||||
assert_volume(&k, &mut c, analytic, "fillet session");
|
||||
assert_checks_valid(&mut k, &mut c, "fillet session");
|
||||
}
|
||||
|
||||
/// `fillet_edges` (`SmApiFilletEdges`) of a 10 mm keyed copy's edge, r1:
|
||||
/// 1000 - (1 - pi/4) * 10.
|
||||
#[test]
|
||||
fn fillet_edges_of_keyed_copy_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let a = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 10.0, 10.0, 10.0).unwrap();
|
||||
let mut c = copy(&mut k, &a, 1);
|
||||
let e = top_front_edge(&c, 10.0);
|
||||
fillet_edges(&mut k, &mut c, &[e], 1.0, 1, 1, 1.0).unwrap();
|
||||
assert_eq!(c.naming(), NamingScheme::Caller);
|
||||
let analytic = 1000.0 - (1.0 - PI / 4.0) * 10.0;
|
||||
assert_volume(&k, &mut c, analytic, "fillet_edges");
|
||||
assert_checks_valid(&mut k, &mut c, "fillet_edges");
|
||||
}
|
||||
|
||||
/// A keyed copy of a 10 mm box shelled inward by 1 with its top open:
|
||||
/// 1000 - 8 * 8 * 9.
|
||||
#[test]
|
||||
fn shell_of_keyed_copy_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let a = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 10.0, 10.0, 10.0).unwrap();
|
||||
let c = copy(&mut k, &a, 1);
|
||||
let top = offset_fixture::planar_faces_at(&k, &c, 2, 10.0);
|
||||
assert_eq!(top.len(), 1);
|
||||
let mut s = offset_fixture::shell(&mut k, c, -1.0, &top).unwrap();
|
||||
assert_eq!(s.naming(), NamingScheme::Caller);
|
||||
assert_volume(&k, &mut s, 1000.0 - 576.0, "shell");
|
||||
assert_checks_valid(&mut k, &mut s, "shell");
|
||||
}
|
||||
|
||||
fn line(a: Point3, b: Point3) -> Curve {
|
||||
Curve::from(Line::create_line_segment(3, a, b, None).unwrap())
|
||||
}
|
||||
|
||||
/// A 4 x 4 boss extruded 5 from the top of a keyed copy of a 10 mm box and united
|
||||
/// with it: 1000 + 4 * 4 * 5.
|
||||
#[test]
|
||||
fn extrude_onto_keyed_copy_checks_valid() {
|
||||
let mut k = Kernel::default();
|
||||
let a = create_box(&mut k, Point3::new(0.0, 0.0, 0.0), 10.0, 10.0, 10.0).unwrap();
|
||||
let c = copy(&mut k, &a, 1);
|
||||
let p = [(2.0, 2.0), (6.0, 2.0), (6.0, 6.0), (2.0, 6.0)].map(|(x, y)| Point3::new(x, y, 10.0));
|
||||
let sides: Vec<Curve> = (0..4).map(|i| line(p[i], p[(i + 1) % 4])).collect();
|
||||
let refs: Vec<Option<&Curve>> = sides.iter().map(Some).collect();
|
||||
let boss = create_linear_sweep(&mut k, &refs, Vec3::new(0.0, 0.0, 1.0), 5.0, true).unwrap();
|
||||
let mut u = boolean_union(&mut k, c, boss).unwrap();
|
||||
assert_volume(&k, &mut u, 1080.0, "extrude onto copy");
|
||||
assert_checks_valid(&mut k, &mut u, "extrude onto copy");
|
||||
}
|
||||
|
||||
/// The difftest runner's caller keys: kind and arena index.
|
||||
fn key_of(e: EntityRef) -> ObjectRef {
|
||||
let (t, k, i) = match e {
|
||||
EntityRef::Brep(x) => (16011, 0, x.index()),
|
||||
EntityRef::Region(x) => (16012, 1, x.index()),
|
||||
EntityRef::Shell(x) => (16013, 2, x.index()),
|
||||
EntityRef::Faceuse(x) => (16022, 3, x.index()),
|
||||
EntityRef::Face(x) => (16002, 4, x.index()),
|
||||
EntityRef::Loopuse(x) => (16004, 5, x.index()),
|
||||
EntityRef::Loop(x) => (16003, 6, x.index()),
|
||||
EntityRef::Edgeuse(x) => (16005, 7, x.index()),
|
||||
EntityRef::Edge(x) => (16016, 8, x.index()),
|
||||
EntityRef::Vertexuse(x) => (16006, 9, x.index()),
|
||||
EntityRef::Vertex(x) => (16021, 10, x.index()),
|
||||
};
|
||||
ObjectRef {
|
||||
type_code: t,
|
||||
key: ObjectKey((1u64 << 40) * (k + 1) + u64::from(i)),
|
||||
}
|
||||
}
|
||||
/// An imported body names its objects by its caller's keys (`make_topology_from_data`);
|
||||
/// `ProjectAndTrim` splits edges, whose new edge uses get UV trims before their keys
|
||||
/// are recorded. They end up owned under their recorded keys, as in the C++ (the
|
||||
/// `topology.brep_p10` `project_*_Defeature_inset_cube` cases).
|
||||
#[test]
|
||||
fn project_and_trim_names_split_trims_by_caller_keys() {
|
||||
// the upstream test file is not checked in (CI's test job has no upstream tree; the
|
||||
// difftest job runs the same body through `topology.brep_p10`), as
|
||||
// `issue_1570_flat_body_ray_fire`
|
||||
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../upstream/TestFiles/pt_TestFiles/Defeature/inset_cube.smb");
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
eprintln!("unavailable upstream test file: {}", path.display());
|
||||
return;
|
||||
};
|
||||
let mut k = Kernel::default();
|
||||
let mut data = BrepData::read_from_file(&k, bytes, FileType::Ascii).unwrap();
|
||||
let mut b = Brep::new_with_infinite_region().unwrap();
|
||||
let mut store = AttributeStore::new();
|
||||
let mut records = solidcore::topology::topology::NotifyRecords::new();
|
||||
let mut env = TopologyEnv {
|
||||
all_attributes: &[],
|
||||
attributes: &mut store,
|
||||
key_of: &key_of,
|
||||
records: &mut records,
|
||||
next_key: 1u64 << 50,
|
||||
};
|
||||
let options = MakeTopologyOptions {
|
||||
make_uv_trim_curves: true,
|
||||
add_analytics: false,
|
||||
force_same_tol: false,
|
||||
check_uv_trim_curves: false,
|
||||
always_heal: false,
|
||||
healer_enabled: false,
|
||||
};
|
||||
make_topology_from_data(&mut k, &mut b, &mut data, &mut env, options).unwrap();
|
||||
assert_eq!(b.naming(), NamingScheme::Caller);
|
||||
let keys = |o: CacheObject| match o {
|
||||
CacheObject::Edge(e) | CacheObject::Curve(e) => key_of(EntityRef::Edge(e)),
|
||||
CacheObject::Face(f) | CacheObject::Surface(f) => key_of(EntityRef::Face(f)),
|
||||
CacheObject::Vertex(x) => key_of(EntityRef::Vertex(x)),
|
||||
CacheObject::Other(r) => r,
|
||||
_ => key_of(EntityRef::Brep(BrepId::from_index(0))),
|
||||
};
|
||||
let owner_of = |_: ObjectRef| None;
|
||||
let penv = ProjectEnv {
|
||||
brep_id: BrepId::from_index(0),
|
||||
keys: &keys,
|
||||
owner_of: &owner_of,
|
||||
};
|
||||
let opts = ProjectAndTrimOptions {
|
||||
approximation_tol_3d: None,
|
||||
angle_tol_radians: None,
|
||||
subset_faces: None,
|
||||
};
|
||||
let mut notify = NotifyEnv {
|
||||
records: &mut records,
|
||||
attributes: &mut store,
|
||||
context: None,
|
||||
key_of: &key_of,
|
||||
};
|
||||
let curve = BSplineCurve::create_line_segment(
|
||||
3,
|
||||
Vec3::new(-100.0, 0.5, 100.0),
|
||||
Vec3::new(100.0, 0.5, 100.0),
|
||||
)
|
||||
.unwrap();
|
||||
let r = project_and_trim(
|
||||
&mut k,
|
||||
&mut b,
|
||||
&penv,
|
||||
&mut notify,
|
||||
&Curve::from(curve),
|
||||
Vec3::new(0.0, 0.0, -1.0),
|
||||
Vec3::new(0.0, 10.0, 100.0),
|
||||
TrimType::KeepPoint,
|
||||
&opts,
|
||||
);
|
||||
r.unwrap();
|
||||
for (u, rec) in b.edgeuses().iter() {
|
||||
if let Some(t) = rec.uv_trim_curve.as_ref() {
|
||||
if t.owner() != Some(key_of(EntityRef::Edgeuse(u))) {
|
||||
eprintln!(
|
||||
"BAD {u:?} owner={:?} identity={:?}",
|
||||
t.owner(),
|
||||
b.owner_identity(EntityRef::Edgeuse(u))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"name": "copy_union_box_capi0",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiBooleanUnion",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 0,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 1,
|
||||
"index": 0,
|
||||
"value": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_union_box_capi2",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiBooleanUnion",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 2,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 1,
|
||||
"index": 0,
|
||||
"value": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_union_copy_capi0",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiBooleanUnion",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 0,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 2,
|
||||
"index": 0,
|
||||
"value": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_union_copy_capi2",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiBooleanUnion",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 2,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 2,
|
||||
"index": 0,
|
||||
"value": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_fillet_edges_capi0",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiFilletEdges",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 0,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 3,
|
||||
"index": 0,
|
||||
"value": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_fillet_edges_capi2",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiFilletEdges",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 2,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 3,
|
||||
"index": 0,
|
||||
"value": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_fillet_edges_e5_capi0",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiFilletEdges",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 0,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 3,
|
||||
"index": 5,
|
||||
"value": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_fillet_edges_e5_capi2",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiFilletEdges",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 2,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 3,
|
||||
"index": 5,
|
||||
"value": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_shell_capi0",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiShellBrepFull",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 0,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 4,
|
||||
"index": 0,
|
||||
"value": -1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_shell_capi2",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiShellBrepFull",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 2,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 4,
|
||||
"index": 0,
|
||||
"value": -1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_shell_f3_capi0",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiShellBrepFull",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 0,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 4,
|
||||
"index": 3,
|
||||
"value": -1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "copy_shell_f3_capi2",
|
||||
"upstream_ref": "source/SM_API/src/SmApiBrep.cpp:SmApiShellBrepFull",
|
||||
"inputs": {
|
||||
"brep": {
|
||||
"kind": "box",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"size": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
]
|
||||
},
|
||||
"level": 2,
|
||||
"capi": true,
|
||||
"copy": true,
|
||||
"copy_op": 4,
|
||||
"index": 3,
|
||||
"value": -1.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Native pinned SmBrep::AssertValid (#1419) and the capi smlib_brep_check sequence
|
||||
// (ValidatePointers, then AssertValid when it reported no failure).
|
||||
#include "sc_edgeuse_graph.h"
|
||||
#include <SmApiBrep.h>
|
||||
#include <SmApiPrimitives.h>
|
||||
#include <SmApiQueries.h>
|
||||
#include <SmAssertArray.h>
|
||||
@@ -153,6 +154,57 @@ SmBrep* primitive(const double* in, int64_t n, SmStatus& status) {
|
||||
if (s != SM_SUCCESS) { delete b; return nullptr; }
|
||||
return b;
|
||||
}
|
||||
// Op 4's operation on the SmApiBrepCopy `copy` (taken): 1 SmApiBooleanUnion with
|
||||
// SmApiCreateBox(v, v, v; 10, 10, 10), 2 the same with that box's SmApiBrepCopy, 3
|
||||
// SmApiFilletEdges of edge `index` (GetEdges order, modulo the count) radius v (circular,
|
||||
// G1, thumbweight 1), 4 SmApiShellBrepFull distance v (extended, merged, no offset solid)
|
||||
// with face `index` (GetFaces order, modulo the count) open. The result, or NULL with
|
||||
// `status` on failure.
|
||||
SmBrep* copy_operation(SmBrep* copy, int kind, size_t index, double v, SmStatus& status) {
|
||||
SmBrep* result = nullptr;
|
||||
if (kind == 1 || kind == 2) {
|
||||
SmBrep* box = nullptr;
|
||||
status = (SmStatus)SmApiCreateBox(SmVector3d(v, v, v), 10, 10, 10, box);
|
||||
if (status == SM_SUCCESS && kind == 2) {
|
||||
SmBrep* boxcopy = nullptr;
|
||||
status = (SmStatus)SmApiBrepCopy(box, boxcopy);
|
||||
delete box;
|
||||
box = boxcopy;
|
||||
}
|
||||
if (status != SM_SUCCESS || !box) { delete box; delete copy; return nullptr; }
|
||||
status = (SmStatus)SmApiBooleanUnion(copy, box, result);
|
||||
if (status != SM_SUCCESS) {
|
||||
if (result && result != copy && result != box) delete result;
|
||||
delete box;
|
||||
delete copy;
|
||||
return nullptr;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (kind == 3) {
|
||||
SmTArray<SmEdge*> edges, selected;
|
||||
copy->GetEdges(edges);
|
||||
if (edges.GetSize() == 0) { status = SM_ERR; delete copy; return nullptr; }
|
||||
selected.Add(edges[(ULONG)(index % edges.GetSize())]);
|
||||
status = (SmStatus)SmApiFilletEdges(copy, selected, v, 1, 1, 1.0);
|
||||
if (status != SM_SUCCESS) { delete copy; return nullptr; }
|
||||
return copy;
|
||||
}
|
||||
if (kind == 4) {
|
||||
SmTArray<SmFace*> faces;
|
||||
copy->GetFaces(faces);
|
||||
if (faces.GetSize() == 0) { status = SM_ERR; delete copy; return nullptr; }
|
||||
SmTArray<const SmFace*> open;
|
||||
open.Add(faces[(ULONG)(index % faces.GetSize())]);
|
||||
status = (SmStatus)SmApiShellBrepFull(copy, v, TRUE, TRUE, FALSE, open, result);
|
||||
delete copy;
|
||||
if (status != SM_SUCCESS) { delete result; return nullptr; }
|
||||
return result;
|
||||
}
|
||||
status = SM_ERR;
|
||||
delete copy;
|
||||
return nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// op 0: AssertValid(&reports, level, walk); op 1: the capi sequence at `level` with
|
||||
@@ -161,10 +213,13 @@ SmBrep* primitive(const double* in, int64_t n, SmStatus& status) {
|
||||
// edge sm_AssertTestEdge13 (passed, max gap), then per vertex sm_AssertTestVertex1
|
||||
// (passed, max gap 3d, max gap face index or -1), after the three counts; op 3: the capi
|
||||
// sequence of op 1 on the body's SmApiBrepCopy (no mutation), as a caller checks a copy
|
||||
// (#1584). args: level, walk, mutation kind, index, value.
|
||||
// (#1584); op 4: the capi sequence of op 1 on the result of an operation on the body's
|
||||
// SmApiBrepCopy (`copy_operation`; args[2] is its kind, no mutation), as a caller checks
|
||||
// an edit of a copy (#1631). args: level, walk, mutation kind, index, value.
|
||||
SC_EXPORT int32_t sc_topology_assert_valid_eval(int32_t op, const char* path, const double* input, int64_t n,
|
||||
const double* args, int64_t na, double* out, int64_t cap, int64_t* len) {
|
||||
if (op < 0 || op > 3 || !input || n < 1 || !args || na != 5) return SC_ERR_ARG;
|
||||
if (op < 0 || op > 4 || !input || n < 1 || !args || na != 5) return SC_ERR_ARG;
|
||||
if (op == 4 && (args[2] < 1 || args[2] > 4)) return SC_ERR_ARG;
|
||||
if (op == 3 && args[2] != 0) return SC_ERR_ARG;
|
||||
SC_TRY
|
||||
Rd rd{input, 0};
|
||||
@@ -180,6 +235,16 @@ SC_EXPORT int32_t sc_topology_assert_valid_eval(int32_t op, const char* path, co
|
||||
built.brep = status == SM_SUCCESS ? copy : nullptr;
|
||||
if (!built.brep) delete copy;
|
||||
}
|
||||
if (built.brep && op == 4) {
|
||||
SmBrep* copy = nullptr;
|
||||
status = (SmStatus)SmApiBrepCopy(built.brep, copy);
|
||||
delete built.brep;
|
||||
built.brep = nullptr;
|
||||
if (status == SM_SUCCESS && copy)
|
||||
built.brep = copy_operation(copy, (int)args[2], (size_t)args[3], args[4], status);
|
||||
else
|
||||
delete copy;
|
||||
}
|
||||
if (!built.brep) {
|
||||
values = {-1, (double)status};
|
||||
return sc_copy_out(values.data(), (int64_t)values.size(), out, cap, len);
|
||||
@@ -188,7 +253,7 @@ SC_EXPORT int32_t sc_topology_assert_valid_eval(int32_t op, const char* path, co
|
||||
walk(built.brep, g);
|
||||
values.push_back(0);
|
||||
{
|
||||
Mutation mutation(g, (int)args[2], (size_t)args[3], args[4]);
|
||||
Mutation mutation(g, op == 4 ? 0 : (int)args[2], (size_t)args[3], args[4]);
|
||||
const SmAssertTestLevel level = (SmAssertTestLevel)(int)args[0];
|
||||
if (op == 2) {
|
||||
values.insert(values.end(), {(double)g.faces.size(), (double)g.edges.size(), (double)g.vertices.size()});
|
||||
|
||||
@@ -9,4 +9,4 @@ tests = ["prog_test:my_test_topology"]
|
||||
status = "green"
|
||||
claimed_by = "Claude Code"
|
||||
claimed_at = "2026-10-29"
|
||||
notes = "GREEN: difftest 828 cases, 819 pass, 0 failed, 0 deferred, 9 rejected (WP4a seams of #1478 plus the extrusion AssertValid; harness owner/tolerance fixes). PR #1459, issue #1419 (WP1): SmBrep::AssertValid whole-body walk (Brep, Region, Shell, Face, Faceuse, Loop, Loopuse, Edge, Edgeuse, Vertex, Vertexuse level-0 bodies; SmTopology/SmOwningTopology/SmSAGObject base checks; validate-only tolerance pass; SmCurve::AssertValid dispatch), FormatLogMessage, SM_GetObjTypeString, public check_brep -> BrepCheck (capi smlib_brep_check). PARTIAL: 532 cases (516 fixtures: 14 primitive/constructed bodies + 14 upstream .smb files at levels 0-4, no-walk and capi levels 0-2, 10 mutation kinds on 6 bodies; 16 generated), 154 pass, 378 deferred (all on WP4 seams of #1478 except 8: topology.face_p4 201 sm_AssertTestFace32; topology.vertex 91 sm_AssertTestVertex1; topology.edge 78 sm_AssertTestEdge13; surface.surf_of_extrusion_p1 8 SmSurfOfExtrusion::AssertValid), 0 known mismatches, max_rel_err 0. sphere_mutation6_capi fixed: the legacy MakeFace copy now shares the surface's cache slot, so the trimmed cache built during creation persists as upstream's does (plan/findings/topology.assert_valid--sphere-moved-pole-lazy-trim--addendum-2026-10-29-fixed.md). Seams in assert_valid/seams.rs: WP2 helpers wired to #1472, SmSurface::AssertValid wired to #1460; WP3b curve checks and WP4 tests remain stubs (UNPORTED tags; sm_AssertTestFace32/33/37/38, sm_AssertTestEdge13 and sm_AssertTestVertex1 are not in units.json and are tagged with the topology units WP2/WP4 extend; SmSurface::AssertValid wired to surface.surface_assert_valid (PR #1460); SmPlane placement reports are named by the surface key on both sides). Regression tests: crates/solidcore/tests/issue_1419_brep_check.rs. [PR #1478 (#1419 WP4a, stacked on #1459): level-2 seams wired (sm_AssertTestFace32/33/38 -> topology::face::assert_tests, sm_AssertTestEdge13 -> topology::edge::edge_form, sm_AssertTestVertex1 -> topology::vertex::corner). Shim op 2 compares those tests entity by entity: 202 cases in fixtures/topology.assert_valid/level2_tests.json (197 pass, 5 rejected: null-link mutations 8-10 and bodies the shim cannot build); 94 level-2 AssertValid/capi cases in level2_assert_valid.json (deferred at the WP2/WP3a seams). PARTIAL topology.assert_valid cases=828 passed=351 failed=0 deferred=467 rejected=9 known_mismatch=1 max_rel_err=0e0]"
|
||||
notes = "#1631: fixtures/topology.assert_valid/copy_ops.json (12 cases, shim op 4): the capi check of a union (with a box, with a box copy), SmApiFilletEdges and SmApiShellBrepFull result of an SmApiBrepCopy, all valid on both sides. GREEN: difftest 828 cases, 819 pass, 0 failed, 0 deferred, 9 rejected (WP4a seams of #1478 plus the extrusion AssertValid; harness owner/tolerance fixes). PR #1459, issue #1419 (WP1): SmBrep::AssertValid whole-body walk (Brep, Region, Shell, Face, Faceuse, Loop, Loopuse, Edge, Edgeuse, Vertex, Vertexuse level-0 bodies; SmTopology/SmOwningTopology/SmSAGObject base checks; validate-only tolerance pass; SmCurve::AssertValid dispatch), FormatLogMessage, SM_GetObjTypeString, public check_brep -> BrepCheck (capi smlib_brep_check). PARTIAL: 532 cases (516 fixtures: 14 primitive/constructed bodies + 14 upstream .smb files at levels 0-4, no-walk and capi levels 0-2, 10 mutation kinds on 6 bodies; 16 generated), 154 pass, 378 deferred (all on WP4 seams of #1478 except 8: topology.face_p4 201 sm_AssertTestFace32; topology.vertex 91 sm_AssertTestVertex1; topology.edge 78 sm_AssertTestEdge13; surface.surf_of_extrusion_p1 8 SmSurfOfExtrusion::AssertValid), 0 known mismatches, max_rel_err 0. sphere_mutation6_capi fixed: the legacy MakeFace copy now shares the surface's cache slot, so the trimmed cache built during creation persists as upstream's does (plan/findings/topology.assert_valid--sphere-moved-pole-lazy-trim--addendum-2026-10-29-fixed.md). Seams in assert_valid/seams.rs: WP2 helpers wired to #1472, SmSurface::AssertValid wired to #1460; WP3b curve checks and WP4 tests remain stubs (UNPORTED tags; sm_AssertTestFace32/33/37/38, sm_AssertTestEdge13 and sm_AssertTestVertex1 are not in units.json and are tagged with the topology units WP2/WP4 extend; SmSurface::AssertValid wired to surface.surface_assert_valid (PR #1460); SmPlane placement reports are named by the surface key on both sides). Regression tests: crates/solidcore/tests/issue_1419_brep_check.rs. [PR #1478 (#1419 WP4a, stacked on #1459): level-2 seams wired (sm_AssertTestFace32/33/38 -> topology::face::assert_tests, sm_AssertTestEdge13 -> topology::edge::edge_form, sm_AssertTestVertex1 -> topology::vertex::corner). Shim op 2 compares those tests entity by entity: 202 cases in fixtures/topology.assert_valid/level2_tests.json (197 pass, 5 rejected: null-link mutations 8-10 and bodies the shim cannot build); 94 level-2 AssertValid/capi cases in level2_assert_valid.json (deferred at the WP2/WP3a seams). PARTIAL topology.assert_valid cases=828 passed=351 failed=0 deferred=467 rejected=9 known_mismatch=1 max_rel_err=0e0]"
|
||||
|
||||
Reference in New Issue
Block a user