From 5b6cbff5b20b3d41a7ae07dd7df37dfa3db61a94 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:08:06 +0300 Subject: [PATCH] Attach Underlay ribbon menu, DWF/DGN attach dialog, DGN true colours - Insert > Reference: the PDF Attach button becomes an Attach Underlay menu (PDF / DWF / DGN); its face runs the last one chosen. - DWFATTACH, DGNATTACH and ATTACH on a .dwf/.dwfx/.dgn open the Attach dialog for that kind: sheets or models with thumbnails (one chosen), placement, path type, file details with the sheet / model size, and for DGN the conversion units (sub units offer 1 / sub units per master as the scale). - DGN V8 true colours come from the ExtendedColors record packed in the non-model attributes stream; the colour word's high part numbers them. - A DGN model's extent includes each text's box (its length along the baseline and 1.5 x its height), as the reference sizes a model. - DWFx text uses the family named in its embedded (deobfuscated) font. - DWF contrast pivots at 0.73 once colours are turned over for a dark background. - Hairline strokes are drawn solid one pixel wide, and underlay rasters are made at or below their size on screen so those lines stay unbroken. Co-Authored-By: Claude Opus 5.5 --- locales/ar-SA/opencadstudio.ftl | 19 +++++++ locales/bg-BG/opencadstudio.ftl | 19 +++++++ locales/cs-CZ/opencadstudio.ftl | 19 +++++++ locales/de-DE/opencadstudio.ftl | 19 +++++++ locales/el-GR/opencadstudio.ftl | 19 +++++++ locales/en-US/opencadstudio.ftl | 19 +++++++ locales/es-ES/opencadstudio.ftl | 19 +++++++ locales/fi-FI/opencadstudio.ftl | 19 +++++++ locales/fr-FR/opencadstudio.ftl | 19 +++++++ locales/hi-IN/opencadstudio.ftl | 19 +++++++ locales/hu-HU/opencadstudio.ftl | 19 +++++++ locales/it-IT/opencadstudio.ftl | 19 +++++++ locales/ja-JP/opencadstudio.ftl | 19 +++++++ locales/ko-KR/opencadstudio.ftl | 19 +++++++ locales/nl-NL/opencadstudio.ftl | 19 +++++++ locales/pl-PL/opencadstudio.ftl | 19 +++++++ locales/pt-BR/opencadstudio.ftl | 19 +++++++ locales/ru-RU/opencadstudio.ftl | 19 +++++++ locales/tr-TR/opencadstudio.ftl | 19 +++++++ locales/zh-CN/opencadstudio.ftl | 19 +++++++ locales/zh-TW/opencadstudio.ftl | 19 +++++++ src/app/commands/pdf_dialogs.rs | 68 ++++++++++++++++++++--- src/app/commands/xref_attach.rs | 21 +++++++- src/app/view/modal.rs | 6 ++- src/locale_catalog.rs | 19 +++++++ src/modules/insert/mod.rs | 13 ++++- src/modules/insert/pdf_attach.rs | 35 ++++++------ src/scene/mod.rs | 4 +- src/scene/model/image_model.rs | 10 +++- src/scene/model/underlay_vector/dgn8.rs | 48 +++++++++++++++-- src/scene/model/underlay_vector/dwf.rs | 72 ++++++++++++++++++++++--- src/scene/model/underlay_vector/mod.rs | 37 +++++++++---- src/ui/window/pdf_dialogs.rs | 72 ++++++++++++++++++++++--- 33 files changed, 745 insertions(+), 59 deletions(-) diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 15e531a1e..589948d59 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3564,6 +3564,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index b79ca01d2..b146c39cd 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 56ec1252e..32a532052 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3549,6 +3549,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 89fa87a05..2dbb00c46 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3552,6 +3552,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index e8f177aa4..316dde7d7 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3552,6 +3552,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 36747bc5d..a8ba7fc41 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3553,6 +3553,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 51caecad4..788932a08 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3552,6 +3552,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 51117251c..cb6892d0c 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 3499e3570..e89501078 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3552,6 +3552,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index ea7b8bcc3..4cb65b077 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3540,6 +3540,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index db3c1870e..a4f12329d 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 24c215ef3..6ddc5324c 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 7e0514932..7b7c5d080 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3542,6 +3542,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index eafa273a2..ef8c2cf6b 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index a31f03e38..27308747e 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3550,6 +3550,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index bad98e700..d1cd1b3d6 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 92ef7d11c..0ab66e7d7 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3552,6 +3552,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 19bdbb5ef..0e25649a3 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3553,6 +3553,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index f12ee6f36..3822f9ea4 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3526,6 +3526,25 @@ insert = .pdfui-search-for-layer = Katman ara .pdfui-reference-name = Referans adı: .pdfui-page-size = Sayfa boyutu: + .ul-attach-underlay = Altlık Bağla + .ul-attach-dwf = DWF Bağla + .ul-attach-dgn = DGN Bağla + .ul-attach-dwf-underlay = DWF Altlığı Ekle + .ul-attach-dgn-underlay = DGN Altlığı Ekle + .ul-sheet-count = __ocs_fmt_0__ pafta + .ul-model-count = __ocs_fmt_0__ model + .ul-sheets = Paftalar + .ul-models = Modeller + .ul-selected-sheet = Seçili pafta: __ocs_fmt_0__ + .ul-selected-model = Seçili model: __ocs_fmt_0__ + .ul-sheet-size = Pafta boyutu: + .ul-model-size = Model boyutu: + .ul-conversion-units = Dönüştürme birimleri + .ul-master-units = Ana birim + .ul-sub-units = Alt birim + .ul-sub-units-scale = Alt birim seçilirse varsayılan ölçek __ocs_fmt_0__ olur. + .ul-attaches-dwf = Bir DWF veya DWFx dosyasını altlık olarak bağlar. + .ul-attaches-dgn = Bir DGN dosyasını altlık olarak bağlar. .pdfui-select-one-or-more-pages-from-the-pdf-file = PDF dosyasından bir veya daha fazla sayfa seçin: .pdfui-import-pdf = PDF İçe Aktar .pdfui-pdf-import-settings = PDF İçe Aktarma Ayarları diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 8002089ca..5208a0465 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3539,6 +3539,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 9f20be922..c82db139c 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3551,6 +3551,25 @@ insert = .pdfui-search-for-layer = Search for layer .pdfui-reference-name = Reference name: .pdfui-page-size = Page size: + .ul-attach-underlay = Attach Underlay + .ul-attach-dwf = Attach DWF + .ul-attach-dgn = Attach DGN + .ul-attach-dwf-underlay = Attach DWF Underlay + .ul-attach-dgn-underlay = Attach DGN Underlay + .ul-sheet-count = __ocs_fmt_0__ sheets + .ul-model-count = __ocs_fmt_0__ models + .ul-sheets = Sheets + .ul-models = Models + .ul-selected-sheet = Selected sheet: __ocs_fmt_0__ + .ul-selected-model = Selected model: __ocs_fmt_0__ + .ul-sheet-size = Sheet size: + .ul-model-size = Model size: + .ul-conversion-units = Conversion units + .ul-master-units = Master units + .ul-sub-units = Sub units + .ul-sub-units-scale = Sub units make the default scale __ocs_fmt_0__ + .ul-attaches-dwf = Attaches a DWF or DWFx file as an underlay. + .ul-attaches-dgn = Attaches a DGN file as an underlay. .pdfui-select-one-or-more-pages-from-the-pdf-file = Select one or more pages from the PDF file: .pdfui-import-pdf = Import PDF .pdfui-pdf-import-settings = PDF Import Settings diff --git a/src/app/commands/pdf_dialogs.rs b/src/app/commands/pdf_dialogs.rs index 70ebf72fa..85629aed6 100644 --- a/src/app/commands/pdf_dialogs.rs +++ b/src/app/commands/pdf_dialogs.rs @@ -5,7 +5,7 @@ use super::*; use crate::io::xref_model::Pathtype; use crate::modules::insert::pdf_import::{self, PdfFileImport}; use crate::ui::window::pdf_dialogs::{ - click_page, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg, PdfImportFileState, + click_page, item_thumbs, page_thumbs, LayerTarget, PdfAttachState, PdfDialogMsg, PdfImportFileState, RotationChoice, UnderlayLayersState, }; @@ -16,6 +16,23 @@ fn page_size_text(path: &str, page: &str) -> String { .unwrap_or_default() } +/// The page's (PDF, inches) or the sheet's / model's size. +fn item_size_text(kind: codec::entities::UnderlayType, path: &str, item: &str) -> String { + if kind == codec::entities::UnderlayType::Pdf { + return page_size_text(path, item); + } + crate::scene::model::underlay_vector::sheet(kind, path, item) + .map(|s| format!("{:.4} × {:.4}", s.rect[2] - s.rect[0], s.rect[3] - s.rect[1])) + .unwrap_or_default() +} + +fn sub_per_master(kind: codec::entities::UnderlayType, path: &str, item: &str) -> f64 { + crate::scene::model::underlay_vector::sheet(kind, path, item) + .map(|s| s.sub_per_master) + .filter(|v| *v > 0.0) + .unwrap_or(1.0) +} + fn file_stem(path: &str) -> String { std::path::Path::new(&path.replace('\\', "/")) .file_stem() @@ -45,10 +62,18 @@ impl OpenCADStudio { /// PDFATTACH after the file is chosen. pub(in crate::app) fn open_pdf_attach_dialog(&mut self, path: &str) { + self.open_underlay_attach_dialog(codec::entities::UnderlayType::Pdf, path); + } + + /// PDFATTACH / DWFATTACH / DGNATTACH after the file is chosen. + pub(in crate::app) fn open_underlay_attach_dialog(&mut self, kind: codec::entities::UnderlayType, path: &str) { let i = self.active_tab; let mut existing: Vec<(String, String)> = Vec::new(); for object in self.tabs[i].scene.document.objects.values() { if let codec::objects::ObjectType::UnderlayDefinition(def) = object { + if def.underlay_type != kind { + continue; + } let name = file_stem(&def.file_path); if !existing.iter().any(|(n, _)| *n == name) { existing.push((name, def.file_path.clone())); @@ -56,6 +81,7 @@ impl OpenCADStudio { } } let mut state = PdfAttachState::new(existing); + state.kind = kind; self.load_pdf_attach_file(&mut state, path); self.pdf_attach = Some(state); self.active_modal = Some(crate::app::ModalKind::PdfAttach); @@ -64,11 +90,14 @@ impl OpenCADStudio { fn load_pdf_attach_file(&self, state: &mut PdfAttachState, path: &str) { state.path = path.to_string(); state.name = file_stem(path); - state.pages = page_thumbs(path); + state.pages = item_thumbs(state.kind, path); state.selected = vec![0]; state.anchor = 0; state.found_in = crate::entities::underlay::display_path(path); - state.page_size = page_size_text(path, "1"); + let first = state.page_labels().into_iter().next().unwrap_or_else(|| "1".to_string()); + state.page_size = item_size_text(state.kind, path, &first); + state.sub_per_master = sub_per_master(state.kind, path, &first); + state.sub_units = false; state.saved_path = crate::entities::underlay::display_path(&self.pdf_stored_path(path, state.path_type.0)); } @@ -206,7 +235,14 @@ impl OpenCADStudio { let i = self.active_tab; match message { PdfDialogMsg::Help(which) => { + let kind = self.pdf_attach.as_ref().map(|s| s.kind); let text = match which { + "attach" if kind == Some(codec::entities::UnderlayType::Dwf) => { + crate::t!("Attaches a DWF or DWFx file as an underlay.") + } + "attach" if kind == Some(codec::entities::UnderlayType::Dgn) => { + crate::t!("Attaches a DGN file as an underlay.") + } "attach" => crate::t!("Attaches a PDF file as an underlay."), "layers" => crate::t!("Turns the layers of a PDF underlay on or off."), _ => crate::t!("Imports the geometry, fills, raster images and text of a PDF file as drawing objects."), @@ -220,11 +256,22 @@ impl OpenCADStudio { // ── Attach ──────────────────────────────────────────────────── PdfDialogMsg::AttachBrowse => { + let kind = self.pdf_attach.as_ref().map(|s| s.kind).unwrap_or(codec::entities::UnderlayType::Pdf); if let Some(state) = &self.pdf_attach { state.remember(); } + if kind != codec::entities::UnderlayType::Pdf { + return Task::done(Message::UnderlayAttachPick(kind)); + } return Task::done(Message::PdfAttachPick); } + PdfDialogMsg::AttachSubUnits(sub) => { + if let Some(s) = self.pdf_attach.as_mut() { + s.sub_units = sub; + // The scale offered follows the units, as on the command line. + s.scale = if sub { format!("{}", 1.0 / s.sub_per_master) } else { "1.0000".to_string() }; + } + } PdfDialogMsg::AttachName(name) => { let Some(mut state) = self.pdf_attach.take() else { return Task::none(); @@ -237,9 +284,16 @@ impl OpenCADStudio { PdfDialogMsg::AttachPage(page) => { let (ctrl, shift) = (self.ctrl_down, self.shift_down); if let Some(state) = self.pdf_attach.as_mut() { - click_page(&mut state.selected, &mut state.anchor, page, ctrl, shift); + if state.kind == codec::entities::UnderlayType::Pdf { + click_page(&mut state.selected, &mut state.anchor, page, ctrl, shift); + } else { + // One sheet or model at a time. + state.selected = vec![page]; + state.anchor = page; + } let first = state.page_labels().into_iter().next().unwrap_or_default(); - state.page_size = page_size_text(&state.path, &first); + state.page_size = item_size_text(state.kind, &state.path, &first); + state.sub_per_master = sub_per_master(state.kind, &state.path, &first); } } PdfDialogMsg::AttachPathType(choice) => { @@ -475,12 +529,14 @@ impl OpenCADStudio { } self.close_active_modal(); let insunits = self.tabs[i].scene.document.header.insertion_units; - let mut command = crate::modules::insert::pdf_attach::PdfAttachCommand::from_dialog( + let mut command = crate::modules::insert::pdf_attach::PdfAttachCommand::from_kind_dialog( + state.kind, &state.path, &stored, &state.page_labels(), scale, rotation, + state.sub_units, insunits, ); match insertion { diff --git a/src/app/commands/xref_attach.rs b/src/app/commands/xref_attach.rs index 0b6d08efb..7ff91dfcf 100644 --- a/src/app/commands/xref_attach.rs +++ b/src/app/commands/xref_attach.rs @@ -402,8 +402,25 @@ impl OpenCADStudio { } else { codec::entities::UnderlayType::Dwf }; - let i = self.active_tab; - self.start_underlay_attach(i, kind, &path.to_string_lossy()); + let path = path.to_string_lossy().into_owned(); + match std::fs::read(&path) { + Ok(bytes) => { + crate::scene::model::pdf_raster::register_source(&path, std::sync::Arc::new(bytes)); + crate::scene::model::underlay_vector::forget(&path); + if crate::scene::model::underlay_vector::item_names(kind, &path).is_some_and(|n| !n.is_empty()) { + self.open_underlay_attach_dialog(kind, &path); + } else { + self.command_line.push_error(&format!( + "{} not found.", + crate::entities::underlay::display_path(&path) + )); + } + } + Err(_) => self.command_line.push_error(&format!( + "{} not found.", + crate::entities::underlay::display_path(&path) + )), + } Task::none() } "png" | "jpg" | "jpeg" | "bmp" | "tif" | "tiff" => { diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 29387b438..2915a28ec 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -24,7 +24,11 @@ impl OpenCADStudio { Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(), Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(), Some(K::BlockDefinition) => crate::t!("Block Definition").into_owned(), - Some(K::PdfAttach) => crate::t!("Attach PDF Underlay").into_owned(), + Some(K::PdfAttach) => match self.pdf_attach.as_ref().map(|s| s.kind) { + Some(codec::entities::UnderlayType::Dwf) => crate::t!("Attach DWF Underlay").into_owned(), + Some(codec::entities::UnderlayType::Dgn) => crate::t!("Attach DGN Underlay").into_owned(), + _ => crate::t!("Attach PDF Underlay").into_owned(), + }, Some(K::UnderlayLayers) => crate::t!("Underlay Layers").into_owned(), Some(K::PdfImportSettings) => crate::t!("PDF Import Settings").into_owned(), Some(K::PdfImportFile) => crate::t!("Import PDF").into_owned(), diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 3c267d846..b09da7810 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -2983,6 +2983,25 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Import PDF" => Some(("insert", "pdfui-import-pdf")), "Select one or more pages from the PDF file:" => Some(("insert", "pdfui-select-one-or-more-pages-from-the-pdf-file")), "Page size:" => Some(("insert", "pdfui-page-size")), + "Attach Underlay" => Some(("insert", "ul-attach-underlay")), + "Attach DWF" => Some(("insert", "ul-attach-dwf")), + "Attach DGN" => Some(("insert", "ul-attach-dgn")), + "Attach DWF Underlay" => Some(("insert", "ul-attach-dwf-underlay")), + "Attach DGN Underlay" => Some(("insert", "ul-attach-dgn-underlay")), + "{count} sheets" => Some(("insert", "ul-sheet-count")), + "{count} models" => Some(("insert", "ul-model-count")), + "Sheets" => Some(("insert", "ul-sheets")), + "Models" => Some(("insert", "ul-models")), + "Selected sheet: {sheet}" => Some(("insert", "ul-selected-sheet")), + "Selected model: {model}" => Some(("insert", "ul-selected-model")), + "Sheet size:" => Some(("insert", "ul-sheet-size")), + "Model size:" => Some(("insert", "ul-model-size")), + "Conversion units" => Some(("insert", "ul-conversion-units")), + "Master units" => Some(("insert", "ul-master-units")), + "Sub units" => Some(("insert", "ul-sub-units")), + "Sub units make the default scale {scale}" => Some(("insert", "ul-sub-units-scale")), + "Attaches a DWF or DWFx file as an underlay." => Some(("insert", "ul-attaches-dwf")), + "Attaches a DGN file as an underlay." => Some(("insert", "ul-attaches-dgn")), "Reference name:" => Some(("insert", "pdfui-reference-name")), "Search for layer" => Some(("insert", "pdfui-search-for-layer")), "This file does not contain any layers" => Some(("insert", "pdfui-this-file-does-not-contain-any-layers")), diff --git a/src/modules/insert/mod.rs b/src/modules/insert/mod.rs index d75eec575..8d1827d13 100644 --- a/src/modules/insert/mod.rs +++ b/src/modules/insert/mod.rs @@ -53,7 +53,18 @@ impl CadModule for InsertModule { title: "Reference", tools: vec![ RibbonItem::LargeTool(xattach::tool()), - RibbonItem::LargeTool(pdf_attach::tool()), + // PDF, DWF or DGN; the face runs the last one chosen. + RibbonItem::LargeDropdown { + id: "UNDERLAY_ATTACH", + label: "Attach Underlay", + icon: pdf_attach::ICON, + items: vec![ + ("PDFATTACH", "Attach PDF", pdf_attach::ICON), + ("DWFATTACH", "Attach DWF", pdf_attach::ICON), + ("DGNATTACH", "Attach DGN", pdf_attach::ICON), + ], + default: "PDFATTACH", + }, RibbonItem::LargeTool(xclip::tool()), RibbonItem::LargeTool(xadjust::tool()), RibbonItem::LabeledTool(underlay_layers::tool()), diff --git a/src/modules/insert/pdf_attach.rs b/src/modules/insert/pdf_attach.rs index db9456761..5cc02dc6c 100644 --- a/src/modules/insert/pdf_attach.rs +++ b/src/modules/insert/pdf_attach.rs @@ -23,20 +23,11 @@ use glam::DVec3; use crate::command::{CadCommand, CmdOption, CmdResult, InputKind, WorkingPlane}; use crate::scene::model::wire_model::WireModel; -use crate::modules::{IconKind, ModuleEvent, ToolDef}; +use crate::modules::IconKind; pub const ICON: IconKind = IconKind::Svg(include_bytes!("../../../assets/icons/underlay_layers.svg")); -pub fn tool() -> ToolDef { - ToolDef { - id: "PDFATTACH", - label: "Attach PDF", - icon: ICON, - event: ModuleEvent::Command("PDFATTACH".to_string()), - } -} - /// Units the Unit option offers: keyword, the name the prompt shows. const UNITS: [(&str, &str); 9] = [ ("MM", "Millimeters"), @@ -172,11 +163,6 @@ impl PdfAttachCommand { } } - /// PDFATTACH with a file already chosen: starts at the page prompt. - pub fn with_file(path: &str, insunits: i16) -> Self { - Self::with_kind_file(UnderlayType::Pdf, path, insunits) - } - /// DWFATTACH / DGNATTACH / PDFATTACH with the file chosen: starts at the /// page, sheet or model prompt. pub fn with_kind_file(kind: UnderlayType, path: &str, insunits: i16) -> Self { @@ -216,23 +202,34 @@ impl PdfAttachCommand { } } - /// The dialog's attach with the insertion point asked on screen: pages - /// chosen, the path to store, and any scale / rotation already given. - pub fn from_dialog( + /// The DWF / DGN (or PDF) Attach dialog: the sheet or model chosen and, + /// for DGN, whether sub units were chosen (their scale is then offered). + #[allow(clippy::too_many_arguments)] + pub fn from_kind_dialog( + kind: UnderlayType, read_path: &str, stored_path: &str, pages: &[String], scale: Option, rotation_deg: Option, + sub_units: bool, insunits: i16, ) -> Self { - let mut command = Self::with_file(read_path, insunits); + let mut command = Self::with_kind_file(kind, read_path, insunits); command.stored_path = Some(stored_path.to_string()); if let Some((first, rest)) = pages.split_first() { command.page = first.clone(); command.extra_pages = rest.to_vec(); } command.rect = command.page_rect(&command.page); + if kind == UnderlayType::Dgn && sub_units { + let sub = crate::scene::model::underlay_vector::sheet(kind, read_path, &command.page) + .map(|s| s.sub_per_master) + .filter(|v| *v > 0.0) + .unwrap_or(1.0); + command.default_scale = 1.0 / sub; + command.scale = command.default_scale; + } command.preset_scale = scale; if let Some(s) = scale { command.scale = s; diff --git a/src/scene/mod.rs b/src/scene/mod.rs index d23f9850b..a69397491 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -2753,7 +2753,7 @@ impl Scene { let Some(wpp) = self.world_per_pixel() else { return; }; - let step = (f64::from(wpp).log2() * 4.0).floor() as i32; + let step = (f64::from(wpp).log2() * 4.0).ceil() as i32; let current = self.underlay_world_per_pixel.map(|w| (w.log2() * 4.0).round() as i32); if current == Some(step) { self.underlay_scale_pending = None; @@ -2783,7 +2783,7 @@ impl Scene { let Some(wpp) = self.world_per_pixel() else { return false; }; - let step = (f64::from(wpp).log2() * 4.0).floor() as i32; + let step = (f64::from(wpp).log2() * 4.0).ceil() as i32; self.underlay_world_per_pixel.map(|w| (w.log2() * 4.0).round() as i32) != Some(step) } diff --git a/src/scene/model/image_model.rs b/src/scene/model/image_model.rs index 8da2881ae..dfd6d1ea9 100644 --- a/src/scene/model/image_model.rs +++ b/src/scene/model/image_model.rs @@ -207,7 +207,8 @@ impl ImageModel { let page = crate::entities::underlay::page_of(def); let rect = crate::entities::underlay::definition_rect(def)?; // The page's longest side on screen, pixels (the view scale is taken - // a step finer, so the raster is never enlarged on screen). + // a step coarser, so the raster is never reduced on screen and its + // one-pixel lines stay unbroken). let screen_side = world_per_pixel .filter(|wpp| *wpp > 0.0) .map(|wpp| { @@ -258,8 +259,15 @@ impl ImageModel { &raster, PageAdjust { contrast: u.contrast.min(100), + // Measured: PDF 0.5; DWF 0.243, or 0.73 once its colours are + // turned over for a dark background; DGN 0.65. contrast_pivot: match def.underlay_type { UnderlayType::Pdf => 500, + UnderlayType::Dwf + if u.flags.contains(UnderlayDisplayFlags::ADJUST_FOR_BACKGROUND) && bg_lum < 0.5 => + { + 730 + } UnderlayType::Dwf => 243, UnderlayType::Dgn => 650, }, diff --git a/src/scene/model/underlay_vector/dgn8.rs b/src/scene/model/underlay_vector/dgn8.rs index 4bc4d8605..f6dae1281 100644 --- a/src/scene/model/underlay_vector/dgn8.rs +++ b/src/scene/model/underlay_vector/dgn8.rs @@ -212,19 +212,57 @@ struct Styles { levels: HashMap, /// Levels the underlay turns off: their elements are left out. hidden: std::collections::HashSet, + /// True colours, in the order colour words number them (1 first). + extended: Vec<[u8; 3]>, +} + +/// The true colours of the file: a zlib-packed UTF-16 record in the +/// non-model attributes stream reading +/// `…`. +fn extended_colors(cfb: &Cfb) -> Vec<[u8; 3]> { + let Some(attrs) = cfb.streams().iter().find(|(p, _)| p == "Dgn^NmA/$1").map(|(_, e)| inflate(&cfb.stream(e))) else { + return Vec::new(); + }; + for at in 0..attrs.len().saturating_sub(2) { + // A zlib header: deflate, and a check value divisible by 31. + let (a, b) = (attrs[at], attrs[at + 1]); + if a & 0x0f != 8 || ((a as u16) << 8 | b as u16) % 31 != 0 { + continue; + } + let mut out = Vec::new(); + if flate2::read::ZlibDecoder::new(&attrs[at..]).read_to_end(&mut out).is_err() || out.len() < 2 { + continue; + } + let units: Vec = out.chunks_exact(2).map(|c| u16::from_le_bytes([c[0], c[1]])).collect(); + let text = String::from_utf16_lossy(&units); + if !text.contains(" = rest.split(')').next()?.split(',').filter_map(|n| n.trim().parse().ok()).collect(); + (v.len() == 3).then(|| [v[0], v[1], v[2]]) + }) + .collect(); + } + Vec::new() } impl Styles { fn color(&self, e: &[u8]) -> [u8; 3] { // The colour word: an index into the colour table, 0xFFFFFFFF for - // the level's colour, or a true colour — n × 256 plus the index of - // its nearest table colour. - // ponytail: a true colour draws as that nearest index; the RGB it - // stands for is not read. + // the level's colour, or true colour n (from 1) × 256 plus the index + // of its nearest table colour, drawn in its own RGB when the file + // lists it. let explicit = u32_at(e, 52).unwrap_or(u32::MAX); let index = if explicit == u32::MAX { self.levels.get(&u32_at(e, 12).unwrap_or(0)).copied().unwrap_or(0) } else { + if let Some(rgb) = (explicit >> 8).checked_sub(1).and_then(|n| self.extended.get(n as usize)) { + return *rgb; + } explicit & 0xff }; self.colors.get(index as usize).copied().unwrap_or([255, 255, 255]) @@ -580,7 +618,7 @@ pub fn model(bytes: &[u8], name: &str, hidden: &[String]) -> Option { .filter(|l| hidden.iter().any(|h| h.eq_ignore_ascii_case(&l.1))) .map(|l| l.0) .collect(); - let styles = Styles { colors, levels, hidden }; + let styles = Styles { colors, levels, hidden, extended: extended_colors(&cfb) }; // Shared cell definitions: a type-34 element and its components. let mut defs = HashMap::new(); let mut i = 0; diff --git a/src/scene/model/underlay_vector/dwf.rs b/src/scene/model/underlay_vector/dwf.rs index 094f9b19a..a92733183 100644 --- a/src/scene/model/underlay_vector/dwf.rs +++ b/src/scene/model/underlay_vector/dwf.rs @@ -164,12 +164,69 @@ pub fn sheet(bytes: &[u8], name: &str) -> Option { w2d::read(data)? } else { let w2x = entry.w2x.as_ref().and_then(|p| files.get(p)); - xps::read(data, w2x.map(|v| v.as_slice()), entry)? + // An embedded font (FontUri, relative to the page) names its family. + let page_dir = entry.graphics.rsplit_once('/').map(|(d, _)| d.to_string()).unwrap_or_default(); + let family = |uri: &str| -> Option { + let uri = uri.trim_start_matches("./"); + let key = if let Some(abs) = uri.strip_prefix('/') { abs.to_string() } else { format!("{page_dir}/{uri}") }; + let data = files.get(&key)?; + let name = key.rsplit('/').next()?; + font_family(&deobfuscate(data, name)?) + }; + xps::read(data, w2x.map(|v| v.as_slice()), entry, &family)? }; let rect = view.or_else(|| paths_bounds(&paths))?; Some(Sheet { rect, paths, texts, sub_per_master: 1.0 }) } +/// An XPS obfuscated font (.odttf): its first 32 bytes are XORed with the +/// GUID of its file name, byte order reversed. +fn deobfuscate(data: &[u8], file_name: &str) -> Option> { + let hex: String = file_name.split('.').next()?.chars().filter(|c| c.is_ascii_hexdigit()).collect(); + if hex.len() != 32 || data.len() < 32 { + return None; + } + let bytes: Vec = (0..16).filter_map(|i| u8::from_str_radix(&hex[i * 2..i * 2 + 2], 16).ok()).collect(); + let key: Vec = bytes.into_iter().rev().collect(); + let mut out = data.to_vec(); + for i in 0..32 { + out[i] ^= key[i % 16]; + } + Some(out) +} + +/// The family name (name id 1) of a TrueType font. +fn font_family(font: &[u8]) -> Option { + let u16be = |at: usize| Some(u16::from_be_bytes(font.get(at..at + 2)?.try_into().ok()?)); + let u32be = |at: usize| Some(u32::from_be_bytes(font.get(at..at + 4)?.try_into().ok()?)); + let tables = u16be(4)? as usize; + let name = (0..tables).find_map(|t| { + let at = 12 + t * 16; + (font.get(at..at + 4)? == b"name").then(|| u32be(at + 8)).flatten() + })? as usize; + let count = u16be(name + 2)? as usize; + let strings = name + u16be(name + 4)? as usize; + let mut fallback = None; + for r in 0..count { + let at = name + 6 + r * 12; + let (platform, name_id) = (u16be(at)?, u16be(at + 6)?); + if name_id != 1 { + continue; + } + let (len, off) = (u16be(at + 8)? as usize, u16be(at + 10)? as usize); + let raw = font.get(strings + off..strings + off + len)?; + match platform { + // Windows: UTF-16 big endian. + 3 | 0 => { + let units: Vec = raw.chunks_exact(2).map(|c| u16::from_be_bytes([c[0], c[1]])).collect(); + return Some(String::from_utf16_lossy(&units)); + } + _ => fallback = Some(String::from_utf8_lossy(raw).to_string()), + } + } + fallback +} + /// The default colour map of a W2D stream: ten system colours, a 6×6×6 /// colour cube from index 10 (red 36 steps, green 6, blue 1), a grey ramp /// at 226-245 and ten more system colours (measured against plotted files: @@ -617,7 +674,12 @@ mod xps { } /// The fixed page's paths and text in model units, and the plotted view. - pub fn read(page: &[u8], w2x: Option<&[u8]>, entry: &SheetEntry) -> Option<(Vec, Option<[f64; 4]>, Vec)> { + pub fn read( + page: &[u8], + w2x: Option<&[u8]>, + entry: &SheetEntry, + family: &dyn Fn(&str) -> Option, + ) -> Option<(Vec, Option<[f64; 4]>, Vec)> { let mut units = Units::identity(); let mut view_log = None; if let Some(w2x) = w2x.and_then(|b| std::str::from_utf8(b).ok()) { @@ -660,9 +722,7 @@ mod xps { }; if node.tag_name().name() == "Glyphs" { // Text: its baseline origin and em size in page units (y - // down), drawn in the plotted family. - // ponytail: the embedded font is obfuscated and not read; - // DWFx text is drawn in Arial. + // down), drawn in the embedded font's family. let num = |name: &str| attr(node, name).and_then(|v| v.parse::().ok()); let (Some(em), Some(text)) = (num("FontRenderingEmSize"), attr(node, "UnicodeString")) else { return }; let o = [num("OriginX").unwrap_or(0.0), num("OriginY").unwrap_or(0.0)]; @@ -677,7 +737,7 @@ mod xps { width_factor: if len(up) > 0.0 { len(along) / len(up) } else { 1.0 }, rotation: (along[1] - a[1]).atan2(along[0] - a[0]), color: attr(node, "Fill").and_then(parse_color).unwrap_or([0, 0, 0]), - font: "Arial".to_string(), + font: attr(node, "FontUri").and_then(family).unwrap_or_else(|| "Arial".to_string()), }); } return; diff --git a/src/scene/model/underlay_vector/mod.rs b/src/scene/model/underlay_vector/mod.rs index b0073acae..09809956f 100644 --- a/src/scene/model/underlay_vector/mod.rs +++ b/src/scene/model/underlay_vector/mod.rs @@ -93,15 +93,31 @@ pub fn sheet_without(kind: UnderlayType, path: &str, item: &str, hidden: &[Strin UnderlayType::Dgn => dgn8::model(&bytes, item, hidden).unwrap_or_default(), UnderlayType::Pdf => return None, }; - outline_texts(&mut sheet); - // A DGN model's extent is its geometry, text included (a DWF sheet - // keeps its plotted view). + // A DGN model's extent includes its text: each text's box runs its + // length along the baseline and 1.5 × its height above it (as the + // reference measures a model). A DWF sheet keeps its plotted view. if kind == UnderlayType::Dgn { - if let Some(b) = model::paths_bounds(&sheet.paths) { - let r = sheet.rect; - sheet.rect = [r[0].min(b[0]), r[1].min(b[1]), r[2].max(b[2]), r[3].max(b[3])]; + let mut r = sheet.rect; + for t in &sheet.texts { + let (strokes, _) = crate::scene::text::lff::tessellate_text_ex( + [0.0, 0.0], + t.height as f32, + 0.0, + t.width_factor as f32, + 0.0, + &t.font, + &t.text, + ); + let length = strokes.iter().flatten().map(|p| p[0] as f64).fold(0.0, f64::max); + let (c, s) = (t.rotation.cos(), t.rotation.sin()); + for [x, y] in [[0.0, 0.0], [length, 0.0], [length, 1.5 * t.height], [0.0, 1.5 * t.height]] { + let p = [t.origin[0] + x * c - y * s, t.origin[1] + x * s + y * c]; + r = [r[0].min(p[0]), r[1].min(p[1]), r[2].max(p[0]), r[3].max(p[1])]; + } } + sheet.rect = r; } + outline_texts(&mut sheet); if !hidden.is_empty() { sheet.rect = sheet_without(kind, path, item, &[])?.rect; } @@ -206,10 +222,13 @@ fn rasterize(sheet: &Sheet, side: f64) -> Option { if let Some((c, width)) = path.stroke { let mut paint = tiny_skia::Paint::default(); paint.set_color_rgba8(c[0], c[1], c[2], 255); - paint.anti_alias = true; + // A negative width is in pixels (DGN line weights). + let px = if width < 0.0 { (-width) as f32 } else { ((width * scale) as f32).max(1.0) }; + // Hairlines are drawn solid, one pixel wide, as the reference + // draws them; anti-aliasing would spread them to half intensity. + paint.anti_alias = px > 1.5; let stroke = tiny_skia::Stroke { - // A negative width is in pixels (DGN line weights). - width: if width < 0.0 { (-width) as f32 } else { ((width * scale) as f32).max(1.0) }, + width: px, line_cap: tiny_skia::LineCap::Round, line_join: tiny_skia::LineJoin::Round, ..Default::default() diff --git a/src/ui/window/pdf_dialogs.rs b/src/ui/window/pdf_dialogs.rs index 7c72a5bb1..cee3e71f4 100644 --- a/src/ui/window/pdf_dialogs.rs +++ b/src/ui/window/pdf_dialogs.rs @@ -24,6 +24,8 @@ pub enum PdfDialogMsg { AttachName(String), AttachBrowse, AttachPage(usize), + /// DGN conversion units: sub (true) or master. + AttachSubUnits(bool), AttachPathType(PathTypeChoice), AttachInsertOnScreen(bool), AttachInsert(usize, String), @@ -134,6 +136,23 @@ pub fn page_thumbs(path: &str) -> Vec { .collect() } +/// Thumbnails of a file's pages (PDF), sheets (DWF) or models (DGN). +pub fn item_thumbs(kind: codec::entities::UnderlayType, path: &str) -> Vec { + use crate::scene::model::underlay_vector; + if kind == codec::entities::UnderlayType::Pdf { + return page_thumbs(path); + } + underlay_vector::item_names(kind, path) + .unwrap_or_default() + .into_iter() + .map(|label| { + let image = underlay_vector::display_raster(kind, path, &label, &[], 220.0) + .map(|page| image::Handle::from_rgba(page.width, page.height, page.pixels.to_vec())); + PageThumb { label, image } + }) + .collect() +} + /// Ctrl adds or removes a page, Shift takes the range from the last click, /// a plain click selects that page alone. pub fn click_page(selected: &mut Vec, anchor: &mut usize, page: usize, ctrl: bool, shift: bool) { @@ -413,6 +432,12 @@ impl Default for AttachMemory { pub static ATTACH_MEMORY: std::sync::Mutex> = std::sync::Mutex::new(None); pub struct PdfAttachState { + /// PDF pages, DWF sheets or DGN models. + pub kind: codec::entities::UnderlayType, + /// DGN: sub units chosen (the scale then turns them into master units). + pub sub_units: bool, + /// DGN: sub units per master unit of the chosen model. + pub sub_per_master: f64, /// The file read (absolute). pub path: String, pub name: String, @@ -444,6 +469,9 @@ impl PdfAttachState { .unwrap_or_default(); let names = existing.iter().map(|(name, _)| name.clone()).collect(); Self { + kind: codec::entities::UnderlayType::Pdf, + sub_units: false, + sub_per_master: 1.0, path: String::new(), name: String::new(), existing, @@ -501,9 +529,13 @@ pub fn view_attach<'a>( .size(12) .padding([5, 8]) .width(Fill); - let count = container( - text(crate::tf!("{count} pages", count = state.pages.len())).size(11).style(accent_text), - ) + use codec::entities::UnderlayType; + let count_text = match state.kind { + UnderlayType::Pdf => crate::tf!("{count} pages", count = state.pages.len()), + UnderlayType::Dwf => crate::tf!("{count} sheets", count = state.pages.len()), + UnderlayType::Dgn => crate::tf!("{count} models", count = state.pages.len()), + }; + let count = container(text(count_text).size(11).style(accent_text)) .padding([4, 10]) .style(well_style); let file = row![ @@ -519,15 +551,20 @@ pub fn view_attach<'a>( // Pages: the thumbnails, then which ones are chosen. let chosen = state.page_labels().join(", "); + let (items_title, chosen_text, size_label) = match state.kind { + UnderlayType::Pdf => (t!("Pages"), crate::tf!("Selected pages: {pages}", pages = chosen), t!("Page size:")), + UnderlayType::Dwf => (t!("Sheets"), crate::tf!("Selected sheet: {sheet}", sheet = chosen), t!("Sheet size:")), + UnderlayType::Dgn => (t!("Models"), crate::tf!("Selected model: {model}", model = chosen), t!("Model size:")), + }; let pages = card( - t!("Pages").into_owned(), + items_title.into_owned(), column![ container(page_tiles(&state.pages, &state.selected, PdfDialogMsg::AttachPage)) .padding(6) .height(Length::Fixed(226.0)) .width(Fill) .style(well_style), - text(crate::tf!("Selected pages: {pages}", pages = chosen)).size(11).style(muted_style), + text(chosen_text).size(11).style(muted_style), ] .spacing(6), ); @@ -588,14 +625,35 @@ pub fn view_attach<'a>( column![ info_line(t!("Found in:").into_owned(), &state.found_in), info_line(t!("Saved path:").into_owned(), &state.saved_path), - info_line(t!("Page size:").into_owned(), &state.page_size), + info_line(size_label.into_owned(), &state.page_size), ] .spacing(4), ); + let mut right = column![placement, path].spacing(8).width(Length::FillPortion(2)); + // A DGN model is attached in its master or its sub units. + if state.kind == UnderlayType::Dgn { + right = right.push(card( + t!("Conversion units").into_owned(), + column![ + segmented( + vec![(false, t!("Master units").into_owned()), (true, t!("Sub units").into_owned())], + state.sub_units, + PdfDialogMsg::AttachSubUnits, + ), + text(crate::tf!( + "Sub units make the default scale {scale}", + scale = 1.0 / state.sub_per_master.max(1e-12) + )) + .size(11) + .style(muted_style), + ] + .spacing(6), + )); + } let body = row![ column![pages, details].spacing(8).width(Length::FillPortion(3)), - column![placement, path].spacing(8).width(Length::FillPortion(2)), + right, ] .spacing(10);