SAVEAS / QSAVE with a .svg, .png or .pdf path wrote the export and then marked the drawing saved under that file: the title became the image name, the unsaved changes were cleared (no "Save changes?" on close) and a later Save wrote the image again. Exports now only store the file; DXF/DWG saves are unchanged. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eee9de96f5
commit
ba026c484a
|
|
@ -159,10 +159,14 @@ fn store(path: &str, bytes: &[u8]) -> Result<()> {
|
|||
/// Store the bytes, then mark the drawing saved under `path`. If storing fails the drawing keeps
|
||||
/// its unsaved changes, file name and title. On the web a started download counts as saved: the
|
||||
/// browser owns the file from there (we can't see whether the user keeps it), and the name stays so
|
||||
/// a later QSAVE downloads the drawing again under it.
|
||||
/// a later QSAVE downloads the drawing again under it. An image or PDF export (SVG, PNG, PDF) only
|
||||
/// stores the file: the drawing keeps its name, title and unsaved changes, so closing still asks.
|
||||
fn commit_save(s: &mut Session, path: &str, bytes: &[u8], store: impl FnOnce(&str, &[u8]) -> Result<()>) -> Result<usize> {
|
||||
s.state()?;
|
||||
store(path, bytes)?;
|
||||
if !is_drawing_file(path) {
|
||||
return Ok(bytes.len());
|
||||
}
|
||||
let st = s.state_mut()?;
|
||||
st.saved = st.doc.clone();
|
||||
st.path = Some(path.to_string());
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
//! Saving to an image or PDF path exports the drawing; it isn't a save of the drawing (#327). The
|
||||
//! drawing keeps its name and its unsaved changes, so closing still asks and QSAVE doesn't write
|
||||
//! the image again.
|
||||
|
||||
use cadcraft_engine::Session;
|
||||
use cadcraft_engine::cmd::file::{IoHooks, set_io};
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn saveas_to_an_image_keeps_the_drawing_unsaved_under_its_name() {
|
||||
set_io(IoHooks { read: |_, _| Err("no reader in this test".into()), write: |_, _| Ok(b"0\nEOF\n".to_vec()), plot: None });
|
||||
let dir = std::env::temp_dir().join(format!("cadcraft-saveas-export-{}", std::process::id()));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let mut s = Session::new();
|
||||
s.cmdline("circle 0,0 5").unwrap();
|
||||
let title = s.state().unwrap().title.clone();
|
||||
|
||||
for ext in ["png", "svg", "pdf"] {
|
||||
let out = dir.join(format!("plate.{ext}"));
|
||||
s.execute("saveas", &json!({ "path": out.to_string_lossy() })).unwrap();
|
||||
assert!(out.exists(), "{ext}: the export is written");
|
||||
let st = s.state().unwrap();
|
||||
assert!(st.is_dirty(), "{ext}: an export must not clear the unsaved changes");
|
||||
assert_eq!((st.path.as_deref(), st.title.as_str()), (None, title.as_str()), "{ext}: the drawing keeps its name");
|
||||
}
|
||||
// QSAVE with a path is the same export.
|
||||
s.execute("qsave", &json!({ "path": dir.join("again.png").to_string_lossy() })).unwrap();
|
||||
assert!(s.state().unwrap().is_dirty());
|
||||
// Still no file name: a plain QSAVE asks for one instead of exporting the image again.
|
||||
assert!(s.execute("qsave", &json!({})).is_err());
|
||||
|
||||
// A drawing file is a save: name, title and saved state follow it.
|
||||
let dxf = dir.join("plate.dxf");
|
||||
s.execute("saveas", &json!({ "path": dxf.to_string_lossy() })).unwrap();
|
||||
let st = s.state().unwrap();
|
||||
assert!(!st.is_dirty());
|
||||
assert_eq!((st.path.as_deref(), st.title.as_str()), (Some(dxf.to_string_lossy().as_ref()), "plate.dxf"));
|
||||
// Exporting afterwards keeps that name, so QSAVE writes the DXF again.
|
||||
s.execute("saveas", &json!({ "path": dir.join("plate.png").to_string_lossy() })).unwrap();
|
||||
let r = s.execute("qsave", &json!({})).unwrap();
|
||||
assert_eq!(r["path"], json!(dxf.to_string_lossy()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ pub fn tool_definitions(has_ui: bool) -> Value {
|
|||
json!({"name": "new_drawing", "description": "Create a new drawing (imperial by default, or metric).",
|
||||
"inputSchema": {"type": "object", "properties": {"metric": {"type": "boolean"}}}}),
|
||||
json!({"name": "open", "description": "Open a DXF file.", "inputSchema": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}}),
|
||||
json!({"name": "save", "description": "Save the drawing (DXF; .svg/.png export by extension).", "inputSchema": {"type": "object", "properties": {"path": {"type": "string"}}}}),
|
||||
json!({"name": "save", "description": "Save the drawing (DXF or DWG by extension). A .svg, .png or .pdf path exports instead: the drawing keeps its name and unsaved changes.", "inputSchema": {"type": "object", "properties": {"path": {"type": "string"}}}}),
|
||||
json!({"name": "cancel", "description": "Press Escape: cancel the running command (or clear the selection).", "inputSchema": {"type": "object", "properties": {}}}),
|
||||
];
|
||||
if has_ui {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ Example client configuration (Claude Code / Claude Desktop):
|
|||
| `inspect_drawing {entities?, limit?}` | Counts, extents, layers, styles, blocks, layouts, selection, undo history and entities. |
|
||||
| `query_entities {type?, layer?, window?}` | Filtered entities with handles and geometry. |
|
||||
| `render {width?, height?, fit?}` | A PNG image of the drawing. |
|
||||
| `new_drawing {metric?}`, `open {path}`, `save {path?}`, `cancel` | Files and Escape. |
|
||||
| `new_drawing {metric?}`, `open {path}`, `save {path?}`, `cancel` | Files and Escape. `save` to a `.svg`, `.png` or `.pdf` path exports: the drawing keeps its name and unsaved changes. |
|
||||
| `screenshot`, `ui_inspect`, `ui_click {x, y}` | Only when connected to the app. |
|
||||
|
||||
Resources: `cadcraft://drawing` (inspect JSON) and `cadcraft://commands` (catalog).
|
||||
|
|
|
|||
Loading…
Reference in New Issue