Agent-built designs: stop redoing whole-design work on every command (#1148)
A design built over MCP is many small commands, and each one did work over the whole design, so building or showing a 1,300-body design took minutes: - moved components were transformed and meshed again after every change; world_state now reuses each placed body whose definition and placement are unchanged - next_d_name searched the design once per candidate number - Document::eval evaluated every parameter for every expression; now only when the expression names one - param_rows' used_by was quadratic; the desktop no longer gathers completions on every change while no text box has focus solidcraft-cli bench-agent builds a generated ~1,300-body bicycle command by command and times it (241 s -> 16 s here). Eval output is unchanged apart from timings. Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
37547f8eba
commit
6de8f6db95
11 files changed
+439
-21
No files matched your search
@@ -173,6 +173,8 @@ struct WorldCache {
|
||||
active_component: u64,
|
||||
active_occurrence_path: Option<Vec<u64>>,
|
||||
world: Arc<solidcraft_doc::ModelState>,
|
||||
/// The moved components' bodies as placed for `world`, reused by the next world state.
|
||||
placed: Arc<std::sync::Mutex<solidcraft_doc::PlacedBodies>>,
|
||||
}
|
||||
|
||||
impl WorldCache {
|
||||
@@ -395,17 +397,27 @@ impl Session {
|
||||
{
|
||||
return cached.world.clone();
|
||||
}
|
||||
let mut w = if self.pending_moves.is_empty() {
|
||||
solidcraft_doc::world_state(&self.doc, &st)
|
||||
} else {
|
||||
let mut d = (*self.doc).clone();
|
||||
for o in &mut d.occurrences {
|
||||
if let Some(m) = self.pending_moves.get(&o.id) {
|
||||
o.transform = *m;
|
||||
// The bodies the last world state placed: unchanged ones are not transformed and meshed
|
||||
// again (a design built command by command changed one body at a time).
|
||||
let placed = self.world_cache.lock().ok().and_then(|c| c.as_ref().map(|c| c.placed.clone())).unwrap_or_default();
|
||||
// Taken out while placing, so a scratch copy of the session sharing it never waits.
|
||||
let mut reuse = placed.lock().map(|mut p| std::mem::take(&mut *p)).unwrap_or_default();
|
||||
let mut w = {
|
||||
if self.pending_moves.is_empty() {
|
||||
solidcraft_doc::world_state_reusing(&self.doc, &st, &mut reuse)
|
||||
} else {
|
||||
let mut d = (*self.doc).clone();
|
||||
for o in &mut d.occurrences {
|
||||
if let Some(m) = self.pending_moves.get(&o.id) {
|
||||
o.transform = *m;
|
||||
}
|
||||
}
|
||||
solidcraft_doc::world_state_reusing(&d, &st, &mut reuse)
|
||||
}
|
||||
solidcraft_doc::world_state(&d, &st)
|
||||
};
|
||||
if let Ok(mut p) = placed.lock() {
|
||||
*p = reuse;
|
||||
}
|
||||
// Sketch definitions are displayed once, at the placement currently being authored.
|
||||
if self.active_occurrence_path.is_some() && self.active_component != 0 {
|
||||
let first = self.occurrence_placement(&self.doc.first_path(self.active_component)).map(|p| p.world).unwrap_or(solidcraft_doc::IDENTITY);
|
||||
@@ -440,6 +452,7 @@ impl Session {
|
||||
active_component: self.active_component,
|
||||
active_occurrence_path: self.active_occurrence_path.clone(),
|
||||
world: w.clone(),
|
||||
placed,
|
||||
});
|
||||
}
|
||||
w
|
||||
|
||||
@@ -175,3 +175,41 @@ fn adopted_scratch_keeps_its_warm_geometry_and_history_stages_invalidate_it() {
|
||||
run(&mut session, "timeline.suppress", json!({"feature":feature,"suppressed":false}));
|
||||
assert!(session.world_state().body("Draft").is_some());
|
||||
}
|
||||
|
||||
/// A moved component's bodies keep their placed geometry and display mesh while the design
|
||||
/// changes elsewhere: each new body used to transform and mesh every body of every moved
|
||||
/// component again. A body that changes, or a placement that moves, is placed afresh.
|
||||
#[test]
|
||||
fn moved_component_bodies_are_not_placed_again_when_something_else_changes() {
|
||||
let mut session = Session::default();
|
||||
let created = run(&mut session, "component.create", json!({"name":"Wheel"}));
|
||||
let occurrence = created["occurrence"].as_u64().unwrap();
|
||||
run(&mut session, "solid.box", json!({"length":60,"width":60,"height":5,"body_name":"Rim"}));
|
||||
run(&mut session, "component.activate", json!({"component":"root"}));
|
||||
run(&mut session, "occurrence.move", json!({"occurrence":occurrence,"translate":[100,0,0]}));
|
||||
let placed = session.world_state();
|
||||
let rim = placed.bodies.iter().find(|b| b.source_name.as_deref() == Some("Rim")).unwrap().clone();
|
||||
let mesh = rim.mesh();
|
||||
assert!(mesh.bounds().min.dist(Vec3::new(100., 0., 0.)) < 1e-6);
|
||||
// A body added elsewhere: the rim is the same placed body, already meshed.
|
||||
run(&mut session, "solid.box", json!({"length":5,"width":6,"height":7,"body_name":"Frame"}));
|
||||
let after = session.world_state();
|
||||
let again = after.body(&rim.name).unwrap();
|
||||
assert!(Arc::ptr_eq(&mesh, &again.mesh()), "an unchanged placed body keeps its mesh");
|
||||
assert!(again.body.same(&rim.body));
|
||||
// The placement moves: placed and meshed again where it now is.
|
||||
run(&mut session, "occurrence.move", json!({"occurrence":occurrence,"translate":[0,50,0]}));
|
||||
let moved = session.world_state();
|
||||
let moved_rim = moved.bodies.iter().find(|b| b.source_name.as_deref() == Some("Rim")).unwrap();
|
||||
assert!(!Arc::ptr_eq(&mesh, &moved_rim.mesh()));
|
||||
assert!(moved_rim.mesh().bounds().min.dist(Vec3::new(100., 50., 0.)) < 1e-6);
|
||||
// The body itself changes: placed again too, and the old snapshot is untouched.
|
||||
run(&mut session, "body.rename", json!({"body":"Frame","name":"Frame tube"}));
|
||||
let source = session.model.state().body("Rim").unwrap().clone();
|
||||
let world = session.world_state();
|
||||
let kept = world.bodies.iter().find(|b| b.source_name.as_deref() == Some("Rim")).unwrap();
|
||||
assert!(Arc::ptr_eq(&moved_rim.mesh(), &kept.mesh()));
|
||||
assert_eq!(kept.source_transform, moved_rim.source_transform);
|
||||
assert!(source.mesh().bounds().min.dist(Vec3::ZERO) < 1e-6);
|
||||
assert!(mesh.bounds().min.dist(Vec3::new(100., 0., 0.)) < 1e-6);
|
||||
}
|
||||
Reference in new issue
Block a user