Insert Design on the ASSEMBLE panel and in the File menu (#1240) (#1405)

component.insert_design (a saved part as a linked component) had no button, so an
assembly of separately saved parts looked impossible. It is now on the ASSEMBLE panel
of every tab and in the File menu; it asks for the file, and a STEP or mesh file picked
there is inserted as Insert STEP or Insert Mesh would.

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
bherbruckandClaude Opus 5.5 authored and GitHub committed 2026-10-15 10:28:02 -04:00
1 parent 4823c293dd
commit b156b5e09c
5 files changed
+64 -3

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ use crate::params::{bad, str_, vec3};
use crate::{EngineError, Result, Session};
pub static COMMANDS: &[CommandSpec] = &[
CommandSpec::new("component.insert_design", "Insert Design", insert_design).icon("component").params(
CommandSpec::new("component.insert_design", "Insert Design", insert_design).at("SOLID", "ASSEMBLE").icon("component").params(
"path: a .solidcraft design → a new component linked to it, holding its solid bodies as placed in that design; \
name? (default: the design's name); parent?: component id or name (default: the active one); translate?: [x,y,z]",
),
+22
View File
@@ -679,6 +679,21 @@ impl SolveApp {
self.pick_open_command(id, json!({}));
return;
}
if id == "component.insert_design" {
// Another design becomes a linked component; a STEP or mesh file picked here is
// inserted the way Insert STEP or Insert Mesh would (#1240).
self.pick_open(|app, path| {
let Some(path) = path else { return };
if is_design_path(&path) {
if app.run("component.insert_design", json!({ "path": path })).is_ok() {
app.fit_view();
}
} else {
app.insert_path_async(&path);
}
});
return;
}
if matches!(id, "file.insert_step" | "file.insert_mesh") {
self.pick_open(|app, path| {
if let Some(path) = path {
@@ -1419,3 +1434,10 @@ pub fn now_ms() -> f64 {
#[cfg(test)]
mod picker_test_support;
/// A SolidCraft design file (`.solidcraft`, or a design saved as JSON), as opposed to a STEP or
/// mesh file.
fn is_design_path(path: &str) -> bool {
let ext = std::path::Path::new(path).extension().and_then(|e| e.to_str()).unwrap_or("").to_ascii_lowercase();
matches!(ext.as_str(), "solidcraft" | "solvecraft" | "json")
}
+4
View File
@@ -390,6 +390,10 @@ fn file_menu(app: &mut SolveApp, ctx: &egui::Context) {
app.start("file.insert_mesh");
close = true;
}
if item_if(ui, &format!("{}…", app.tr_label("Insert Design")), "", shown) {
app.start("component.insert_design");
close = true;
}
if item_if(ui, app.tr("ui.save"), "Ctrl+S", shown) {
save(app);
close = true;
+33
View File
@@ -11,6 +11,7 @@ const ASSEMBLE: &[&str] = &[
"component.create",
"joint.create",
"joint.as_built",
"component.insert_design",
"joint.origin",
"joint.rigid_group",
"joint.drive",
@@ -212,4 +213,36 @@ mod tests {
assert!(ids("PLASTIC", "MODIFY").contains(&"solid.shell"));
assert!(panels("PLASTIC").is_some_and(|p| p.first() == Some(&"SETUP")));
}
/// #1240: a saved part reaches an assembly from the ASSEMBLE panel (and the File menu) as a
/// linked component, and a STEP file picked there is inserted as Insert STEP would.
#[test]
fn insert_design_is_on_the_assemble_panel_and_inserts_a_saved_part() {
use serde_json::json;
for tab in ["SOLID", "SHEET METAL", "PLASTIC"] {
assert!(ids(tab, "ASSEMBLE").contains(&"component.insert_design"), "{tab}");
}
assert!(!ids("SOLID", "ASSEMBLE")[..3].contains(&"component.insert_design"), "Component, Joint and As-built Joint keep the big buttons");
let mut h = crate::scenario::Harness::new();
let part = h.dir.join("part1.solidcraft");
let mut s = solidcraft_engine::Session::default();
s.execute("solid.box", &json!({"length": 10, "width": 20, "height": 30})).unwrap();
s.execute("file.save", &json!({"path": part})).unwrap();
s.execute("file.export", &json!({"path": h.dir.join("part2.step")})).unwrap();
h.step(&json!({"choose_file": "part1.solidcraft"})).unwrap();
h.step(&json!({"start": "component.insert_design"})).unwrap();
h.frames(3);
let links = h.app.session.execute("component.links", &json!({})).unwrap();
assert_eq!(links["links"][0]["name"], "part1", "{links}");
assert_eq!(h.app.session.world_state().bodies.len(), 1);
h.step(&json!({"choose_file": "part2.step"})).unwrap();
h.step(&json!({"start": "component.insert_design"})).unwrap();
for _ in 0..600 {
if h.app.session.world_state().bodies.len() == 2 {
break;
}
h.frames(1);
}
assert_eq!(h.app.session.world_state().bodies.len(), 2, "the STEP file's body is inserted");
}
}