mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-05 03:25:58 +00:00
STEP import: a surface model's closed shell beside a sheet is a solid of its own
A SHELL_BASED_SURFACE_MODEL with both closed and open shells (Poppy Humanoid's thighs: the part and a two-face sheet on it) came in as one body, open for the sheet's edges. Each closed shell is now read as an item of its own, a solid in the model's colour, and the model's open shells as a surface body. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DtL5Lzv2n8fyKqD61NCHSF
This commit is contained in:
@@ -624,10 +624,22 @@ impl Reader {
|
||||
self.warn(format!("more than {MAX_BODIES} bodies: the rest are left out"));
|
||||
return;
|
||||
}
|
||||
// The node lists the planned solid; it becomes a body index once converted.
|
||||
out.push(self.plan.len());
|
||||
self.plan.push(Planned { item, units: u, batch: id });
|
||||
*self.uses.entry(item).or_default() += 1;
|
||||
// The node lists the planned solids (a surface model's closed shells
|
||||
// beside its sheet are solids of their own, in the model's colour); each
|
||||
// becomes a body index once converted.
|
||||
for piece in solidcraft_step::topology::items(&self.ex, item) {
|
||||
if piece != item {
|
||||
if let Some(c) = self.colors.get(&item).copied() {
|
||||
self.colors.entry(piece).or_insert(c);
|
||||
}
|
||||
if let Some(o) = self.opacity.get(&item).copied() {
|
||||
self.opacity.entry(piece).or_insert(o);
|
||||
}
|
||||
}
|
||||
out.push(self.plan.len());
|
||||
self.plan.push(Planned { item: piece, units: u, batch: id });
|
||||
*self.uses.entry(piece).or_default() += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
"MAPPED_ITEM" => {
|
||||
|
||||
@@ -570,6 +570,46 @@ fn product(pd: u64, name: &str) -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// A surface model of a closed shell and an open one beside it (Poppy Humanoid's thighs: the
|
||||
/// part, and a sheet of two faces on it) comes in as two bodies: the closed shell a solid of its
|
||||
/// own, the open one a surface body, each in the model's colour. It once came in as one body,
|
||||
/// open for its sheet's edges.
|
||||
#[test]
|
||||
fn a_surface_models_closed_shell_beside_a_sheet_is_a_solid_of_its_own() {
|
||||
let mut t = String::from(HEAD);
|
||||
t += &cube_entities();
|
||||
// A unit square sheet at z = 5.
|
||||
let corners = [(0, 0), (1, 0), (1, 1), (0, 1)];
|
||||
for (i, (x, y)) in corners.iter().enumerate() {
|
||||
t += &format!("#{}=CARTESIAN_POINT('',({x}.,{y}.,5.));\n#{}=VERTEX_POINT('',#{});\n", 800 + i, 810 + i, 800 + i);
|
||||
}
|
||||
let mut oes = Vec::new();
|
||||
for i in 0..4 {
|
||||
let (a, b) = (corners[i], corners[(i + 1) % 4]);
|
||||
t += &format!("#{}=DIRECTION('',({}.,{}.,0.));\n#{}=VECTOR('',#{},1.);\n", 820 + 2 * i, b.0 - a.0, b.1 - a.1, 821 + 2 * i, 820 + 2 * i);
|
||||
t += &format!("#{}=LINE('',#{},#{});\n", 830 + i, 800 + i, 821 + 2 * i);
|
||||
t += &format!("#{}=EDGE_CURVE('',#{},#{},#{},.T.);\n", 840 + i, 810 + i, 810 + (i + 1) % 4, 830 + i);
|
||||
t += &format!("#{}=ORIENTED_EDGE('',*,*,#{},.T.);\n", 850 + i, 840 + i);
|
||||
oes.push(format!("#{}", 850 + i));
|
||||
}
|
||||
t += &format!("#860=EDGE_LOOP('',({}));\n#861=FACE_OUTER_BOUND('',#860,.T.);\n", oes.join(","));
|
||||
t += "#862=CARTESIAN_POINT('',(0.,0.,5.));\n#863=DIRECTION('',(0.,0.,1.));\n#864=AXIS2_PLACEMENT_3D('',#862,#863,$);\n#865=PLANE('',#864);\n";
|
||||
t += "#866=ADVANCED_FACE('',(#861),#865,.T.);\n#130=OPEN_SHELL('',(#866));\n#110=SHELL_BASED_SURFACE_MODEL('Thigh',(#100,#130));\n";
|
||||
t += &context(20, "(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.))");
|
||||
t += "#60=MANIFOLD_SURFACE_SHAPE_REPRESENTATION('',(#110),#20);\n";
|
||||
t += "#70=STYLED_ITEM('',(#71),#110);\n#71=PRESENTATION_STYLE_ASSIGNMENT((#72));\n#72=SURFACE_STYLE_USAGE(.BOTH.,#73);\n#73=SURFACE_SIDE_STYLE('',(#74));\n#74=SURFACE_STYLE_FILL_AREA(#75);\n#75=FILL_AREA_STYLE('',(#76));\n#76=FILL_AREA_STYLE_COLOUR('',#77);\n#77=COLOUR_RGB('',1.,0.5,0.);\n";
|
||||
t += TAIL;
|
||||
let imp = step_import(&t).unwrap();
|
||||
assert_eq!(imp.bodies.len(), 2, "{:?}", imp.warnings);
|
||||
let (solid, sheet) = (&imp.bodies[0], &imp.bodies[1]);
|
||||
assert!(solid.closed && solid.refused.is_none() && !solid.body.is_surface(), "{:?}", imp.warnings);
|
||||
assert_eq!((solid.source_item, solid.file_faces), (100, 6));
|
||||
assert!(rel(measure(&solid.body).unwrap().volume, 1.0) < 1e-9);
|
||||
assert!(!sheet.closed && sheet.body.is_surface() && sheet.body.face_count() == 1);
|
||||
assert_eq!((sheet.source_item, sheet.name.as_str()), (110, "Thigh"));
|
||||
assert!(imp.bodies.iter().all(|b| b.color == Some([1.0, 0.5, 0.0])));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hand_written_part_in_inches_with_colour() {
|
||||
let mut t = String::from(HEAD);
|
||||
|
||||
@@ -248,9 +248,24 @@ pub fn read_solids_with(ex: &Exchange, trims: &dyn Trims) -> Solids {
|
||||
}
|
||||
|
||||
fn read_solids_in(ex: &Exchange, trims: Option<&dyn Trims>) -> Solids {
|
||||
let mut items: Vec<u64> = ex.entities.iter().filter(|(_, e)| SOLID_ITEMS.contains(&e.name())).map(|(id, _)| *id).collect();
|
||||
items.sort_unstable();
|
||||
let index = Index::new(ex);
|
||||
let mut solids: Vec<u64> = ex.entities.iter().filter(|(_, e)| SOLID_ITEMS.contains(&e.name())).map(|(id, _)| *id).collect();
|
||||
solids.sort_unstable();
|
||||
let mut index = Index::new(ex);
|
||||
// A surface model's closed shells read on their own sit where the model does.
|
||||
let mut items = Vec::with_capacity(solids.len());
|
||||
for solid in solids {
|
||||
for piece in self::items(ex, solid) {
|
||||
if piece != solid {
|
||||
if let Some(c) = index.context.get(&solid).copied() {
|
||||
index.context.insert(piece, c);
|
||||
}
|
||||
if let Some(p) = index.product.get(&solid).cloned() {
|
||||
index.product.insert(piece, p);
|
||||
}
|
||||
}
|
||||
items.push(piece);
|
||||
}
|
||||
}
|
||||
let mut out = Solids::default();
|
||||
let mut budget = MAX_FILE_FACES;
|
||||
let mut readers: HashMap<u64, (Reader<'_>, units::Context)> = HashMap::new();
|
||||
@@ -334,7 +349,7 @@ fn read_with<'a>(
|
||||
let mut b = Builder::new(ex, reader, tolerance(&context), budget, trims);
|
||||
// The file's faces, edges, vertices and their geometry read into the builder.
|
||||
let topology = solidcraft_profile::span("import", "step.topology");
|
||||
let (ids, kind) = item_shells(e)?;
|
||||
let (ids, kind) = item_shells(ex, item)?;
|
||||
let mut shells = Vec::with_capacity(ids.len());
|
||||
let mut skipped = false;
|
||||
for s in ids {
|
||||
@@ -362,7 +377,7 @@ pub struct FaceRead {
|
||||
/// as [`read_solid_with`] reads it, with its own edges and vertices (so its tolerances cover its
|
||||
/// own gaps only); a face that cannot be read on its own says why and the others are still read.
|
||||
pub fn read_faces_with(ex: &Exchange, item: u64, context: units::Context, trims: &dyn Trims) -> R<Vec<FaceRead>> {
|
||||
let (shells, _) = item_shells(entity(ex, item)?)?;
|
||||
let (shells, _) = item_shells(ex, item)?;
|
||||
let mut reader = Reader::new(ex, context.units);
|
||||
let mut budget = MAX_FILE_FACES;
|
||||
let mut out = Vec::new();
|
||||
@@ -384,8 +399,31 @@ pub fn read_faces_with(ex: &Exchange, item: u64, context: units::Context, trims:
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// A surface model's shells the file calls closed and the others, in its order.
|
||||
fn model_shells(ex: &Exchange, shells: &[u64]) -> (Vec<u64>, Vec<u64>) {
|
||||
shells.iter().partition(|s| ex.get(**s).is_some_and(|e| matches!(e.name(), "CLOSED_SHELL" | "ORIENTED_CLOSED_SHELL")))
|
||||
}
|
||||
|
||||
/// The items a solid item is read as, each one body: the item itself, but for a surface model
|
||||
/// with both closed and open shells (a part with a sheet beside it) each closed shell on its own
|
||||
/// (a solid), then the model (its open shells, a sheet).
|
||||
pub fn items(ex: &Exchange, item: u64) -> Vec<u64> {
|
||||
let shells = match ex.get(item) {
|
||||
Some(e) if e.name() == "SHELL_BASED_SURFACE_MODEL" => {
|
||||
e.params().get(1).and_then(Param::as_list).unwrap_or(&[]).iter().filter_map(Param::as_ref_id).collect()
|
||||
}
|
||||
_ => Vec::new(),
|
||||
};
|
||||
let (closed, open) = model_shells(ex, &shells);
|
||||
if closed.is_empty() || open.is_empty() {
|
||||
return vec![item];
|
||||
}
|
||||
closed.into_iter().chain([item]).collect()
|
||||
}
|
||||
|
||||
/// The shells of a solid item (the outer shell first, then its voids), and what kind it is.
|
||||
fn item_shells(e: &Entity) -> R<(Vec<u64>, Kind)> {
|
||||
fn item_shells(ex: &Exchange, item: u64) -> R<(Vec<u64>, Kind)> {
|
||||
let e = entity(ex, item)?;
|
||||
let p = e.params();
|
||||
match e.name() {
|
||||
"MANIFOLD_SOLID_BREP" => Ok((vec![rid(p.get(1), "outer shell")?], Kind::Solid)),
|
||||
@@ -403,8 +441,12 @@ fn item_shells(e: &Entity) -> R<(Vec<u64>, Kind)> {
|
||||
if ids.is_empty() || ids.len() > brep::MAX_SHELLS {
|
||||
return Err(format!("a surface model with {} shells", ids.len()));
|
||||
}
|
||||
Ok((ids, Kind::Model))
|
||||
// Its closed shells, where it has open ones too, are items of their own ([`items`]).
|
||||
let (closed, open) = model_shells(ex, &ids);
|
||||
Ok((if closed.is_empty() || open.is_empty() { ids } else { open }, Kind::Model))
|
||||
}
|
||||
// A closed shell of a surface model that has open shells too ([`items`]).
|
||||
"CLOSED_SHELL" | "ORIENTED_CLOSED_SHELL" => Ok((vec![item], Kind::Model)),
|
||||
n => Err(unsupported(n)),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user