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 <noreply@anthropic.com>
This commit is contained in:
1 parent
4823c293dd
commit
b156b5e09c
5 files changed
+64
-3
No files matched your search
@@ -16,7 +16,7 @@ use crate::params::{bad, str_, vec3};
|
|||||||
use crate::{EngineError, Result, Session};
|
use crate::{EngineError, Result, Session};
|
||||||
|
|
||||||
pub static COMMANDS: &[CommandSpec] = &[
|
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; \
|
"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]",
|
name? (default: the design's name); parent?: component id or name (default: the active one); translate?: [x,y,z]",
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -679,6 +679,21 @@ impl SolveApp {
|
|||||||
self.pick_open_command(id, json!({}));
|
self.pick_open_command(id, json!({}));
|
||||||
return;
|
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") {
|
if matches!(id, "file.insert_step" | "file.insert_mesh") {
|
||||||
self.pick_open(|app, path| {
|
self.pick_open(|app, path| {
|
||||||
if let Some(path) = path {
|
if let Some(path) = path {
|
||||||
@@ -1419,3 +1434,10 @@ pub fn now_ms() -> f64 {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod picker_test_support;
|
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")
|
||||||
|
}
|
||||||
@@ -390,6 +390,10 @@ fn file_menu(app: &mut SolveApp, ctx: &egui::Context) {
|
|||||||
app.start("file.insert_mesh");
|
app.start("file.insert_mesh");
|
||||||
close = true;
|
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) {
|
if item_if(ui, app.tr("ui.save"), "Ctrl+S", shown) {
|
||||||
save(app);
|
save(app);
|
||||||
close = true;
|
close = true;
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const ASSEMBLE: &[&str] = &[
|
|||||||
"component.create",
|
"component.create",
|
||||||
"joint.create",
|
"joint.create",
|
||||||
"joint.as_built",
|
"joint.as_built",
|
||||||
|
"component.insert_design",
|
||||||
"joint.origin",
|
"joint.origin",
|
||||||
"joint.rigid_group",
|
"joint.rigid_group",
|
||||||
"joint.drive",
|
"joint.drive",
|
||||||
@@ -212,4 +213,36 @@ mod tests {
|
|||||||
assert!(ids("PLASTIC", "MODIFY").contains(&"solid.shell"));
|
assert!(ids("PLASTIC", "MODIFY").contains(&"solid.shell"));
|
||||||
assert!(panels("PLASTIC").is_some_and(|p| p.first() == Some(&"SETUP")));
|
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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -64,8 +64,10 @@ instead, so two origins given at one point with the same z stay as they are. For
|
|||||||
drive many steps, `quiet: true` leaves the per-part degrees of freedom out of joint answers, and
|
drive many steps, `quiet: true` leaves the per-part degrees of freedom out of joint answers, and
|
||||||
`motion.play` with `quiet: true` gives the poses of only the parts that moved.
|
`motion.play` with `quiet: true` gives the poses of only the parts that moved.
|
||||||
|
|
||||||
**Insert Design** (`component.insert_design {path, translate?}`) adds a component linked to
|
**Insert Design** (`component.insert_design {path, translate?}`, on the **ASSEMBLE** panel and
|
||||||
another saved design, so each part can live in its own document. The part's solid bodies come
|
in the **File** menu) adds a component linked to another saved design, so each part can live in
|
||||||
|
its own document. A STEP or mesh file picked there is inserted as **Insert STEP** or **Insert
|
||||||
|
Mesh** would. The part's solid bodies come
|
||||||
in as they sit in that design, and they are kept in the assembly, so it opens even without the
|
in as they sit in that design, and they are kept in the assembly, so it opens even without the
|
||||||
part file. The link is stored relative to the assembly's folder once the assembly is saved.
|
part file. The link is stored relative to the assembly's folder once the assembly is saved.
|
||||||
After the part file changes, **Update Links** (`component.update_links`) reads it again.
|
After the part file changes, **Update Links** (`component.update_links`) reads it again.
|
||||||
|
|||||||
Reference in new issue
Block a user