forked from 1b-agent/opencadstudio-upstream
feat(io): preserve source DWG version for files with version-locked data
Civil3D/AEC objects (and raw MLEADER/Surface records, EED) can only be written losslessly in their source DWG version. On save, fall back to the source version when the chosen version is an incompatible encoding family (acadrust CadDocument::has_version_locked_data), with a command-line notice, across every save path (Save, Save As, save-before-close/quit) via a shared dwg_save_version helper. Default the Save-As format dropdown to the loaded file's version so a round-trip preserves it. Picks up acadrust DWG-writer conformance fixes (3dbae05) so a Civil3D R2004 drawing round-trips through OCS and opens in AutoCAD with no errors. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
12f4264183
commit
d1efa4ea7c
4 files changed
+92
-4
No files matched your search
Generated
+2
-2
@@ -71,7 +71,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#ad507470661689a85a999166daf7f22ee507e63c"
|
||||
source = "git+https://github.com/HakanSeven12/acadrust?branch=main#3dbae0556208221822257ead37a8d445dcc7d768"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
@@ -3410,7 +3410,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b577e2d69827c4740cba2b52efaad1c4cc7c73042860b199710b3575c68438d"
|
||||
dependencies = [
|
||||
"bytecount",
|
||||
"memchr 1.0.2",
|
||||
"memchr 2.8.2",
|
||||
"nom 8.0.0",
|
||||
]
|
||||
|
||||
|
||||
@@ -23,6 +23,20 @@ use iced::{mouse, Point, Task};
|
||||
|
||||
impl OpenCADStudio {
|
||||
pub(in crate::app) fn open_save_dialog_window(&mut self, tab_idx: usize) -> Task<Message> {
|
||||
// Default the format dropdown to the loaded file's version so a
|
||||
// round-trip preserves it (e.g. an R2004 drawing offers "DWG 2004",
|
||||
// not "DWG 2018"). The save still falls back to the source version if
|
||||
// the user picks an incompatible one (dwg_save_version).
|
||||
let is_dxf = self.tabs[tab_idx]
|
||||
.current_path
|
||||
.as_ref()
|
||||
.and_then(|p| p.extension())
|
||||
.and_then(|e| e.to_str())
|
||||
.map(|e| e.eq_ignore_ascii_case("dxf"))
|
||||
.unwrap_or(false);
|
||||
let doc_version = self.tabs[tab_idx].scene.document.version;
|
||||
self.save_dialog_format = crate::io::format_for_version(doc_version, is_dxf);
|
||||
|
||||
// Pre-fill filename and folder from current path or defaults.
|
||||
if let Some(p) = &self.tabs[tab_idx].current_path.clone() {
|
||||
if let Some(name) = p.file_name() {
|
||||
@@ -244,9 +258,11 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
}
|
||||
|
||||
pub(super) fn on_unsaved_picked_save_path_some(&mut self, path: std::path::PathBuf) -> Task<Message> {
|
||||
let (_, version) = crate::io::parse_save_format(&self.save_dialog_format);
|
||||
let (ext, version) = crate::io::parse_save_format(&self.save_dialog_format);
|
||||
match self.pending_close.take() {
|
||||
Some(crate::app::PendingClose::Tab(idx)) => {
|
||||
// Fall back to source version for version-locked data.
|
||||
let version = self.dwg_save_version(idx, ext, version);
|
||||
match crate::io::save_as_version(
|
||||
&self.tabs[idx].scene.document,
|
||||
&path,
|
||||
@@ -268,6 +284,8 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven
|
||||
}
|
||||
Some(crate::app::PendingClose::Quit) => {
|
||||
let i = self.active_tab;
|
||||
// Fall back to source version for version-locked data.
|
||||
let version = self.dwg_save_version(i, ext, version);
|
||||
match crate::io::save_as_version(
|
||||
&self.tabs[i].scene.document,
|
||||
&path,
|
||||
|
||||
+52
-1
@@ -518,6 +518,43 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
Task::none()
|
||||
}
|
||||
|
||||
/// Resolve the effective DWG save version for `tab`.
|
||||
///
|
||||
/// A drawing that carries data locked to its source DWG version (e.g.
|
||||
/// AEC/Civil3D objects with no published spec, raw MLEADER/Surface records,
|
||||
/// EED) can only be written losslessly in that version. When the requested
|
||||
/// version is in a different encoding family, fall back to the source
|
||||
/// version so nothing is dropped and the file opens cleanly elsewhere, and
|
||||
/// note it on the command line. Non-DWG / convertible saves are unchanged.
|
||||
/// Shared by every save path (Save, Save As, save-before-close).
|
||||
pub(crate) fn dwg_save_version(
|
||||
&mut self,
|
||||
tab: usize,
|
||||
ext: &str,
|
||||
requested: acadrust::DxfVersion,
|
||||
) -> acadrust::DxfVersion {
|
||||
if ext != "dwg" {
|
||||
return requested;
|
||||
}
|
||||
let fallback = {
|
||||
let doc = &self.tabs[tab].scene.document;
|
||||
match doc.dwg_source_version {
|
||||
Some(src) if src != requested && doc.has_version_locked_data(requested) => Some(src),
|
||||
_ => None,
|
||||
}
|
||||
};
|
||||
match fallback {
|
||||
Some(src) => {
|
||||
self.command_line.push_output(&format!(
|
||||
"Saved in source DWG version ({src:?}) to preserve objects that cannot be \
|
||||
converted to the selected version."
|
||||
));
|
||||
src
|
||||
}
|
||||
None => requested,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn on_save_file(&mut self) -> Task<Message> {
|
||||
if self.read_only {
|
||||
self.command_line
|
||||
@@ -535,7 +572,17 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
if let Some(path) = self.tabs[i].current_path.clone() {
|
||||
self.tabs[i].scene.document.header.user_real1 =
|
||||
self.tabs[i].scene.annotation_scale as f64;
|
||||
match crate::io::save(&self.tabs[i].scene.document, &path) {
|
||||
// Preserve the document's version, but fall back to the
|
||||
// source version if it carries version-locked data (so a
|
||||
// direct Save can't silently drop AEC/Civil3D objects).
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or("")
|
||||
.to_ascii_lowercase();
|
||||
let requested = self.tabs[i].scene.document.version;
|
||||
let version = self.dwg_save_version(i, &ext, requested);
|
||||
match crate::io::save_as_version(&self.tabs[i].scene.document, &path, version) {
|
||||
Ok(()) => {
|
||||
self.command_line
|
||||
.push_output(&format!("Saved: {}", path.display()));
|
||||
@@ -567,6 +614,10 @@ pub(super) fn on_open_file(&mut self) -> Task<Message> {
|
||||
let i = self.active_tab;
|
||||
sync_annotation_scale_header(&mut self.tabs[i].scene);
|
||||
|
||||
// Fall back to the source version for files with version-locked
|
||||
// data (e.g. AEC/Civil3D objects) so nothing is dropped.
|
||||
let version = self.dwg_save_version(i, ext, version);
|
||||
|
||||
// Native: write to the chosen path. Web: download the bytes
|
||||
// under the chosen name (no filesystem).
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -423,6 +423,25 @@ pub fn parse_save_format(format: &str) -> (&'static str, acadrust::DxfVersion) {
|
||||
(ext, version)
|
||||
}
|
||||
|
||||
/// Reverse of [`parse_save_format`]: the Save-dialog format string for a
|
||||
/// version + DXF/DWG choice (e.g. `AC1018, is_dxf=false` -> `"DWG 2004"`).
|
||||
/// Used to default the Save-As dropdown to the loaded file's version so a
|
||||
/// round-trip preserves it instead of silently offering "DWG 2018".
|
||||
pub fn format_for_version(version: acadrust::DxfVersion, is_dxf: bool) -> String {
|
||||
use acadrust::DxfVersion::*;
|
||||
let year = match version {
|
||||
AC1032 => "2018",
|
||||
AC1027 => "2013",
|
||||
AC1024 => "2010",
|
||||
AC1021 => "2007",
|
||||
AC1018 => "2004",
|
||||
AC1015 => "2000",
|
||||
AC1014 => "R14",
|
||||
_ => "2018",
|
||||
};
|
||||
format!("{} {}", if is_dxf { "DXF" } else { "DWG" }, year)
|
||||
}
|
||||
|
||||
// ── Plot Style Table ──────────────────────────────────────────────────────
|
||||
|
||||
/// Show a file-open dialog and load the selected CTB or STB file.
|
||||
|
||||
Reference in new issue
Block a user