STEP assemblies: mesh a repeated part once and place it for its instances (#1152)
The reader converts each item once, but every instance made its own display mesh (tessellation, seam and tangent flags), the most expensive step of opening a large assembly. A later instance of an item now shows its first instance's mesh, placed by the difference of the two placements. Only rigid placements share; split lines and any operation drop the link. Measurements and export read each instance's own geometry, so eval output is unchanged. Generated assemblies: mesh phase 2,215 -> 139 ms (901 bodies) and 1,028 -> 85 ms (5,401 bodies), with the same triangle counts. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
764fcaa5a8
commit
9b0f028f13
8 files changed
+135
No files matched your search
@@ -248,6 +248,9 @@ struct Reader<'a> {
|
||||
/// Items the file converts exactly once (see `index`): keeping their solids would hold a
|
||||
/// second copy of the whole import for nothing.
|
||||
once: HashSet<u64>,
|
||||
/// An item placed more than once: its first instance's display, shared by the later ones
|
||||
/// (with the placement that first instance had), so the part is meshed once.
|
||||
shown: HashMap<u64, (Arc<crate::body::SharedDisplay>, mt::Matrix4)>,
|
||||
used_items: HashSet<u64>,
|
||||
/// Items already instanced for the product being read.
|
||||
node_items: HashSet<u64>,
|
||||
@@ -280,6 +283,7 @@ impl<'a> Reader<'a> {
|
||||
opacity: HashMap::new(),
|
||||
solids: HashMap::new(),
|
||||
once: HashSet::new(),
|
||||
shown: HashMap::new(),
|
||||
used_items: HashSet::new(),
|
||||
node_items: HashSet::new(),
|
||||
visits: 0,
|
||||
@@ -680,6 +684,16 @@ impl<'a> Reader<'a> {
|
||||
.map(|face| Some(crate::FaceSource::Imported { item, face: *face, payload_sha256: None, producer: None }))
|
||||
.collect();
|
||||
let body = body.with_face_sources(Some(std::sync::Arc::new(identities)));
|
||||
// A later instance of an item shows its first instance's mesh, placed.
|
||||
let body = match self.shown.get(&item) {
|
||||
Some((shared, first)) => body.with_display_of(shared.clone(), to_array(&mat_mul(m, &rigid_inverse(first)))),
|
||||
None => {
|
||||
if !self.once.contains(&item) {
|
||||
self.shown.insert(item, (Arc::new(crate::body::SharedDisplay::new(body.clone())), *m));
|
||||
}
|
||||
body
|
||||
}
|
||||
};
|
||||
self.bodies.push(ImportedBody {
|
||||
name,
|
||||
body,
|
||||
|
||||
@@ -523,6 +523,31 @@ fn hand_written_assembly() {
|
||||
assert!((root.children[1].transform[3][0] - 100.0).abs() < 1e-9);
|
||||
}
|
||||
|
||||
/// A part an assembly places twice is meshed once: the second instance shows the first one's
|
||||
/// display mesh, placed (the same triangles and faces, at its own place), and keeps its own
|
||||
/// placed geometry for everything else.
|
||||
#[test]
|
||||
fn later_instances_show_the_first_instances_mesh_placed() {
|
||||
let imp = step_import(&two_instance_assembly()).unwrap();
|
||||
let (first, second) = (&imp.bodies[0].body, &imp.bodies[1].body);
|
||||
assert!(first.display_of.is_none() && second.display_of.is_some());
|
||||
let a = first.reference_display_mesh_angular(0.01, crate::DISPLAY_ANGLE).unwrap();
|
||||
let b = second.reference_display_mesh_angular(0.01, crate::DISPLAY_ANGLE).unwrap();
|
||||
assert_eq!(a.triangles, b.triangles);
|
||||
assert_eq!(a.tri_face, b.tri_face);
|
||||
assert_eq!(a.edges.len(), b.edges.len());
|
||||
// Turned 90 degrees about Z and moved 100 mm along X.
|
||||
for (p, q) in a.positions.iter().zip(&b.positions) {
|
||||
assert!(q.dist(Vec3::new(100.0 - p.y, p.x, p.z)) < 1e-9, "{p:?} -> {q:?}");
|
||||
}
|
||||
let own = measure(second).unwrap().bbox;
|
||||
let shown = b.bounds();
|
||||
assert!(shown.min.dist(own.min) < 1e-6 && shown.max.dist(own.max) < 1e-6, "{shown:?} vs {own:?}");
|
||||
// Anything that changes what the body shows drops the shared mesh.
|
||||
let split = second.clone().with_split_lines(Some(std::sync::Arc::new(vec![vec![Vec3::new(91.0, 0.0, 5.0), Vec3::new(91.0, 10.0, 5.0)]])));
|
||||
assert!(split.display_of.is_none());
|
||||
}
|
||||
|
||||
/// The import keeps a conversion only where the file can ask for the item again. A flat file of
|
||||
/// separate solids converts each item exactly once (no second copy of the whole import is held);
|
||||
/// an assembly that instances one item keeps it, and each instance still gets geometry of its
|
||||
|
||||
Reference in new issue
Block a user