mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-06 20:02:16 +00:00
copy_of named each copy in its own key domain with no naming scheme of its own; brep_copy gives a Notify copy. A split then marks the body Caller: with_kernel_callbacks names the cut's new edge curves by the kernel's identities, which it records, and the check reads owners by the body's naming (Test #61). Rig level: 939/974, no status or volume change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MNXLXkZLQ6T7TSi4wswcmk
This commit is contained in:
@@ -49,9 +49,9 @@ use solidcore::topology::face::p4 as face_p4;
|
||||
use solidcore::topology::topology_traverser::Marks;
|
||||
use solidcore::topology::{Brep, EdgeId, EdgeuseId, FaceId, FaceuseId, VertexId, edge, vertex};
|
||||
|
||||
use super::boolean::copy_of;
|
||||
use super::{Body, answer, run};
|
||||
use crate::{Blend, ChamferSecond, Error, Hole, HoleEdge, MAX_POINTS, Result};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
|
||||
/// The capi's statuses as SolidCraft errors: an operation's own result.
|
||||
type Out<T> = std::result::Result<T, Error>;
|
||||
@@ -507,14 +507,14 @@ pub(super) fn finish(b: Brep, solid_in: bool) -> solidcore::Result<Out<Brep>> {
|
||||
Ok(Ok(b))
|
||||
}
|
||||
|
||||
/// Runs `f` on a copy of `body` (`copy_of`) under the kernel's lock.
|
||||
/// Runs `f` on a copy of `body` (`brep_copy`) under the kernel's lock.
|
||||
pub(super) fn on_copy(call: &'static str, body: &Body, f: impl FnOnce(&mut Kernel, Brep) -> solidcore::Result<Out<Brep>>) -> Result<Body> {
|
||||
if body.0.is_poisoned() {
|
||||
return Err(Error::Internal(format!("{call}: the body was left unfinished by an earlier panic")));
|
||||
}
|
||||
run(call, |k| {
|
||||
let g = body.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
let c = copy_of(k, &g, 1)?;
|
||||
let c = brep_copy(k, &g)?;
|
||||
drop(g);
|
||||
f(k, c)
|
||||
})?
|
||||
@@ -584,7 +584,7 @@ fn overflow_first(
|
||||
}
|
||||
let mut fresh = {
|
||||
let g = body.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
copy_of(k, &g, 2)?
|
||||
brep_copy(k, &g)?
|
||||
};
|
||||
let again = match pick_edges(k, &fresh, points, tolerance)? {
|
||||
Ok((again, _)) if again.len() == picked.len() => again,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Booleans on solidcore: the C API's own logic round `SM_API`, ported from the fork's
|
||||
//! `capi/src/smlib_ops.cpp` (`smlib_boolean_with`, `smlib_boolean2d`,
|
||||
//! `smlib_boolean_union_all`, `finish`, `closed_nonmanifold`, `copy_of`) and
|
||||
//! `smlib_boolean_union_all`, `finish`, `closed_nonmanifold`) and
|
||||
//! `capi/src/smlib_imprint.cpp` (`smlib_imprint`).
|
||||
//!
|
||||
//! As there, every operation works on copies of its operands (`SmApiBrepCopy`): `SM_API` edits
|
||||
@@ -8,17 +8,16 @@
|
||||
|
||||
use std::sync::PoisonError;
|
||||
|
||||
use solidcore::Result as SolidResult;
|
||||
use solidcore::SolidError;
|
||||
use solidcore::boolean::api_brep::{self as sm, Boolean2dOutcome};
|
||||
use solidcore::boolean::merge::MergeOptions;
|
||||
use solidcore::geom::a_object::BooleanOperation;
|
||||
use solidcore::geom::attribute::ObjectKey;
|
||||
use solidcore::geom::object::ObjectRef;
|
||||
use solidcore::kernel::Kernel;
|
||||
use solidcore::primitive::primitive_creation::p1::Boolean2d;
|
||||
use solidcore::topology::brep::{p1, p2, p3};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
use solidcore::topology::brep::{p1, p2};
|
||||
use solidcore::topology::{Brep, edge};
|
||||
use solidcore::{EntityRef, Result as SolidResult};
|
||||
|
||||
use super::{Body, run};
|
||||
use crate::{BooleanOp, Error, Result};
|
||||
@@ -35,34 +34,6 @@ enum Done {
|
||||
NoCut,
|
||||
}
|
||||
|
||||
/// The start of key domain `n`: past every [`named`] key of the domain before it.
|
||||
pub(super) const fn domain(n: u64) -> u64 {
|
||||
n << 36
|
||||
}
|
||||
|
||||
/// The object names of a copy's source (domain 0) and of the copy itself: each copy in a call
|
||||
/// names its objects in its own key domain ([`domain`]), as solidcore's difftest copies do, so that
|
||||
/// the notification records of source and copy never collide. Within a domain the key is
|
||||
/// `(kind + 1) << 32 | index`, [`convert`](super::convert)'s, distinct for every 32-bit arena index
|
||||
/// (solidcore refuses namings in which two live objects share a key).
|
||||
pub(super) fn named(entity: EntityRef, domain: u64) -> ObjectRef {
|
||||
let r = p3::topology_ref(0, entity);
|
||||
// `topology_ref(0, ..)`'s kind (at most 10) and index.
|
||||
let (kind, index) = ((r.key.0 >> 32) & 0xf, r.key.0 & 0xffff_ffff);
|
||||
ObjectRef { type_code: r.type_code, key: ObjectKey(domain + (((kind + 1) << 32) | index)) }
|
||||
}
|
||||
|
||||
/// `copy_of` (smlib_ops.cpp, smlib_imprint.cpp): `SmApiBrepCopy`, a copy that owns its geometry.
|
||||
/// A plain `Clone` is not one: its geometry keeps the source's owners. Copy `n` (from 1) names
|
||||
/// its objects in key domain `n`.
|
||||
pub(crate) fn copy_of(k: &mut Kernel, b: &Brep, n: u64) -> SolidResult<Brep> {
|
||||
let target = move |e| named(e, domain(n));
|
||||
let mut state = p2::ArenaNotifyState::new(&Brep::new());
|
||||
let mut env = state.env(None);
|
||||
env.key_of = ⌖
|
||||
p3::api_brep_copy(k, &mut env, Some(b), &|e| named(e, 0))
|
||||
}
|
||||
|
||||
/// `GetNumFaces`.
|
||||
fn num_faces(b: &Brep) -> usize {
|
||||
b.face_list().len()
|
||||
@@ -113,7 +84,7 @@ fn copies(k: &mut Kernel, bodies: &[&Body]) -> SolidResult<Vec<Brep>> {
|
||||
let mut out = Vec::with_capacity(bodies.len());
|
||||
for (n, b) in bodies.iter().enumerate() {
|
||||
let g = b.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
let c = copy_of(k, &g, n as u64 + 1)?;
|
||||
let c = brep_copy(k, &g)?;
|
||||
drop(g);
|
||||
out.push(c);
|
||||
}
|
||||
|
||||
@@ -475,7 +475,7 @@ mod history {
|
||||
let body = a.core().unwrap();
|
||||
let copy = super::run("copy", |k| {
|
||||
let g = body.0.lock().unwrap();
|
||||
super::super::boolean::copy_of(k, &g, 7)
|
||||
solidcore::topology::brep::p3::brep_copy(k, &g)
|
||||
})
|
||||
.unwrap();
|
||||
let core = Brep::made_core(super::Body::new(copy)).history().unwrap();
|
||||
@@ -670,45 +670,6 @@ mod history {
|
||||
}
|
||||
}
|
||||
|
||||
/// Every object of a copy's naming has a key of its own past index 1,000,000, where a key of
|
||||
/// `type_code * 1_000_000 + index` made face 1,000,000 share one with loop 0 (solidcore refuses
|
||||
/// such a naming), and domains stay apart at the largest kinds and indices.
|
||||
#[test]
|
||||
fn object_names_stay_distinct_past_a_million() {
|
||||
use solidcore::EntityRef;
|
||||
use solidcore::topology::{BrepId, EdgeId, EdgeuseId, FaceId, FaceuseId, LoopId, LoopuseId, RegionId, ShellId, VertexId, VertexuseId};
|
||||
|
||||
use super::boolean::{domain, named};
|
||||
let at = |i: u32| {
|
||||
[
|
||||
EntityRef::Region(RegionId::from_index(i)),
|
||||
EntityRef::Shell(ShellId::from_index(i)),
|
||||
EntityRef::Faceuse(FaceuseId::from_index(i)),
|
||||
EntityRef::Face(FaceId::from_index(i)),
|
||||
EntityRef::Loopuse(LoopuseId::from_index(i)),
|
||||
EntityRef::Loop(LoopId::from_index(i)),
|
||||
EntityRef::Edgeuse(EdgeuseId::from_index(i)),
|
||||
EntityRef::Edge(EdgeId::from_index(i)),
|
||||
EntityRef::Vertexuse(VertexuseId::from_index(i)),
|
||||
EntityRef::Vertex(VertexId::from_index(i)),
|
||||
]
|
||||
};
|
||||
let mut keys = std::collections::HashSet::new();
|
||||
let mut total = 0;
|
||||
for d in 0..3 {
|
||||
total += 1;
|
||||
keys.insert(named(EntityRef::Brep(BrepId::from_index(0)), domain(d)).key);
|
||||
for i in [0, 1, 1_000_000, 1_000_001, u32::MAX] {
|
||||
for e in at(i) {
|
||||
total += 1;
|
||||
keys.insert(named(e, domain(d)).key);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(keys.len(), total);
|
||||
assert_ne!(named(EntityRef::Face(FaceId::from_index(1_000_000)), 0).key, named(EntityRef::Loop(LoopId::from_index(0)), 0).key);
|
||||
}
|
||||
|
||||
/// Rig case booleans/2928, reduced (fork c2e3d9d): a ring, r 3..5 about (0, 10), z 0..2, unioned
|
||||
/// with a half annulus prism, r 4..5 about (e, 10), z 2..20, whose cap sits on the ring's top. Two
|
||||
/// equal circles a little apart cross; they do not coincide over a tiny edge. The stack's volume
|
||||
|
||||
@@ -23,10 +23,10 @@ use solidcore::topology::{Brep, EdgeId, VertexId, edge};
|
||||
use solidcore::{EntityRef, Point3, SolidError};
|
||||
use solidcraft_geom::brep::history::Change;
|
||||
|
||||
use super::boolean::copy_of;
|
||||
use super::{Body, answer, with_body};
|
||||
use crate::history::{Raw, RawEdge, RawFace};
|
||||
use crate::{Error, Result};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
|
||||
/// `TAG_ID`: above SMLib's own ids (all below `SM_AI_USER_1`).
|
||||
const TAG_ID: u64 = ids::USER_1 + 7;
|
||||
@@ -210,7 +210,7 @@ pub(crate) fn tag(body: &Body, operand: usize) -> Result<Body> {
|
||||
return Err(Error::Argument);
|
||||
}
|
||||
with_body("smlib_history_tag", body, |k, b| {
|
||||
let mut r = copy_of(k, b, 1)?;
|
||||
let mut r = brep_copy(k, b)?;
|
||||
let faces = r.face_list().to_vec();
|
||||
let edges = r.edge_list().to_vec();
|
||||
if faces.len() >= TAG_MAX || edges.len() >= TAG_MAX {
|
||||
|
||||
@@ -25,8 +25,8 @@ use solidcore::topology::{Brep, FaceId, RegionId, edge};
|
||||
|
||||
use super::Body;
|
||||
use super::blend::{edge_miss, finish, on_copy, pick_edges, points_ok, status, tolerance_ok};
|
||||
use super::boolean::copy_of;
|
||||
use crate::{Error, Result};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
|
||||
type Out<T> = std::result::Result<T, Error>;
|
||||
|
||||
@@ -127,7 +127,7 @@ fn remove_faces_inside(k: &mut Kernel, b: &mut Brep) -> solidcore::Result<Out<()
|
||||
/// glued elsewhere it joins the solid's inside to the outside) is refused, as the capi's
|
||||
/// `SMLIB_ERR_UNSUPPORTED`.
|
||||
fn close_onto_shared_edges(k: &mut Kernel, b: Brep, tolerance: f64, lamina: &mut u32) -> solidcore::Result<Out<Brep>> {
|
||||
let mut c = copy_of(k, &b, 2)?;
|
||||
let mut c = brep_copy(k, &b)?;
|
||||
let material = material_regions(&c)?;
|
||||
let outcome = match stitch_faces(k, &mut c, tolerance, None, true) {
|
||||
Ok(o) => o,
|
||||
|
||||
@@ -26,7 +26,6 @@ use solidcore::topology::brep::p2::ArenaNotifyState;
|
||||
use solidcore::topology::brep::p3::{merge_brep, new_brep};
|
||||
use solidcore::topology::brep::p8::create_face_from_surface;
|
||||
|
||||
use super::boolean::{domain, named};
|
||||
use super::merge::{merge_faces, stitch};
|
||||
use super::sweep::build_faces;
|
||||
use super::{Body, area, bounds, counts, make_box, run, to_arrays, volume};
|
||||
@@ -298,11 +297,9 @@ fn cube_and_sheet(sheet: impl FnOnce(&mut Kernel) -> solidcore::Result<Brep>) ->
|
||||
run("cube and sheet", |k| {
|
||||
let mut cube = create_box(k, Vec3::new(0.0, 0.0, 0.0), 10.0, 10.0, 10.0)?;
|
||||
let sheet = sheet(k)?;
|
||||
let target = |e| named(e, domain(1));
|
||||
let mut state = ArenaNotifyState::new(&cube);
|
||||
let mut env = state.env(None);
|
||||
env.key_of = ⌖
|
||||
merge_brep(k, &mut env, &mut cube, &sheet, &|e| named(e, 0), None, None, true)?;
|
||||
merge_brep(k, &mut env, &mut cube, &sheet, &solidcore::topology::brep::p2::copy_source_ref, None, None, true)?;
|
||||
cube.refresh_cached_topology();
|
||||
Ok(cube)
|
||||
})
|
||||
|
||||
@@ -122,10 +122,7 @@ impl FaceSet {
|
||||
}
|
||||
|
||||
/// The live view of `b` the closest-point solves read, under `identity`: `query`'s effective
|
||||
/// identity ([`BrepQuery::effective_identity`]). A working copy (`copy_of`) has no naming
|
||||
/// scheme of its own, so `BrepIdentity::of_brep` gives it the arena's identities, which name
|
||||
/// none of its geometry owners (`copy_of`'s keys, recorded on the copy as proven owners): every
|
||||
/// face and edge then drops out of a solve (solidcore#1440 was this).
|
||||
/// identity ([`BrepQuery::effective_identity`]).
|
||||
fn live<'a, 'i>(b: &'a mut Brep, identity: &'a BrepIdentity<'i>) -> solidcore::Result<LiveBrepObjects<'a, 'i>> {
|
||||
LiveBrepObjects::new(b, identity, None)
|
||||
}
|
||||
|
||||
@@ -529,21 +529,16 @@ fn box_face_distances_are_exact(what: &str, d: &[(f64, Vec3)]) {
|
||||
}
|
||||
|
||||
/// Picking faces on a solidcore box: on the body itself, on the working copy every operation
|
||||
/// makes (`copy_of`), and by solidcore's recommended usage, each face's distance is the closed
|
||||
/// form and the top face is picked. The copy names its geometry owners in its own key domain
|
||||
/// with no naming scheme of its own, so the body's identities (`BrepIdentity::of_brep`: the
|
||||
/// arena's) name none of them; the copy's proven owners (`BrepQuery::effective_identity`) do
|
||||
/// (solidcore#1440 was this). Under the arena's identities the solve fails, and the failure is
|
||||
/// an error, not a skipped face.
|
||||
/// makes (`brep_copy`), and by solidcore's recommended usage, each face's distance is the closed
|
||||
/// form and the top face is picked.
|
||||
#[test]
|
||||
fn faces_are_picked_on_the_box_and_on_its_working_copy() {
|
||||
use solidcore::SolidError;
|
||||
use solidcore::query::brep::{BrepIdentity, BrepQuery};
|
||||
let p = Vec3::new(15.0, 10.0, 20.0);
|
||||
let on_top = [[15.0, 10.0, 10.0]];
|
||||
run("pick", |k| {
|
||||
let mut b = solidcore::primitive::api_primitives::create_box(k, Vec3::new(0.0, 0.0, 0.0), 30.0, 20.0, 10.0)?;
|
||||
let mut w = super::boolean::copy_of(k, &b, 1)?;
|
||||
let mut w = solidcore::topology::brep::p3::brep_copy(k, &b)?;
|
||||
let mut faces = Vec::new();
|
||||
for (what, x) in [("the body", &mut b), ("its copy", &mut w)] {
|
||||
let query = BrepQuery::of_brep(x);
|
||||
@@ -560,14 +555,6 @@ fn faces_are_picked_on_the_box_and_on_its_working_copy() {
|
||||
let proven = BrepIdentity::of_brep(x);
|
||||
box_face_distances_are_exact(what, &face_distances(k, x, &proven.identity(), p)?);
|
||||
}
|
||||
// The copy under its naming scheme alone (the arena's identities): no face's geometry
|
||||
// owner resolves, and the query says so rather than answering from the vertices. Since
|
||||
// solidcore#1452 that is a port-side invariant, not an SMLib status.
|
||||
let arena = BrepIdentity::of_naming_with_id(solidcore::topology::BrepId::from_index(0), &w);
|
||||
match face_distances(k, &mut w, &arena, p) {
|
||||
Err(e @ SolidError::PortInvariant { .. }) if e.sm_status().is_none() => {}
|
||||
other => panic!("the copy under the arena's identities: {other:?}"),
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Split Face and Split Body on solidcore: the C API's composition, ported from the fork's
|
||||
//! `capi/src/smlib_split.cpp` (`smlib_split_faces`, `smlib_split_body`, `cut_face`,
|
||||
//! `plane_covers`, `copy_of`, `slice`, `both_sides`, `smlib_split_body_by_sheet`) and the tool surfaces of `capi/src/smlib_ssi.cpp`
|
||||
//! `plane_covers`, `slice`, `both_sides`, `smlib_split_body_by_sheet`) and the tool surfaces of `capi/src/smlib_ssi.cpp`
|
||||
//! (`smlib_surface_plane`, `_cylinder`, `_cone`, `_sphere`, `_torus`, `_nurbs`).
|
||||
//!
|
||||
//! As there, every operation works on copies of its body (`SmApiBrepCopy`). A face is cut where
|
||||
@@ -52,11 +52,11 @@ use solidcore::topology::face::p2 as face_p2;
|
||||
use solidcore::topology::{Brep, EdgeId, FaceId};
|
||||
|
||||
use super::blend::status;
|
||||
use super::boolean::copy_of;
|
||||
use super::sweep::build_faces;
|
||||
use super::{Body, run};
|
||||
use crate::ssi::Spec;
|
||||
use crate::{Error, Result};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
|
||||
/// `SMLIB_SPLIT_MAX_FACES`, `SMLIB_SPLIT_MAX_TOOLS`.
|
||||
pub(crate) const MAX_FACES: usize = crate::ffi::split::MAX_FACES;
|
||||
@@ -326,7 +326,7 @@ pub(crate) fn split_faces(body: &Body, faces: &[usize], tools: &[&Spec]) -> Resu
|
||||
if faces.iter().any(|&f| f >= count || !seen.insert(f)) {
|
||||
return Ok(Err(Error::Argument));
|
||||
}
|
||||
copy_of(k, &g, 1)?
|
||||
brep_copy(k, &g)?
|
||||
};
|
||||
let all = brep_p1::faces(&a, None)?;
|
||||
// Each chosen face and, as tools cut it, its pieces.
|
||||
@@ -373,6 +373,9 @@ pub(crate) fn split_faces(body: &Body, faces: &[usize], tools: &[&Spec]) -> Resu
|
||||
a.set_editing_enabled(editing);
|
||||
k.context.set_doing_boolean(doing);
|
||||
a.refresh_cached_topology();
|
||||
// The cut's new edges own their curves under the kernel's identities (it records them),
|
||||
// not the copy's Notify ones, and the check reads the owners by the body's naming.
|
||||
a.set_naming(solidcore::topology::NamingScheme::Caller);
|
||||
Ok(match cut? {
|
||||
Err(e) => Err(e),
|
||||
Ok(0) => Ok(None),
|
||||
@@ -387,7 +390,7 @@ pub(crate) fn split_faces(body: &Body, faces: &[usize], tools: &[&Spec]) -> Resu
|
||||
fn slice(k: &mut Kernel, body: &Body, sheet: Brep) -> solidcore::Result<Out<Option<Brep>>> {
|
||||
let a = {
|
||||
let g = body.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
copy_of(k, &g, 1)?
|
||||
brep_copy(k, &g)?
|
||||
};
|
||||
let options = MergeOptions::new(false, false, false, false, false);
|
||||
let r = match boolean_with_options(k, a, sheet, BooleanOperation::Slice, options) {
|
||||
@@ -487,7 +490,7 @@ pub(crate) fn split_body_by_sheet(body: &Body, sheet: &Body, face: usize) -> Res
|
||||
both_sides(k, body, true, |k, flip| {
|
||||
let mut copy = {
|
||||
let g = sheet.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
copy_of(k, &g, 2)?
|
||||
brep_copy(k, &g)?
|
||||
};
|
||||
let Some(&f) = brep_p1::faces(©, None)?.get(face) else { return Ok(Err(Error::Argument)) };
|
||||
let turned = orient_sheet_surfaces(k, &mut copy, Some(f), !flip);
|
||||
|
||||
@@ -276,7 +276,7 @@ fn lid(side: bool) -> Body {
|
||||
use solidcore::topology::brep::p7::copy_faces;
|
||||
let slab = make_box([-20.0; 3], [25.0, 40.0, 25.0]).unwrap();
|
||||
super::run("sheet", |k| {
|
||||
let mut whole = super::boolean::copy_of(k, &slab.0.lock().unwrap(), 1)?;
|
||||
let mut whole = solidcore::topology::brep::p3::brep_copy(k, &slab.0.lock().unwrap())?;
|
||||
let mut picked = Vec::new();
|
||||
for f in faces(&whole, None)? {
|
||||
let p = face_internal_point(k, &mut whole, f)?;
|
||||
|
||||
@@ -56,11 +56,11 @@ use solidcore::topology::topology_traverser::Marks;
|
||||
use solidcore::topology::{Brep, BrepId, EdgeId, FaceId, LoopId, edge};
|
||||
|
||||
use super::blend::{p3, status};
|
||||
use super::boolean::copy_of;
|
||||
use super::sweep::build_faces;
|
||||
use super::{Body, answer, run};
|
||||
use crate::trim::Continuation;
|
||||
use crate::{Error, Result};
|
||||
use solidcore::topology::brep::p3::brep_copy;
|
||||
|
||||
/// `MAX_HOLE_POINTS`.
|
||||
const MAX_HOLE_POINTS: usize = 10_000;
|
||||
@@ -298,7 +298,7 @@ pub(crate) fn untrim_face(body: &Body, point: [f64; 3], tol: f64, holes: Option<
|
||||
run("smlib_untrim_face", |k| {
|
||||
let mut whole = {
|
||||
let g = body.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
copy_of(k, &g, 1)?
|
||||
brep_copy(k, &g)?
|
||||
};
|
||||
// The face to untrim, in a body of its own (its hole edges lamina there) or in the copy
|
||||
// of the whole body.
|
||||
@@ -750,7 +750,7 @@ pub(crate) fn extend_sheet(sheet: &Body, point: [f64; 3], tol: f64, distance: f6
|
||||
run("smlib_extend_sheet", |k| {
|
||||
let b = {
|
||||
let g = sheet.0.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
copy_of(k, &g, 1)?
|
||||
brep_copy(k, &g)?
|
||||
};
|
||||
let faces = brep_p1::faces(&b, None)?;
|
||||
let [f] = faces.as_slice() else { return Ok(Err(Error::Unsupported)) };
|
||||
|
||||
Reference in New Issue
Block a user