STEP import takes up for Y-up files and names for the bodies' names (#1312)
file.insert_step and doc.open on a STEP file take `up` (turn a Y-up file so its up is +Z) and `names` (names for the imported bodies). The MCP tool and the app's control requests pass them through; an import without them works as before. Tests in the kernel's STEP reader, the engine, MCP and the file-load UI; docs/step-import.md and docs/mcp.md describe both.
This commit is contained in:
1 parent
81d9b7e04a
commit
0ee5e97db5
12 files changed
+572
-38
No files matched your search
@@ -39,20 +39,29 @@ pub struct Request {
|
||||
pub reply: Option<Sender<ControlResponse>>,
|
||||
/// The folders that caller's agent may use: the worker reads the file inside them.
|
||||
pub scope: Option<solidcraft_engine::io::vfs::PathScope>,
|
||||
/// What that caller asked of a STEP import besides its path (`up`, `names`): the command
|
||||
/// gets them as given.
|
||||
pub options: serde_json::Map<String, Value>,
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn new(path: &str, kind: Kind) -> Self {
|
||||
Request { path: path.into(), kind, reply: None, scope: None }
|
||||
Request { path: path.into(), kind, reply: None, scope: None, options: Default::default() }
|
||||
}
|
||||
fn insert(&self) -> bool {
|
||||
self.kind == Kind::Insert
|
||||
}
|
||||
/// The command's parameters: the path and the caller's import options.
|
||||
fn params(&self) -> Value {
|
||||
let mut params = self.options.clone();
|
||||
params.insert("path".into(), json!(self.path));
|
||||
Value::Object(params)
|
||||
}
|
||||
fn command(&self) -> (&'static str, Value) {
|
||||
match &self.kind {
|
||||
Kind::Open => ("doc.open", json!({"path": self.path})),
|
||||
Kind::Open => ("doc.open", self.params()),
|
||||
Kind::Insert if solidcraft_engine::io::is_mesh_path(&self.path) => ("file.insert_mesh", json!({"path": self.path})),
|
||||
Kind::Insert => ("file.insert_step", json!({"path": self.path})),
|
||||
Kind::Insert => ("file.insert_step", self.params()),
|
||||
Kind::Recover { id, dir } => ("doc.recover", json!({"id": id, "dir": dir})),
|
||||
}
|
||||
}
|
||||
@@ -185,7 +194,8 @@ pub fn control(app: &mut SolveApp, req: crate::control::ControlRequest) -> Optio
|
||||
Some("file.insert_step" | "file.insert_mesh") => Kind::Insert,
|
||||
_ => return Some(req),
|
||||
};
|
||||
let Some(path) = req.params.get("params").and_then(|p| p.get("path")).and_then(Value::as_str) else { return Some(req) };
|
||||
let params = req.params.get("params");
|
||||
let Some(path) = params.and_then(|p| p.get("path")).and_then(Value::as_str) else { return Some(req) };
|
||||
let scope = match crate::control::request_scope(app, &req.params) {
|
||||
Ok(scope) => scope,
|
||||
Err(e) => {
|
||||
@@ -193,7 +203,8 @@ pub fn control(app: &mut SolveApp, req: crate::control::ControlRequest) -> Optio
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let _ = begin_request(app, Request { path: path.into(), kind, reply: Some(req.reply.clone()), scope });
|
||||
let options = ["up", "names"].iter().filter_map(|k| Some((k.to_string(), params?.get(*k).filter(|v| !v.is_null())?.clone()))).collect();
|
||||
let _ = begin_request(app, Request { path: path.into(), kind, reply: Some(req.reply.clone()), scope, options });
|
||||
None
|
||||
}
|
||||
|
||||
@@ -550,7 +561,8 @@ pub fn tick(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
app.home.open = false;
|
||||
app.fit_view();
|
||||
if request.kind == Kind::Open || request.insert() {
|
||||
// A caller that said which way is up has been answered: no offer to stand the part up.
|
||||
if (request.kind == Kind::Open || request.insert()) && !request.options.contains_key("up") {
|
||||
crate::stand_up::offer(app, &request.path, &running.before);
|
||||
}
|
||||
let warnings = made.result.get("warnings").and_then(Value::as_array);
|
||||
|
||||
@@ -225,3 +225,49 @@ fn a_control_open_answers_after_loading_without_freezing_the_window() {
|
||||
assert_eq!(w.app.session.doc.features.len(), sync.doc.features.len() + 1, "the edit went into the opened design");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// A control-channel (MCP) import hands its `up` and `names` to the command, as the engine
|
||||
/// session takes them, and a caller that said which way is up is not offered to stand the part up.
|
||||
#[test]
|
||||
fn a_control_import_passes_up_and_names_on() {
|
||||
let dir = scratch("import-options");
|
||||
let path = dir.join("tall_in_y.step").to_string_lossy().to_string();
|
||||
let mut src = Session::default();
|
||||
src.execute("solid.box", &json!({"length": 10, "width": 40, "height": 5})).unwrap();
|
||||
src.execute("file.export", &json!({"path": path})).unwrap();
|
||||
|
||||
// The reply, the part's height and whether the offer is up, for an import by this window.
|
||||
let import = |params: serde_json::Value| {
|
||||
let mut w = Win::new();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
w.app.control_rx = Some(rx);
|
||||
let (req, reply_rx) = crate::control::ControlRequest::new("engine.execute", json!({"command": "file.insert_step", "params": params.clone()}));
|
||||
tx.send(req).unwrap();
|
||||
let mut reply = None;
|
||||
for _ in 0..100_000 {
|
||||
w.frame(vec![]);
|
||||
reply = reply_rx.try_recv().ok();
|
||||
if reply.is_some() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let reply = reply.expect("the import was answered");
|
||||
assert_eq!(reply["ok"], true, "{params}: {reply}");
|
||||
let mut sync = Session::default();
|
||||
assert_eq!(reply["result"], sync.execute("file.insert_step", ¶ms).unwrap(), "the window's reply is the session's");
|
||||
let b = solidcraft_engine::view::bounds(&w.app.session);
|
||||
(reply, [b.max.x - b.min.x, b.max.y - b.min.y, b.max.z - b.min.z], w.app.stand_up.is_some())
|
||||
};
|
||||
|
||||
// As before: the file's axes, and the offer to stand it up.
|
||||
let (_, size, offered) = import(json!({"path": path}));
|
||||
assert!(size[1] > 39.9 && offered, "{size:?} {offered}");
|
||||
// Y up said by the caller: stood up by the import, nothing left to offer.
|
||||
let (reply, size, offered) = import(json!({"path": path, "up": "y", "names": "product"}));
|
||||
assert!(size[2] > 39.9 && !offered, "{size:?} {offered}: {reply}");
|
||||
assert!(reply["result"]["stand_up"].is_u64(), "{reply}");
|
||||
// Z up said by the caller: kept as it is, and not asked about either.
|
||||
let (_, size, offered) = import(json!({"path": path, "up": "z"}));
|
||||
assert!(size[1] > 39.9 && !offered, "{size:?} {offered}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
Reference in new issue
Block a user