mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-03 18:45:55 +00:00
STEP import: a refused solid's surface body faces as the file has it
SMLib holds both sides of a sheet's faces and gives each back on its surface's natural side, so a sheet whose file uses faces the other way came back renumbered. The reader no longer turns them (that lost the file's side); instead SMLib is given the sheet with those faces turned over exactly on the record, and they are turned back on SMLib's body, each face mapped through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,28 +100,90 @@ pub(super) fn smlib_part(ex: &Exchange, item: u64, units: Units) -> std::result:
|
||||
}
|
||||
|
||||
/// The part SMLib makes of a solid (or sheet) record the reader made.
|
||||
///
|
||||
/// SMLib holds both sides of a sheet's faces and gives each back facing its surface's natural
|
||||
/// normal, so a sheet whose file uses some faces the other way is given to SMLib with those faces
|
||||
/// turned over (exactly, on the record) and they are turned back on SMLib's body: the body faces
|
||||
/// as the file does, and each of its faces is known to be the record's face it was.
|
||||
fn solid_part(solid: &solidcraft_step::topology::Solid) -> std::result::Result<Part, Failure> {
|
||||
let failed = |e: KernelError| Failure { read: false, msg: e.to_string().trim_start_matches("the operation failed: ").to_string() };
|
||||
let (brep, smlib_order) = crate::native::smlib_record_ordered(&solid.brep).map_err(failed)?;
|
||||
let (body, body_order) = crate::native::body_from_smlib_ordered(&brep).map_err(failed)?;
|
||||
// The file face of each body face: SMLib's face i is the record's `smlib_order` face and the
|
||||
// body's `body_order` face (an empty order is the same one).
|
||||
let (Some(smlib_order), Some(body_order)) = (smlib_order, body_order) else {
|
||||
return Err(Failure { read: false, msg: "not supported yet: SMLib renumbered the solid's faces".into() });
|
||||
};
|
||||
let at = |order: &[usize], i: usize| if order.is_empty() { Some(i) } else { order.get(i).copied() };
|
||||
let faces = brep.counts().map_err(|e| failed(KernelError::Failed(format!("SMLib: {e}"))))?[0];
|
||||
let mut face_origin = vec![0u64; body.face_count()];
|
||||
for i in 0..faces {
|
||||
let file_face = at(&smlib_order.faces, i).and_then(|r| solid.face_origin.get(r)).copied();
|
||||
match (at(&body_order.faces, i).and_then(|b| face_origin.get_mut(b)), file_face) {
|
||||
(Some(slot), Some(f)) => *slot = f,
|
||||
_ => return Err(Failure { read: false, msg: "not supported yet: SMLib renumbered the solid's faces".into() }),
|
||||
let r = &solid.brep;
|
||||
let mut reversed = vec![false; r.faces.len()];
|
||||
if solid.sheet {
|
||||
for s in &r.shells {
|
||||
if let solidcraft_geom::brep::Shell::Faces { faces, .. } = s {
|
||||
for f in faces.iter().filter(|f| !f.forward) {
|
||||
if let Some(slot) = reversed.get_mut(f.face as usize) {
|
||||
*slot = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let (body, of_record) = if reversed.iter().any(|r| *r) {
|
||||
let turn = |body: &Body, by: &dyn Fn(usize) -> bool| -> crate::Result<(Body, Vec<usize>)> {
|
||||
let picked: Vec<(usize, bool)> = (0..body.face_count()).map(|f| (f, by(f))).collect();
|
||||
let turned = crate::surfaces::taken(body, body.record(), &picked)?;
|
||||
let from = kept_faces(&turned).ok_or_else(|| KernelError::Failed("a face turned over from no one face".into()))?;
|
||||
Ok((turned, from))
|
||||
};
|
||||
let as_read = Body::from_record(r.clone()).map_err(failed)?;
|
||||
// `natural`'s face j is the record's `to_record[j]`; SMLib's body's face k is `natural`'s
|
||||
// `of_natural[k]`; the body turned back's face i is SMLib's `back[i]`.
|
||||
let (natural, to_record) = turn(&as_read, &|f| reversed.get(f).copied().unwrap_or(false)).map_err(failed)?;
|
||||
let (smlib, of_natural) = smlib_body(natural.record())?;
|
||||
let record_of = |k: usize| of_natural.get(k).and_then(|j| to_record.get(*j)).copied();
|
||||
let (back, from) = turn(&smlib, &|k| record_of(k).and_then(|f| reversed.get(f)).copied().unwrap_or(false)).map_err(failed)?;
|
||||
let of_record = from.iter().map(|k| record_of(*k)).collect::<Option<Vec<usize>>>().ok_or_else(renumbered)?;
|
||||
(back.without_history(), of_record)
|
||||
} else {
|
||||
smlib_body(r)?
|
||||
};
|
||||
let face_origin = of_record.iter().map(|f| solid.face_origin.get(*f).copied()).collect::<Option<Vec<u64>>>().ok_or_else(renumbered)?;
|
||||
Ok(Part { body, closed: !solid.sheet, file_faces: solid.face_origin.len(), face_origin, refused: None })
|
||||
}
|
||||
|
||||
fn renumbered() -> Failure {
|
||||
Failure { read: false, msg: "not supported yet: SMLib renumbered the solid's faces".into() }
|
||||
}
|
||||
|
||||
/// The body SMLib makes of `record` as given, and the record's face each of its faces is.
|
||||
fn smlib_body(record: &solidcraft_geom::brep::Brep) -> std::result::Result<(Body, Vec<usize>), Failure> {
|
||||
let failed = |e: KernelError| Failure { read: false, msg: e.to_string().trim_start_matches("the operation failed: ").to_string() };
|
||||
let (brep, smlib_order) = crate::native::smlib_record_ordered(record).map_err(failed)?;
|
||||
let (body, body_order) = crate::native::body_from_smlib_ordered(&brep).map_err(failed)?;
|
||||
// SMLib's face i is the record's `smlib_order` face and the body's `body_order` face (an
|
||||
// empty order is the same one).
|
||||
let (Some(smlib_order), Some(body_order)) = (smlib_order, body_order) else { return Err(renumbered()) };
|
||||
let at = |order: &[usize], i: usize| if order.is_empty() { Some(i) } else { order.get(i).copied() };
|
||||
let faces = brep.counts().map_err(|e| failed(KernelError::Failed(format!("SMLib: {e}"))))?[0];
|
||||
let mut of_record = vec![usize::MAX; body.face_count()];
|
||||
for i in 0..faces {
|
||||
match (at(&body_order.faces, i).and_then(|b| of_record.get_mut(b)), at(&smlib_order.faces, i).filter(|r| *r < record.faces.len())) {
|
||||
(Some(slot), Some(r)) => *slot = r,
|
||||
_ => return Err(renumbered()),
|
||||
}
|
||||
}
|
||||
if of_record.contains(&usize::MAX) {
|
||||
return Err(renumbered());
|
||||
}
|
||||
Ok((body, of_record))
|
||||
}
|
||||
|
||||
/// The face of its one operand each face of `body` is, from its history.
|
||||
fn kept_faces(body: &Body) -> Option<Vec<usize>> {
|
||||
let h = body.history()?;
|
||||
let from: Vec<usize> = h
|
||||
.faces
|
||||
.iter()
|
||||
.map(|f| match f.origins.as_slice() {
|
||||
[crate::history::FaceOrigin::Face { operand: 0, face }] => Some(*face),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Option<_>>()?;
|
||||
(from.len() == body.face_count()).then_some(from)
|
||||
}
|
||||
|
||||
/// Most reads of a refused solid without faces, each leaving out the face the refusal before names.
|
||||
const MAX_LEFT_OUT_ROUNDS: usize = 8;
|
||||
|
||||
|
||||
@@ -2164,8 +2164,8 @@ fn a_patch_side_shrunk_within_its_vertexs_tolerance_is_a_pole_of_the_loop() {
|
||||
assert!(rel(m.area, area) < 1e-6, "{} vs {area}", m.area);
|
||||
}
|
||||
|
||||
/// A can of radius 5 and height 10: its bottom, its wall, and its lid on the plane `lid_z`
|
||||
/// (10 where the lid's rim lies on it).
|
||||
/// A can of radius 5 and height 10: its bottom (on a plane facing up, the face against it), its
|
||||
/// wall, and its lid on the plane `lid_z` (10 where the lid's rim lies on it).
|
||||
fn can_with_its_lid_at(lid_z: f64) -> String {
|
||||
format!(
|
||||
"{HEAD}
|
||||
@@ -2182,8 +2182,8 @@ fn can_with_its_lid_at(lid_z: f64) -> String {
|
||||
#11=ORIENTED_EDGE('',*,*,#10,.F.);
|
||||
#12=EDGE_LOOP('',(#11));
|
||||
#13=FACE_OUTER_BOUND('',#12,.T.);
|
||||
#14=PLANE('',#6);
|
||||
#15=ADVANCED_FACE('Bottom',(#13),#14,.T.);
|
||||
#14=PLANE('',#5);
|
||||
#15=ADVANCED_FACE('Bottom',(#13),#14,.F.);
|
||||
#16=ORIENTED_EDGE('',*,*,#10,.T.);
|
||||
#17=EDGE_LOOP('',(#16));
|
||||
#18=FACE_BOUND('',#17,.T.);
|
||||
@@ -2234,6 +2234,19 @@ fn a_refused_solid_comes_in_as_a_surface_body_of_the_faces_that_read() {
|
||||
assert!(why.contains("face #45") && why.contains("off the face's surface"), "{why}");
|
||||
assert_eq!((can.file_faces, can.body.face_count()), (3, 2));
|
||||
assert_eq!(can.face_origin, [15, 20]);
|
||||
// The bottom faces down as the file has it, against its plane's natural normal (SMLib gives
|
||||
// a sheet's faces back on their natural side; the import turns it back).
|
||||
let r = can.body.record();
|
||||
let bottom = can.face_origin.iter().position(|f| *f == 15).unwrap();
|
||||
let uses: Vec<bool> = r
|
||||
.shells
|
||||
.iter()
|
||||
.flat_map(|s| match s {
|
||||
solidcraft_geom::brep::Shell::Faces { faces, .. } => faces.iter().filter(|f| f.face as usize == bottom).map(|f| f.forward).collect(),
|
||||
_ => Vec::new(),
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(uses, [false], "the bottom's use");
|
||||
// The report keeps the refusal and names the face left out; no "open body" warning on top.
|
||||
assert!(imp.warnings.iter().any(|w| w.contains("'Can': imported as surfaces: ") && w.contains(why)), "{:?}", imp.warnings);
|
||||
assert!(imp.warnings.iter().any(|w| w.contains("'Can': left out: ") && w.contains("face #45")), "{:?}", imp.warnings);
|
||||
|
||||
@@ -966,13 +966,9 @@ impl<'b, 'a> Builder<'b, 'a> {
|
||||
if flipped > 0 {
|
||||
notes.push(format!("{flipped} faces turned over: their loops disagree with their same_sense"));
|
||||
}
|
||||
// What is left of a solid with faces left out is a sheet whose faces face their surfaces'
|
||||
// natural normals: SMLib holds both sides of a sheet's faces and gives each back on that
|
||||
// side, so the record is the one SMLib's body of it numbers alike.
|
||||
let open = kind == Kind::Open;
|
||||
let shells: Vec<Vec<(u32, bool)>> = shells
|
||||
.into_iter()
|
||||
.map(|s| s.into_iter().map(|(f, fwd)| (f, open || if turned.get(f as usize).copied().unwrap_or(false) { !fwd } else { fwd })).collect())
|
||||
.map(|s| s.into_iter().map(|(f, fwd)| (f, if turned.get(f as usize).copied().unwrap_or(false) { !fwd } else { fwd })).collect())
|
||||
.collect();
|
||||
|
||||
// Regions: which shells are closed (every edge used at least twice among its faces).
|
||||
|
||||
Reference in New Issue
Block a user