From a735b10b3438e96454385dcc64fb240a5026d328 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Fri, 2 Oct 2026 12:38:49 +0300 Subject: [PATCH] feat(field): Formula, Hyperlink and plot fields; local-time field dates, file times and display name; header dates on save Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 4 +- locales/ar-SA/opencadstudio.ftl | 8 ++ locales/bg-BG/opencadstudio.ftl | 8 ++ locales/cs-CZ/opencadstudio.ftl | 8 ++ locales/de-DE/opencadstudio.ftl | 8 ++ locales/el-GR/opencadstudio.ftl | 8 ++ locales/en-US/opencadstudio.ftl | 8 ++ locales/es-ES/opencadstudio.ftl | 8 ++ locales/fi-FI/opencadstudio.ftl | 8 ++ locales/fr-FR/opencadstudio.ftl | 8 ++ locales/hi-IN/opencadstudio.ftl | 8 ++ locales/hu-HU/opencadstudio.ftl | 8 ++ locales/it-IT/opencadstudio.ftl | 8 ++ locales/ja-JP/opencadstudio.ftl | 8 ++ locales/ko-KR/opencadstudio.ftl | 8 ++ locales/nl-NL/opencadstudio.ftl | 8 ++ locales/pl-PL/opencadstudio.ftl | 8 ++ locales/pt-BR/opencadstudio.ftl | 8 ++ locales/ru-RU/opencadstudio.ftl | 8 ++ locales/tr-TR/opencadstudio.ftl | 8 ++ locales/zh-CN/opencadstudio.ftl | 8 ++ locales/zh-TW/opencadstudio.ftl | 8 ++ src/app/commands/field.rs | 22 ++++ src/app/control/actions.rs | 9 +- src/entities/field.rs | 89 +++++++++++++- src/io/mod.rs | 4 + src/locale_catalog.rs | 8 ++ src/scene/parametric_constraints.rs | 1 + src/ui/window/field_dialog.rs | 178 +++++++++++++++++++++++++++- 30 files changed, 475 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4283e181f..f4f2d81b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,7 +4206,7 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" -source = "git+https://github.com/ramox81/cadcodec.git?rev=595800e#595800ed4b778827c3c9e48245cade9cd8ccc05a" +source = "git+https://github.com/ramox81/cadcodec.git?rev=6d3713d#6d3713df1fe0a4aeeb9ef308a376e3db49c727c3" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 915fa648d..e68a8742c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ naga = { version = "27", features = ["wgsl-in"] } [target.'cfg(target_os = "windows")'.dependencies] libloading = "0.8" -windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_System_Com", "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing", "Win32_Graphics_Gdi", "Win32_Storage_Xps", "Win32_Globalization"] } +windows-sys = { version = "0.61", features = ["Win32_UI_Shell", "Win32_UI_WindowsAndMessaging", "Win32_UI_Input", "Win32_System_LibraryLoader", "Win32_System_Threading", "Win32_Security", "Win32_System_Com", "Win32_System_Registry", "Win32_Storage_FileSystem", "Win32_Foundation", "Win32_Graphics_Printing", "Win32_Graphics_Gdi", "Win32_Storage_Xps", "Win32_Globalization", "Win32_Security_Authentication_Identity"] } [target.'cfg(target_os = "linux")'.dependencies] ashpd = { version = "0.13.13", default-features = false, features = ["async-io", "wayland"] } @@ -92,7 +92,7 @@ objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString" # Field authoring API and field format codes (opencadcodec PR pending). [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "595800e" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "6d3713d" } [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index eebcbccbf..97fec3890 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4105,6 +4105,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 69e552f00..0fb1d43e9 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4092,6 +4092,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 03725d1f2..d17ecfb1d 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4090,6 +4090,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index c111960ae..7fd9f0bd4 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4091,6 +4091,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 0355311c9..53601f4be 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4093,6 +4093,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index f7d7c3a38..dcbbb7b69 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4094,6 +4094,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index c6d1a9cfa..df1b19661 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4093,6 +4093,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 59459a0d0..99f18d514 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4092,6 +4092,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 8a6b6f1df..bc00d9334 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4093,6 +4093,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index bda484c8a..59440e35e 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4081,6 +4081,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7407c89e0..2066c5156 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4092,6 +4092,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 88420cc86..bab01a81c 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4092,6 +4092,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 068528c47..f0df48710 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4081,6 +4081,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 791d86503..6ad109cc7 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4092,6 +4092,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index b8076a424..2f5e39e39 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4091,6 +4091,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 49d04e6fe..c31421220 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4092,6 +4092,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 30e2b6a98..e96dea04d 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4093,6 +4093,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index b0e1badaa..e3eb50fa6 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4094,6 +4094,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 9e11988b6..3b01d7c2c 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4053,6 +4053,14 @@ insert = .field-select-object = Nesne seç .field-expression = Alan ifadesi .field-help = Bir alan ekler: çizimden, tarihten veya başka bir kaynaktan güncellenen metin. + .field-cat-linked = Bağlantılı + .field-current-precision = Geçerli hassasiyet + .field-evaluate = Hesapla + .field-text-to-display = Görüntülenecek metin + .field-address = Adres + .field-select-link-file = Bağlanacak dosyayı seçin + .field-current-units = Geçerli birimler + .field-use-scale-name = Ölçek adını kullan .field-cat-date = Tarih ve Saat .field-cat-document = Belge .field-cat-other = Diğer diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index ab519c48a..290f64be0 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4076,6 +4076,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 3782082b2..c38cc4a04 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4088,6 +4088,14 @@ insert = .field-select-object = Select object .field-expression = Field expression .field-help = Inserts a field: text that updates from the drawing, the date or another source. + .field-cat-linked = Linked + .field-current-precision = Current precision + .field-evaluate = Evaluate + .field-text-to-display = Text to display + .field-address = Address + .field-select-link-file = Select a file to link + .field-current-units = Current units + .field-use-scale-name = Use scale name .field-cat-date = Date & Time .field-cat-document = Document .field-cat-other = Other diff --git a/src/app/commands/field.rs b/src/app/commands/field.rs index 7fd9001ee..5c084fc78 100644 --- a/src/app/commands/field.rs +++ b/src/app/commands/field.rs @@ -133,6 +133,28 @@ impl OpenCADStudio { } FieldDialogMsg::Named(k) => state.named = Some(k), FieldDialogMsg::ObjectProp(p) => state.object_prop = Some(p), + FieldDialogMsg::Formula(v) => state.formula = v, + FieldDialogMsg::FormulaFormat(k) => state.formula_format = k, + FieldDialogMsg::FormulaPrecision(k) => state.formula_precision = k, + FieldDialogMsg::Evaluate => {} + FieldDialogMsg::HyperlinkText(v) => state.hyperlink_text = v, + FieldDialogMsg::HyperlinkUrl(v) => state.hyperlink_url = v, + FieldDialogMsg::BrowseHyperlink => { + return Task::perform( + async { + rfd::AsyncFileDialog::new() + .set_title(crate::t!("Select a file to link").as_ref()) + .pick_file() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |path| match path { + Some(path) => Message::FieldDialog(FieldDialogMsg::HyperlinkUrl(path.to_string_lossy().into_owned())), + None => Message::Noop, + }, + ); + } + FieldDialogMsg::PlotScale(k) => state.plot_scale = k, FieldDialogMsg::SelectObject => { // The dialog waits while one object is picked. self.active_modal = None; diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index eeeabb515..dab7a6c47 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -981,7 +981,8 @@ impl OpenCADStudio { // One field of the open Field dialog: `category=`, `name=Date`, // `date=yyyy-MM-dd`, `case=0..4`, `file=1|2|3`, `ext=0|1`, `size=0..2`, // `sysvar=dimscale`, `diesel=…`, `named_type=`, `named=`, - // `prop=Area`, `select_object`. + // `prop=Area`, `select_object`, `formula=(1+2)*3`, `formula_format=`, + // `precision=`, `link_text=…`, `link_url=…`, `plot_scale=`. "field_dialog" => { use crate::ui::window::field_dialog::{self as fd, FieldDialogMsg as F}; let value = string(req, "value")?; @@ -1004,6 +1005,12 @@ impl OpenCADStudio { "named" => F::Named(index()?), "prop" => F::ObjectProp(known(&["Area", "Center", "Circumference", "Diameter", "EndPoint", "Length", "Radius", "StartPoint"])?), "select_object" => F::SelectObject, + "formula" => F::Formula(v.into()), + "formula_format" => F::FormulaFormat(index()?), + "precision" => F::FormulaPrecision(index()?), + "link_text" => F::HyperlinkText(v.into()), + "link_url" => F::HyperlinkUrl(v.into()), + "plot_scale" => F::PlotScale(index()?), _ => return Err(failure("invalid_value", "Unknown field dialog key")), }) } diff --git a/src/entities/field.rs b/src/entities/field.rs index d4e06af1a..c05b5ccb3 100644 --- a/src/entities/field.rs +++ b/src/entities/field.rs @@ -17,11 +17,29 @@ struct OcsFieldContext<'a>(Option<&'a CadDocument>); impl FieldContext for OcsFieldContext<'_> { fn now_julian(&self) -> f64 { - // Unix epoch is Julian Day 2440587.5. - epoch_secs() as f64 / 86_400.0 + 2_440_587.5 + // Fields show local time. Unix epoch is Julian Day 2440587.5. + now_utc_julian() + utc_offset_days() + } + + fn file_times(&self) -> Option<(f64, f64)> { + #[cfg(not(target_arch = "wasm32"))] + { + let meta = std::fs::metadata(self.0?.source_path.as_deref()?).ok()?; + let julian = |t: std::time::SystemTime| -> Option { + let secs = t.duration_since(std::time::UNIX_EPOCH).ok()?.as_secs_f64(); + Some(secs / 86_400.0 + 2_440_587.5 + utc_offset_days()) + }; + return Some((julian(meta.created().ok()?)?, julian(meta.modified().ok()?)?)); + } + #[cfg(target_arch = "wasm32")] + None } fn login(&self) -> Option { + // The account's display name, as the reference shows it. + if let Some(name) = display_name() { + return Some(name); + } #[cfg(not(target_arch = "wasm32"))] { for var in ["USER", "LOGNAME", "USERNAME"] { @@ -275,3 +293,70 @@ pub fn hosts_field(document: &CadDocument, entity: &codec::entities::EntityType) Some(codec::objects::ObjectType::Dictionary(d)) if d.get("ACAD_FIELD").is_some() ) } + +fn now_utc_julian() -> f64 { + epoch_secs() as f64 / 86_400.0 + 2_440_587.5 +} + +/// Local time minus UTC, in days (the zone's current offset). +pub fn utc_offset_days() -> f64 { + #[cfg(target_os = "windows")] + { + use windows_sys::Win32::Foundation::FILETIME; + use windows_sys::Win32::Storage::FileSystem::FileTimeToLocalFileTime; + // 100 ns ticks since 1601-01-01. + let ticks = (epoch_secs() as u64 + 11_644_473_600) * 10_000_000; + let utc = FILETIME { dwLowDateTime: ticks as u32, dwHighDateTime: (ticks >> 32) as u32 }; + let mut local = FILETIME { dwLowDateTime: 0, dwHighDateTime: 0 }; + if unsafe { FileTimeToLocalFileTime(&utc, &mut local) } != 0 { + let local = ((local.dwHighDateTime as u64) << 32) | local.dwLowDateTime as u64; + return (local as i64 - ticks as i64) as f64 / 864_000_000_000.0; + } + 0.0 + } + #[cfg(not(target_os = "windows"))] + 0.0 +} + +/// A DWG stores its header dates in universal time only; the local TDCREATE +/// and TDUPDATE come from the zone, as the reference derives them on load. +pub fn local_header_dates(document: &mut CadDocument) { + let h = &mut document.header; + let offset = utc_offset_days(); + if h.universal_create_date_julian != 0.0 && h.create_date_julian == h.universal_create_date_julian { + h.create_date_julian = h.universal_create_date_julian + offset; + } + if h.universal_update_date_julian != 0.0 && h.update_date_julian == h.universal_update_date_julian { + h.update_date_julian = h.universal_update_date_julian + offset; + } +} + +/// Stamp the header's update date (and the creation date of a new drawing) +/// in local and universal time before a save. +pub fn stamp_save_dates(document: &mut CadDocument) { + let utc = now_utc_julian(); + let local = utc + utc_offset_days(); + let h = &mut document.header; + if h.create_date_julian == 0.0 && h.universal_create_date_julian == 0.0 { + h.create_date_julian = local; + h.universal_create_date_julian = utc; + } + h.update_date_julian = local; + h.universal_update_date_julian = utc; +} + +/// The signed-in account's display name, when it has one. +fn display_name() -> Option { + #[cfg(target_os = "windows")] + { + use windows_sys::Win32::Security::Authentication::Identity::{GetUserNameExW, NameDisplay}; + let mut buffer = [0u16; 256]; + let mut len = buffer.len() as u32; + if unsafe { GetUserNameExW(NameDisplay, buffer.as_mut_ptr(), &mut len) } && len > 0 { + return Some(String::from_utf16_lossy(&buffer[..len as usize])).filter(|n| !n.trim().is_empty()); + } + None + } + #[cfg(not(target_os = "windows"))] + None +} diff --git a/src/io/mod.rs b/src/io/mod.rs index 45fbc2f33..a412dc859 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -723,6 +723,7 @@ async fn load_web_bytes( } fix_viewport_status_flags(&mut doc); fix_current_style_names(&mut doc); + crate::entities::field::local_header_dates(&mut doc); progress.set(crate::app::OPEN_PHASE_CACHING, 7000, 0, 1); let dropped = purge_corrupt_entities(&mut doc); if !recovery_mode && dropped > 0 { @@ -764,6 +765,7 @@ pub fn load_bytes(name: &str, bytes: Vec) -> Result { .map_err(|e| e.to_string())?; fix_viewport_status_flags(&mut doc); fix_current_style_names(&mut doc); + crate::entities::field::local_header_dates(&mut doc); Ok(doc) } "dxf" => { @@ -775,6 +777,7 @@ pub fn load_bytes(name: &str, bytes: Vec) -> Result { fix_dxf_layout_plot_settings(&mut doc); fix_viewport_status_flags(&mut doc); fix_current_style_names(&mut doc); + crate::entities::field::local_header_dates(&mut doc); Ok(doc) } _ => Err(format!("Unsupported file format: .{ext}")), @@ -1040,6 +1043,7 @@ fn finalize_loaded_outcome( } fix_viewport_status_flags(doc); fix_current_style_names(doc); + crate::entities::field::local_header_dates(doc); resolve_raster_image_paths(doc, path.parent()); doc.source_path = Some(path.to_string_lossy().into_owned()); Ok(outcome) diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index e04d085d6..4ec7f0109 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -235,6 +235,14 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Select object" => Some(("insert", "field-select-object")), "Field expression" => Some(("insert", "field-expression")), "Inserts a field: text that updates from the drawing, the date or another source." => Some(("insert", "field-help")), + "Linked" => Some(("insert", "field-cat-linked")), + "Current precision" => Some(("insert", "field-current-precision")), + "Evaluate" => Some(("insert", "field-evaluate")), + "Text to display" => Some(("insert", "field-text-to-display")), + "Address" => Some(("insert", "field-address")), + "Select a file to link" => Some(("insert", "field-select-link-file")), + "Current units" => Some(("insert", "field-current-units")), + "Use scale name" => Some(("insert", "field-use-scale-name")), "Date & Time" => Some(("insert", "field-cat-date")), "Document" => Some(("insert", "field-cat-document")), "Other" => Some(("insert", "field-cat-other")), diff --git a/src/scene/parametric_constraints.rs b/src/scene/parametric_constraints.rs index 119490162..1adb6bf23 100644 --- a/src/scene/parametric_constraints.rs +++ b/src/scene/parametric_constraints.rs @@ -2533,6 +2533,7 @@ impl super::Scene { } } crate::io::xref::strip_resolved_xref_content(&mut document); + crate::entities::field::stamp_save_dates(&mut document); document } diff --git a/src/ui/window/field_dialog.rs b/src/ui/window/field_dialog.rs index 3732cc3f9..8862378db 100644 --- a/src/ui/window/field_dialog.rs +++ b/src/ui/window/field_dialog.rs @@ -31,9 +31,13 @@ pub const CATEGORIES: &[(&str, &[&str])] = &[ "Document", &["Author", "Comments", "Filename", "Filesize", "HyperlinkBase", "Keywords", "LastSavedBy", "Subject", "Title"], ), - ("Objects", &["NamedObject", "Object"]), + ("Linked", &["Hyperlink"]), + ("Objects", &["Formula", "NamedObject", "Object"]), ("Other", &["DieselExpression", "SystemVariable"]), - ("Plot", &["Login", "PlotDate"]), + ( + "Plot", + &["DeviceName", "Login", "PageSetupName", "PaperSize", "PlotDate", "PlotOrientation", "PlotScale", "PlotStyleTable"], + ), ]; /// The fields of a category; "All" lists every field once, sorted. @@ -70,8 +74,15 @@ pub enum FieldKind { Diesel, NamedObject, Object, + Formula, + Hyperlink, + PlotScale, } +/// Formula precisions: the current one, then 0 to 8 decimals. +pub const FORMULA_PRECISIONS: &[&str] = + &["Current precision", "0", "0.0", "0.00", "0.000", "0.0000", "0.00000", "0.000000", "0.0000000", "0.00000000"]; + pub fn kind_of(name: &str) -> FieldKind { match name { "CreateDate" | "Date" | "PlotDate" | "SaveDate" => FieldKind::Date, @@ -81,6 +92,9 @@ pub fn kind_of(name: &str) -> FieldKind { "DieselExpression" => FieldKind::Diesel, "NamedObject" => FieldKind::NamedObject, "Object" => FieldKind::Object, + "Formula" => FieldKind::Formula, + "Hyperlink" => FieldKind::Hyperlink, + "PlotScale" => FieldKind::PlotScale, _ => FieldKind::Text, } } @@ -106,6 +120,14 @@ pub struct FieldDialogState { pub object: Option<(codec::Handle, String)>, pub object_props: Vec<&'static str>, pub object_prop: Option<&'static str>, + pub formula: String, + /// Indexes into codec FORMULA_FORMATS and FORMULA_PRECISIONS. + pub formula_format: usize, + pub formula_precision: usize, + pub hyperlink_text: String, + pub hyperlink_url: String, + /// Index into codec PLOT_SCALE_FORMATS. + pub plot_scale: usize, pub preview: String, /// Today in each of DATE_FORMATS, for the Examples list. pub examples: Vec, @@ -130,6 +152,12 @@ impl FieldDialogState { object: None, object_props: Vec::new(), object_prop: None, + formula: String::new(), + formula_format: 0, + formula_precision: 0, + hyperlink_text: String::new(), + hyperlink_url: String::new(), + plot_scale: 0, preview: String::new(), examples: Vec::new(), } @@ -153,7 +181,36 @@ impl FieldDialogState { }; (format!("\\AcVar Filename \\f \"{case}%fn{bits}\""), vec![]) } - FieldKind::Filesize => (format!("\\AcVar Filesize \\f \"%ld%by{}\"", self.size_unit + 1), vec![]), + // Bytes are whole; kilo- and megabytes keep two decimals. + FieldKind::Filesize => ( + match self.size_unit { + 0 => "\\AcVar Filesize \\f \"%ld%by1\"".to_string(), + n => format!("\\AcVar Filesize \\f \"%.2f%by{}\"", n + 1), + }, + vec![], + ), + FieldKind::Formula => { + let format = codec::fields::FORMULA_FORMATS.get(self.formula_format).map_or("", |f| f.1); + let precision = match self.formula_precision { + 0 => String::new(), + n => format!("%pr{}", n - 1), + }; + let body = format!("\\AcExpr ({})", self.formula.trim()); + match format!("{format}{precision}") { + f if f.is_empty() => (body, vec![]), + f => (format!("{body} \\f \"{f}\""), vec![]), + } + } + FieldKind::Hyperlink => ( + format!("\\AcVar \\href \"{}##{}#0\"", self.hyperlink_url.trim(), self.hyperlink_text), + vec![], + ), + FieldKind::PlotScale => ( + codec::fields::PLOT_SCALE_FORMATS + .get(self.plot_scale) + .map_or_else(|| "\\AcVar PlotScale".to_string(), |f| f.1.to_string()), + vec![], + ), FieldKind::SystemVariable => (format!("\\AcVar {}", self.sysvar), vec![]), FieldKind::Diesel => (format!("\\AcDiesel {}", self.diesel), vec![]), FieldKind::NamedObject => match self.named.and_then(|i| self.named_names.get(i)) { @@ -176,6 +233,8 @@ impl FieldDialogState { FieldKind::NamedObject => self.named.is_some(), FieldKind::Object => self.object.is_some() && self.object_prop.is_some(), FieldKind::Diesel => !self.diesel.trim().is_empty(), + FieldKind::Formula => !self.formula.trim().is_empty(), + FieldKind::Hyperlink => !self.hyperlink_url.trim().is_empty(), _ => true, } } @@ -211,6 +270,14 @@ pub enum FieldDialogMsg { Named(usize), SelectObject, ObjectProp(&'static str), + Formula(String), + FormulaFormat(usize), + FormulaPrecision(usize), + Evaluate, + HyperlinkText(String), + HyperlinkUrl(String), + BrowseHyperlink, + PlotScale(usize), Help, Ok, } @@ -396,6 +463,85 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { ] .spacing(8) .into(), + FieldKind::Formula => { + let formats: Vec = codec::fields::FORMULA_FORMATS + .iter() + .enumerate() + .map(|(i, (n, _))| Pick(i, t!(*n).into_owned())) + .collect(); + let precisions: Vec = + FORMULA_PRECISIONS.iter().enumerate().map(|(i, n)| Pick(i, t!(*n).into_owned())).collect(); + column![ + row![ + text(t!("Format")).size(12).width(Length::Fixed(80.0)), + pick_list(Some(formats[state.formula_format].clone()), formats.clone(), |p: &Pick| p.1.clone()) + .on_select(|p| msg(FieldDialogMsg::FormulaFormat(p.0))) + .text_size(12) + .padding([5, 8]) + .width(Fill), + ] + .spacing(8) + .align_y(iced::Center), + row![ + text(t!("Precision")).size(12).width(Length::Fixed(80.0)), + pick_list( + Some(precisions[state.formula_precision].clone()), + precisions.clone(), + |p: &Pick| p.1.clone(), + ) + .on_select(|p| msg(FieldDialogMsg::FormulaPrecision(p.0))) + .text_size(12) + .padding([5, 8]) + .width(Fill), + ] + .spacing(8) + .align_y(iced::Center), + ] + .spacing(8) + .into() + } + FieldKind::Hyperlink => column![ + row![ + text(t!("Text to display")).size(12).width(Length::Fixed(120.0)), + text_input("", &state.hyperlink_text) + .size(12) + .padding([5, 8]) + .style(field_style) + .on_input(|v| msg(FieldDialogMsg::HyperlinkText(v))), + ] + .spacing(8) + .align_y(iced::Center), + row![ + text(t!("Address")).size(12).width(Length::Fixed(120.0)), + text_input("https://", &state.hyperlink_url) + .size(12) + .padding([5, 8]) + .font(iced::Font::MONOSPACE) + .style(field_style) + .on_input(|v| msg(FieldDialogMsg::HyperlinkUrl(v))), + button(text("…").size(12)) + .on_press(msg(FieldDialogMsg::BrowseHyperlink)) + .style(button_style(false)) + .padding([5, 10]), + ] + .spacing(8) + .align_y(iced::Center), + ] + .spacing(8) + .into(), + FieldKind::PlotScale => column![ + text(t!("Format")).size(12), + list( + codec::fields::PLOT_SCALE_FORMATS + .iter() + .enumerate() + .map(|(i, (n, _))| (t!(*n).into_owned(), state.plot_scale == i, FieldDialogMsg::PlotScale(i))) + .collect(), + 170.0, + ), + ] + .spacing(8) + .into(), FieldKind::Object => column![ row![ text(t!("Object type")).size(12).width(Length::Fixed(96.0)), @@ -460,7 +606,31 @@ pub fn view<'a>( .padding([6, 8]) .width(Fill), ); - let right = column![card(t!("Format").into_owned(), format_panel(state)), preview].spacing(10); + let right = match kind_of(state.name) { + FieldKind::Formula => column![ + card( + t!("Formula").into_owned(), + column![ + text_input("(12+3)*2", &state.formula) + .size(12) + .padding([8, 8]) + .font(iced::Font::MONOSPACE) + .style(field_style) + .on_input(|v| msg(FieldDialogMsg::Formula(v))), + button(text(t!("Evaluate")).size(12)) + .on_press(msg(FieldDialogMsg::Evaluate)) + .style(button_style(false)) + .padding([5, 14]), + ] + .spacing(8), + ), + card(t!("Format").into_owned(), format_panel(state)), + preview, + ] + .spacing(10), + FieldKind::Hyperlink => column![card(t!("Hyperlink").into_owned(), format_panel(state)), preview].spacing(10), + _ => column![card(t!("Format").into_owned(), format_panel(state)), preview].spacing(10), + }; let (code, _) = state.code(); let expression = column![ text(t!("Field expression")).size(12),