From 0e1e13e5241a1e762893ca600aacb20ce525fe13 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 09:10:17 +0300 Subject: [PATCH 01/17] WIP: Sheet Set Manager palette, wizard, properties and sheet set fields Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 1 - Cargo.toml | 3 + locales/ar-SA/opencadstudio.ftl | 96 +++ locales/bg-BG/opencadstudio.ftl | 96 +++ locales/cs-CZ/opencadstudio.ftl | 96 +++ locales/de-DE/opencadstudio.ftl | 96 +++ locales/el-GR/opencadstudio.ftl | 96 +++ locales/en-US/opencadstudio.ftl | 96 +++ locales/es-ES/opencadstudio.ftl | 96 +++ locales/fi-FI/opencadstudio.ftl | 96 +++ locales/fr-FR/opencadstudio.ftl | 96 +++ locales/hi-IN/opencadstudio.ftl | 96 +++ locales/hu-HU/opencadstudio.ftl | 96 +++ locales/it-IT/opencadstudio.ftl | 96 +++ locales/ja-JP/opencadstudio.ftl | 96 +++ locales/ko-KR/opencadstudio.ftl | 96 +++ locales/nl-NL/opencadstudio.ftl | 96 +++ locales/pl-PL/opencadstudio.ftl | 96 +++ locales/pt-BR/opencadstudio.ftl | 96 +++ locales/ru-RU/opencadstudio.ftl | 96 +++ locales/tr-TR/opencadstudio.ftl | 96 +++ locales/zh-CN/opencadstudio.ftl | 96 +++ locales/zh-TW/opencadstudio.ftl | 96 +++ src/app/commands/display.rs | 6 - src/app/commands/field.rs | 17 +- src/app/commands/mod.rs | 13 + src/app/commands/sheet_set.rs | 1277 +++++++++++++++++++++++++++++++ src/app/control/actions.rs | 98 +++ src/app/mod.rs | 13 + src/app/settings.rs | 4 + src/app/update/dialog.rs | 5 + src/app/update/file.rs | 6 +- src/app/update/mod.rs | 5 + src/app/view/mod.rs | 4 + src/app/view/modal.rs | 9 + src/entities/field.rs | 18 + src/locale_catalog.rs | 96 +++ src/modules/view/sheetset.rs | 2 +- src/ui/dock.rs | 5 + src/ui/ribbon/mod.rs | 7 + src/ui/ribbon/widgets.rs | 2 + src/ui/window/field_dialog.rs | 186 +++++ src/ui/window/mod.rs | 1 + src/ui/window/sheet_set.rs | 968 +++++++++++++++++++++++ 44 files changed, 4752 insertions(+), 10 deletions(-) create mode 100644 src/app/commands/sheet_set.rs create mode 100644 src/ui/window/sheet_set.rs diff --git a/Cargo.lock b/Cargo.lock index 51294fb85..a42335e89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,7 +4206,6 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" -source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=d96e3fa#d96e3fa2fe5acbeac966f1db4c01142618bf9c79" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 85cc56ce7..514f98106 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,9 @@ objc2-app-kit = { version = "0.2", features = ["NSApplication", "NSResponder", " block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } +[patch."https://github.com/HakanSeven12/opencadcodec.git"] +opencadcodec = { path = "../cadcodec-sheetset" } + [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } iced_widget = { 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 97fec3890..b1790f958 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4352,6 +4352,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = تجميد | T = إلغاء التجميد | Enter = خروج view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK حدد نقطة الإدراج: .table-block-definition-not-found = TABLE: لم يتم العثور على تعريف الكتلة. .table-edit-applied = TABLE: تم تطبيق التعديل. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 0fb1d43e9..306ace996 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4339,6 +4339,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F <слой> = Замрази | T <слой> = Размрази | Enter = Изход view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Посочете точка на вмъкване: .table-block-definition-not-found = TABLE: дефиницията на блока не е намерена. .table-edit-applied = TABLE: редакцията е приложена. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index d17ecfb1d..6e04fa9d0 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4337,6 +4337,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Zmrazit | T = Rozmrazit | Enter = Konec view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Určete bod vložení: .table-block-definition-not-found = TABLE: definice bloku nebyla nalezena. .table-edit-applied = TABLE: úprava byla použita. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 7fd9f0bd4..b16b87501 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4338,6 +4338,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Einfrieren | T = Auftauen | Eingabe = Beenden view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Einfügepunkt angeben: .table-block-definition-not-found = TABLE: Blockdefinition nicht gefunden. .table-edit-applied = TABLE: Bearbeitung angewendet. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 53601f4be..571dea0e2 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4340,6 +4340,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F <επίπεδο> = Πάγωμα | T <επίπεδο> = Ξεπάγωμα | Enter = Έξοδος view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .message = __ocs_fmt_0__ .code-page = Κωδικοσελίδα: __ocs_fmt_0__ .created-julian-6 = Δημιουργία (Julian): __ocs_fmt_0__ diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index dcbbb7b69..026e3be67 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4341,6 +4341,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Freeze | T = Thaw | Enter = Exit view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .message = __ocs_fmt_0__ .code-page = Code page: __ocs_fmt_0__ .created-julian-6 = Created (Julian): __ocs_fmt_0__ diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index df1b19661..49474bbda 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4340,6 +4340,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Inutilizar | T = Reutilizar | Intro = Salir view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Especifique el punto de inserción: .table-block-definition-not-found = TABLE: no se encontró la definición de bloque. .table-edit-applied = TABLE: edición aplicada. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 99f18d514..18846481f 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4339,6 +4339,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Jäädytys | T = Sulatus | Enter = Poistu view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Määritä lisäyspiste: .table-block-definition-not-found = TABLE: lohkomääritystä ei löytynyt. .table-edit-applied = TABLE: muokkaus otettu käyttöön. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index bc00d9334..b8c592fa8 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4340,6 +4340,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Freeze , T = Thaw , Entrée = Sortie view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Spécifiez le point d’insertion : .table-block-definition-not-found = TABLE : définition de bloc introuvable. .table-edit-applied = TABLE : modification appliquée. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 59440e35e..47fcb2c81 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4328,6 +4328,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F <लेयर> = फ़्रीज़ | T <लेयर> = थॉ | Enter = बाहर निकलें view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK सम्मिलन बिंदु निर्दिष्ट करें: .table-block-definition-not-found = TABLE: ब्लॉक परिभाषा नहीं मिली। .table-edit-applied = TABLE: संपादन लागू किया गया। diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 2066c5156..81c064f6f 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4339,6 +4339,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Fagyasztás | T = Felengedés | Enter = Kilépés view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Adja meg a beszúrási pontot: .table-block-definition-not-found = TABLE: a blokkdefiníció nem található. .table-edit-applied = TABLE: a módosítás alkalmazva. diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index bab01a81c..0137d3a34 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4339,6 +4339,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Congela | T = Scongela | Enter = Esci view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Specificare il punto di inserimento: .table-block-definition-not-found = TABLE: definizione del blocco non trovata. .table-edit-applied = TABLE: modifica applicata. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index f0df48710..101252168 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4328,6 +4328,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = フリーズ | T = フリーズ解除 | Enter = 終了 view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK 挿入点を指定: .table-block-definition-not-found = TABLE: ブロック定義が見つかりません。 .table-edit-applied = TABLE: 編集を適用しました。 diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 6ad109cc7..02604f8ec 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4339,6 +4339,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = 동결 | T = 해동 | Enter = 종료 view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK 삽입점 지정: .table-block-definition-not-found = TABLE: 블록 정의를 찾을 수 없습니다. .table-edit-applied = TABLE: 편집을 적용했습니다. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 2f5e39e39..232c1de7f 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4338,6 +4338,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Bevriezen | T = Ontdooien | Enter = Afsluiten view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Geef invoegpunt op: .table-block-definition-not-found = TABLE: blokdefinitie niet gevonden. .table-edit-applied = TABLE: bewerking toegepast. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index c31421220..bc0e67dd0 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4339,6 +4339,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Zamrożenie | T = Odwilż | Enter = Wyjście view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Wskaż punkt wstawienia: .table-block-definition-not-found = TABLE: nie znaleziono definicji bloku. .table-edit-applied = TABLE: zastosowano edycję. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index e96dea04d..c7c1934df 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4340,6 +4340,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Congelar | T = Descongelar | Enter = Sair view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Especifique o ponto de inserção: .table-block-definition-not-found = TABLE: definição de bloco não encontrada. .table-edit-applied = TABLE: edição aplicada. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index e3eb50fa6..63a92d307 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4341,6 +4341,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F <слой> = заморозить | T <слой> = разморозить | Enter = выход view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK Укажите точку вставки: .table-block-definition-not-found = TABLE: определение блока не найдено. .table-edit-applied = TABLE: изменение применено. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 3b01d7c2c..662134e13 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4300,6 +4300,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F = Dondur | T = Çöz | Enter = Çık view = + .ss-create-sheet-set = Sayfa Kümesi Oluştur + .ss-new-sheet = Yeni Sayfa + .ss-subset-properties = Alt Küme Özellikleri + .ss-rename-renumber-sheet = Sayfayı Yeniden Adlandır ve Numaralandır + .ss-import-layout-as-sheet = Düzeni Sayfa Olarak İçe Aktar + .ss-sheet-set-manager = Sayfa Kümesi Yöneticisi + .ss-open = Aç... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Bir sayfa kümesi açın veya NEWSHEETSET ile yeni bir küme oluşturun. + .ss-no-sheet-views = Sayfa görünümü yok. + .ss-no-locations = Konum yok. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Çift tık sayfayı açar. Diğer komutlar için sağ tıklayın. + .ss-an-empty-sheet-set = Boş sayfa kümesi + .ss-existing-drawings = Var olan çizimler + .ss-create-a-folder-hierarchy-based-on-subsets = Alt kümelere göre klasör hiyerarşisi oluştur + .ss-sheet-set-properties = Sayfa Kümesi Özellikleri... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Çizim içeren klasörleri seçin. Çizimlerdeki düzenler sayfa kümesine eklenebilir. + .ss-sheet-set = Sayfa Kümesi + .ss-sheet-set-data-file = Sayfa kümesi veri dosyası + .ss-sheets-to-import = İçe aktarılacak sayfalar + .ss-add-custom-property = Özel Özellik Ekle... + .ss-new-sheet-x = Yeni Sayfa... + .ss-new-subset = Yeni Alt Küme... + .ss-import-layout-as-sheet-x = Düzeni Sayfa Olarak İçe Aktar... + .ss-rename-renumber = Yeniden Adlandır ve Numaralandır... + .ss-close-sheet-set = Sayfa Kümesini Kapat + .ss-properties = Özellikler... + .ss-sheet-list = Sayfa Listesi + .ss-sheet-views = Sayfa Görünümleri + .ss-model-views = Model Görünümleri + .ss-views-by-sheet = Sayfaya göre görünümler + .ss-locations = Konumlar + .ss-create-a-sheet-set-using = Sayfa kümesini şununla oluştur + .ss-name-of-new-sheet-set = Yeni sayfa kümesinin adı: + .ss-description-optional = Açıklama (isteğe bağlı): + .ss-store-sheet-set-data-file-dst-here = Sayfa kümesi veri dosyasını (.dst) buraya kaydet: + .ss-choose-layouts = Düzenleri Seç + .ss-confirm = Onayla + .ss-default-value = Varsayılan değer: + .ss-owner = Sahip + .ss-custom-properties = Özel Özellikler + .ss-sheet-title = Sayfa başlığı + .ss-folder-path = Klasör yolu + .ss-sheet-template = Sayfa şablonu + .ss-subset-name = Alt küme adı + .ss-new-sheet-location = Yeni sayfa konumu + .ss-remove-subset = Alt Kümeyi Kaldır + .ss-remove-sheet = Sayfayı Kaldır + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Çizim içeren bir veya daha fazla klasör belirtmenizi sağlar. Bu çizimlerdeki düzenler sayfa kümesine otomatik olarak aktarılabilir. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Alt kümesi ve sayfası olmayan bir sayfa kümesi oluşturur. + .ss-open-sheet-set = Sayfa Kümesini Aç + .ss-sheet-set-dst = Sayfa Kümesi (*.dst) + .ss-browse-for-folder = Klasöre Gözat + .ss-select-layout-as-sheet-template = Sayfa Şablonu Olarak Düzen Seç + .ss-drawing-template-dwt = Çizim Şablonu (*.dwt) + .ss-drawing-dwg = Çizim (*.dwg) + .ss-the-name-cannot-be-empty = Ad boş olamaz. + .ss-a-custom-property-with-this-name-already-exists = Bu adda bir özel özellik zaten var. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sayfa kümeleri birden çok çizimdeki düzenleri adlandırılmış tek bir sayfa grubunda toplar. + .ss-default-new-drawing = (varsayılan yeni çizim) + .ss-select-drawing = Çizim Seç + .ss-the-sheet-set-name-cannot-be-empty = Sayfa kümesi adı boş olamaz. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = Sayfa kümesi veri dosyasının klasörü yok. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = Çizimde sayfa şablonu olarak kullanılabilecek düzen yok. + .ss-the-subset-name-cannot-be-empty = Alt küme adı boş olamaz. + .ss-the-drawing-has-no-layout-to-import = Çizimde içe aktarılacak düzen yok. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = Bu düzen zaten bu sayfa kümesinin bir sayfası. + .ss-the-sheet-title-cannot-be-empty = Sayfa başlığı boş olamaz. + .ss-the-sheet-folder-cannot-be-created = Sayfa klasörü oluşturulamıyor. + .ss-sheet-set-properties-x = Sayfa Kümesi Özellikleri + .ss-sheet-properties = Sayfa Özellikleri + .ss-sheet-drawing-not-found = Sayfa çizimi bulunamadı: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ zaten var. + .ss-model-view = Model görünümü + .ss-label-block-for-views = Görünümler için etiket bloğu + .ss-callout-blocks = Belirtme blokları + .ss-page-setup-overrides-file = Sayfa düzeni geçersiz kılma dosyası + .ss-project-control = Proje Denetimi + .ss-sheet-creation = Sayfa Oluşturma + .ss-sheet-storage-location = Sayfa kayıt konumu + .ss-sheet-creation-template = Sayfa oluşturma şablonu + .ss-prompt-for-template = Şablon için sor + .ss-subset = Alt Küme + .ss-layout-name = Düzen adı + .ss-drawing-file = Çizim dosyası + .ss-project-number = Proje numarası + .ss-project-name = Proje adı + .ss-project-phase = Proje evresi + .ss-project-milestone = Proje aşaması + .ss-revision-number = Revizyon numarası + .ss-revision-date = Revizyon tarihi + .ss-issue-purpose = Yayın amacı + .ss-custom-property-name = Özel özellik adı + .ss-placeholder-type = Yer tutucu türü + .ss-sheet-set-x = Sayfa kümesi + .ss-sheet-navigation-tree = Sayfa gezinme ağacı + .ss-sheetset = Sayfa Kümesi .message = __ocs_fmt_0__ .code-page = Kod sayfası: __ocs_fmt_0__ .created-julian-6 = Oluşturulma (Jülyen): __ocs_fmt_0__ diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 290f64be0..2c74a6ab2 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4323,6 +4323,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F <图层> = 冻结 | T <图层> = 解冻 | Enter = 退出 view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK 指定插入点: .table-block-definition-not-found = TABLE:未找到块定义。 .table-edit-applied = TABLE:已应用编辑。 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index c38cc4a04..db069f401 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4335,6 +4335,102 @@ layout = .vplayer-f-layer-freeze-t-layer-thaw-enter-exit = VPLAYER F <圖層> = 凍結 | T <圖層> = 解凍 | Enter = 退出 view = + .ss-create-sheet-set = Create Sheet Set + .ss-new-sheet = New Sheet + .ss-subset-properties = Subset Properties + .ss-rename-renumber-sheet = Rename & Renumber Sheet + .ss-import-layout-as-sheet = Import Layout as Sheet + .ss-sheet-set-manager = Sheet Set Manager + .ss-open = Open... + .ss-open-a-sheet-set-or-create-one-with-newsheetset = Open a sheet set or create one with NEWSHEETSET. + .ss-no-sheet-views = No sheet views. + .ss-no-locations = No locations. + .ss-double-click-opens-a-sheet-right-click-for-more-commands = Double-click opens a sheet. Right-click for more commands. + .ss-an-empty-sheet-set = An empty sheet set + .ss-existing-drawings = Existing drawings + .ss-create-a-folder-hierarchy-based-on-subsets = Create a folder hierarchy based on subsets + .ss-sheet-set-properties = Sheet Set Properties... + .ss-select-folders-containing-drawings-layouts-in-the-drawings-c = Select folders containing drawings. Layouts in the drawings can be added to the sheet set. + .ss-sheet-set = Sheet Set + .ss-sheet-set-data-file = Sheet set data file + .ss-sheets-to-import = Sheets to import + .ss-add-custom-property = Add Custom Property... + .ss-new-sheet-x = New Sheet... + .ss-new-subset = New Subset... + .ss-import-layout-as-sheet-x = Import Layout as Sheet... + .ss-rename-renumber = Rename & Renumber... + .ss-close-sheet-set = Close Sheet Set + .ss-properties = Properties... + .ss-sheet-list = Sheet List + .ss-sheet-views = Sheet Views + .ss-model-views = Model Views + .ss-views-by-sheet = Views by sheet + .ss-locations = Locations + .ss-create-a-sheet-set-using = Create a sheet set using + .ss-name-of-new-sheet-set = Name of new sheet set: + .ss-description-optional = Description (optional): + .ss-store-sheet-set-data-file-dst-here = Store sheet set data file (.dst) here: + .ss-choose-layouts = Choose Layouts + .ss-confirm = Confirm + .ss-default-value = Default value: + .ss-owner = Owner + .ss-custom-properties = Custom Properties + .ss-sheet-title = Sheet title + .ss-folder-path = Folder path + .ss-sheet-template = Sheet template + .ss-subset-name = Subset name + .ss-new-sheet-location = New sheet location + .ss-remove-subset = Remove Subset + .ss-remove-sheet = Remove Sheet + .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. + .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. + .ss-open-sheet-set = Open Sheet Set + .ss-sheet-set-dst = Sheet Set (*.dst) + .ss-browse-for-folder = Browse for Folder + .ss-select-layout-as-sheet-template = Select Layout as Sheet Template + .ss-drawing-template-dwt = Drawing Template (*.dwt) + .ss-drawing-dwg = Drawing (*.dwg) + .ss-the-name-cannot-be-empty = The name cannot be empty. + .ss-a-custom-property-with-this-name-already-exists = A custom property with this name already exists. + .ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n = Sheet sets organize layouts from several drawings into one named set of sheets. + .ss-default-new-drawing = (default new drawing) + .ss-select-drawing = Select Drawing + .ss-the-sheet-set-name-cannot-be-empty = The sheet set name cannot be empty. + .ss-the-folder-for-the-sheet-set-data-file-does-not-exist = The folder for the sheet set data file does not exist. + .ss-the-drawing-has-no-layout-to-use-as-a-sheet-template = The drawing has no layout to use as a sheet template. + .ss-the-subset-name-cannot-be-empty = The subset name cannot be empty. + .ss-the-drawing-has-no-layout-to-import = The drawing has no layout to import. + .ss-the-layout-is-already-a-sheet-of-this-sheet-set = The layout is already a sheet of this sheet set. + .ss-the-sheet-title-cannot-be-empty = The sheet title cannot be empty. + .ss-the-sheet-folder-cannot-be-created = The sheet folder cannot be created. + .ss-sheet-set-properties-x = Sheet Set Properties + .ss-sheet-properties = Sheet Properties + .ss-sheet-drawing-not-found = Sheet drawing not found: __ocs_fmt_0__ + .ss-already-exists = __ocs_fmt_0__ already exists. + .ss-model-view = Model view + .ss-label-block-for-views = Label block for views + .ss-callout-blocks = Callout blocks + .ss-page-setup-overrides-file = Page setup overrides file + .ss-project-control = Project Control + .ss-sheet-creation = Sheet Creation + .ss-sheet-storage-location = Sheet storage location + .ss-sheet-creation-template = Sheet creation template + .ss-prompt-for-template = Prompt for template + .ss-subset = Subset + .ss-layout-name = Layout name + .ss-drawing-file = Drawing file + .ss-project-number = Project number + .ss-project-name = Project name + .ss-project-phase = Project phase + .ss-project-milestone = Project milestone + .ss-revision-number = Revision number + .ss-revision-date = Revision date + .ss-issue-purpose = Issue purpose + .ss-custom-property-name = Custom property name + .ss-placeholder-type = Placeholder type + .ss-sheet-set-x = Sheet set + .ss-sheet-navigation-tree = Sheet navigation tree + .ss-sheetset = SheetSet .datalink-specify-insertion-point = DATALINK 指定插入點: .table-block-definition-not-found = TABLE:找不到圖塊定義。 .table-edit-applied = TABLE:已套用編輯。 diff --git a/src/app/commands/display.rs b/src/app/commands/display.rs index b979fec69..41fb964c3 100644 --- a/src/app/commands/display.rs +++ b/src/app/commands/display.rs @@ -587,12 +587,6 @@ impl OpenCADStudio { .push_info(crate::t!("TOOLPALETTES: Tool Palettes not yet implemented.").as_ref()); } - // ── SHEETSET — not yet implemented ─────────────────────────────────── - "SHEETSET" => { - self.command_line - .push_info(crate::t!("SHEETSET: Sheet Set Manager not yet implemented.").as_ref()); - } - // ── XDATA — read/write extended entity data ────────────────────────── // XDATA LIST — show all xdata records on selected entities // XDATA SET — append a string xdata value for diff --git a/src/app/commands/field.rs b/src/app/commands/field.rs index 5c084fc78..aa14b0516 100644 --- a/src/app/commands/field.rs +++ b/src/app/commands/field.rs @@ -46,6 +46,13 @@ impl OpenCADStudio { .iter() .map(|f| self.field_value(&format!("\\AcVar Date \\f \"{f}\""), &[])) .collect(); + if let Some(db) = self.sheet_set.db() { + let custom = codec::sheet_set::custom_properties(db.sheet_set()); + let names = |flag: i32| custom.iter().filter(|(_, _, f)| f & flag != 0).map(|(n, _, _)| n.clone()).collect(); + state.ss_sheet_custom = names(codec::sheet_set::CUSTOM_SHEET_PROP); + state.ss_set_custom = names(codec::sheet_set::CUSTOM_SHEET_SET_PROP); + state.ss_set_name = db.name().to_string(); + } self.field_dialog = Some(state); self.fill_named_objects(); self.refresh_field_preview(); @@ -113,8 +120,16 @@ impl OpenCADStudio { } FieldDialogMsg::Name(n) => { state.name = n; - state.text_case = 0; + // Sheet set fields start in Title case. + state.text_case = if crate::ui::window::field_dialog::SHEET_SET_FIELDS.contains(&n) { 4 } else { 0 }; + state.ss_custom.clear(); } + FieldDialogMsg::SsCustom(name) => state.ss_custom = name, + FieldDialogMsg::SsPlaceholder(k) => { + state.ss_placeholder = k; + state.ss_custom.clear(); + } + FieldDialogMsg::SsScaleFormat(k) => state.ss_scale_format = k, FieldDialogMsg::DateFormat(f) => state.date_format = f, FieldDialogMsg::DateExample(k) => { if let Some(f) = DATE_FORMATS.get(k) { diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index 7b9f22f2b..325fa24ef 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -21,6 +21,7 @@ mod pc_colormap; mod pc_extract; mod pdf_underlay; mod pdf_dialogs; +pub(crate) mod sheet_set; mod xclip; mod blockvars; mod plotvars; @@ -293,6 +294,9 @@ impl OpenCADStudio { if let Some(t) = self.dispatch_attdef(cmd, i) { return Some(t); } + if let Some(t) = self.dispatch_sheet_set(cmd, i) { + return Some(t); + } if let Some(t) = self.dispatch_field(cmd, i) { return Some(t); } @@ -854,6 +858,15 @@ inventory::submit!(crate::command::CommandRegistration { "CREATESKETCH", "FINISHSKETCH", "SHEETSET", + "SHEETSETHIDE", + "NEWSHEETSET", + "OPENSHEETSET", + "SSMAUTOOPEN", + "SSLOCATE", + "SSMPOLLTIME", + "SSMSHEETSTATUS", + "SSMSTATE", + "SSFOUND", "SHORTCUTS", "SSM", "STEPOUT", diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs new file mode 100644 index 000000000..73d7ecef8 --- /dev/null +++ b/src/app/commands/sheet_set.rs @@ -0,0 +1,1277 @@ +//! Sheet sets: SHEETSET / SHEETSETHIDE / NEWSHEETSET / OPENSHEETSET, the +//! SSM* / SS* system variables, the Sheet Set Manager palette and its +//! dialogs, the drawing's sheet link written on save and located on open. + +use crate::app::{Message, OpenCADStudio}; +use crate::ui::window::sheet_set::{ + FieldId, Form, FormKind, FoundLayout, MenuAction, PropRow, Properties, RowKey, SheetSetMsg, SsDialog, + Wizard, WizardMsg, +}; +use codec::sheet_set::{self as ss, ComponentKind, LayoutReference, SheetSetData, SheetSetDatabase}; +use iced::Task; +use std::path::{Path, PathBuf}; + +/// The variables this family answers for. +pub(super) const SHEET_SET_SYSVARS: &[&str] = + &["SSMAUTOOPEN", "SSLOCATE", "SSMPOLLTIME", "SSMSHEETSTATUS", "SSMSTATE", "SSFOUND"]; + +/// Paper-space layouts of a drawing in tab order: name and handle (hex). +fn paper_layouts(doc: &codec::CadDocument) -> Vec<(String, String)> { + let mut layouts: Vec<(i16, String, String)> = doc + .objects + .values() + .filter_map(|o| match o { + codec::objects::ObjectType::Layout(l) if !l.name.eq_ignore_ascii_case("Model") => { + Some((l.tab_order, l.name.clone(), format!("{:X}", l.handle.value()))) + } + _ => None, + }) + .collect(); + layouts.sort(); + layouts.into_iter().map(|(_, n, h)| (n, h)).collect() +} + +/// UTC now as the reference stamps `UpdateTime`: `yyyy/MM/dd HH:mm:ss.fff`. +fn utc_stamp() -> String { + let ms = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map(|d| d.as_millis() as i64) + .unwrap_or(0); + let (days, rem) = (ms.div_euclid(86_400_000), ms.rem_euclid(86_400_000)); + // Civil date from days since 1970-01-01. + let z = days + 719_468; + let era = z.div_euclid(146_097); + let doe = z - era * 146_097; + let yoe = (doe - doe / 1460 + doe / 36_524 - doe / 146_096) / 365; + let doy = doe - (365 * yoe + yoe / 4 - yoe / 100); + let mp = (5 * doy + 2) / 153; + let d = doy - (153 * mp + 2) / 5 + 1; + let m = if mp < 10 { mp + 3 } else { mp - 9 }; + let y = yoe + era * 400 + i64::from(m <= 2); + format!( + "{y:04}/{m:02}/{d:02} {:02}:{:02}:{:02}.{:03}", + rem / 3_600_000, + rem / 60_000 % 60, + rem / 1000 % 60, + rem % 1000 + ) +} + +fn folder_of(path: &str) -> String { + Path::new(path).parent().map(|p| p.to_string_lossy().to_string()).unwrap_or_default() +} + +/// Template of a component for new sheets: its own `DefDwtLayout`, else +/// the nearest parent's. +fn template_of(db: &SheetSetDatabase, component: &str) -> Option { + let mut id = component.to_string(); + for _ in 0..32 { + if let Some(t) = db.layout_reference(&id, "DefDwtLayout") { + return Some(t); + } + id = db.parent_of(&id)?.id().to_string(); + } + None +} + +/// New sheet folder of a component: its `NewSheetLocation`, else the +/// nearest parent's, else the `.dst` folder. +fn sheet_folder_of(db: &SheetSetDatabase, component: &str) -> String { + let mut id = component.to_string(); + for _ in 0..32 { + if let Some(f) = db.file_reference(&id, "NewSheetLocation").filter(|f| !f.is_empty()) { + return f; + } + match db.parent_of(&id) { + Some(p) => id = p.id().to_string(), + None => break, + } + } + db.path.as_deref().map(folder_of).unwrap_or_default() +} + +impl OpenCADStudio { + pub(super) fn dispatch_sheet_set(&mut self, cmd: &str, i: usize) -> Option> { + let upper = cmd.trim().to_ascii_uppercase(); + let (verb, arg) = match upper.split_once(char::is_whitespace) { + Some((v, _)) => (v.to_string(), cmd.trim()[v.len()..].trim().to_string()), + None => (upper.clone(), String::new()), + }; + match verb.as_str() { + "SHEETSET" | "SSM" => { + self.show_sheet_set_manager(true); + Some(Task::none()) + } + "SHEETSETHIDE" => { + self.show_sheet_set_manager(false); + Some(Task::none()) + } + // The ribbon toggle (its macro picks SHEETSET or SHEETSETHIDE by SSMSTATE). + "_SSMTOGGLE" => { + let on = !self.sheet_set.show; + self.show_sheet_set_manager(on); + Some(Task::none()) + } + "NEWSHEETSET" => { + self.open_new_sheet_set_wizard(); + Some(Task::none()) + } + "OPENSHEETSET" => { + if !arg.is_empty() { + return Some(self.update(Message::SheetSet(SheetSetMsg::OpenPicked(Some(PathBuf::from( + arg.trim_matches('"'), + )))))); + } + Some(Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Open Sheet Set").as_ref()) + .add_filter(crate::t!("Sheet Set (*.dst)").as_ref(), &["dst", "DST"]) + .pick_file() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |p| Message::SheetSet(SheetSetMsg::OpenPicked(p)), + )) + } + _ => self.dispatch_sheet_set_vars(cmd, i), + } + } + + fn dispatch_sheet_set_vars(&mut self, cmd: &str, _i: usize) -> Option> { + let rest = cmd.strip_prefix("SETVAR ").map(str::trim).unwrap_or(cmd); + let mut parts = rest.splitn(2, char::is_whitespace); + let name = parts.next().unwrap_or("").to_ascii_uppercase(); + if !SHEET_SET_SYSVARS.contains(&name.as_str()) { + return None; + } + let value = parts.next().map(str::trim).filter(|v| !v.is_empty()); + let s = &mut self.sheet_set.settings; + match name.as_str() { + "SSMSTATE" => { + let v = u8::from(self.sheet_set.show); + self.command_line.push_output(&format!("SSMSTATE = {v} (read only)")); + } + "SSFOUND" => { + let v = self.sheet_set.found.clone(); + self.command_line.push_output(&format!("SSFOUND = \"{v}\" (read only)")); + } + _ => { + let (current, min, max) = match name.as_str() { + "SSMAUTOOPEN" => (i32::from(s.auto_open), 0, 1), + "SSLOCATE" => (i32::from(s.locate), 0, 1), + "SSMPOLLTIME" => (i32::from(s.poll_time), 20, 600), + _ => (i32::from(s.sheet_status), 0, 2), + }; + let ask = |app: &mut Self, name: String| { + app.command_line.push_output(&format!("Enter new value for {name} <{current}>:")); + app.pending_setvar = Some(name); + }; + match value { + None => ask(self, name), + Some(v) => match v.parse::() { + Err(_) => { + self.command_line.push_error("Requires an integer value."); + ask(self, name); + } + Ok(n) if !(min..=max).contains(&n) => { + self.command_line.push_error(&if max == 1 { + "Requires 0 or 1 only.".to_string() + } else { + format!("Requires an integer between {min} and {max}.") + }); + ask(self, name); + } + Ok(n) => { + let s = &mut self.sheet_set.settings; + match name.as_str() { + "SSMAUTOOPEN" => s.auto_open = n as u8, + "SSLOCATE" => s.locate = n as u8, + "SSMPOLLTIME" => s.poll_time = n as u16, + _ => s.sheet_status = n as u8, + } + self.persist_settings_if_changed(); + } + }, + } + } + } + Some(Task::none()) + } + + /// Show or hide the palette (docked on the right like the other + /// managers). + pub(in crate::app) fn show_sheet_set_manager(&mut self, on: bool) { + let id = crate::ui::dock::PanelId::SheetSetManager; + self.sheet_set.show = on; + self.ribbon.set_sheet_set(on); + if on { + if self.dock.location(id).is_none() { + self.dock.dock(id, crate::app::config::DockSide::Right, usize::MAX); + } + self.dock_expanded = Some(id); + } else if self.dock_expanded == Some(id) { + self.dock_expanded = None; + } + } + + /// Publish the open sets to the field engine and redraw every field. + fn sheet_sets_changed(&mut self) { + crate::entities::field::set_sheet_sets(self.sheet_set.sets.clone()); + for tab in &mut self.tabs { + let changes: Vec<_> = tab + .scene + .document + .entities() + .filter(|e| crate::entities::field::hosts_field(&tab.scene.document, e)) + .map(|e| (e.common().handle, crate::scene::ChangeKind::Modified)) + .collect(); + if !changes.is_empty() { + tab.scene.bump_entities(&changes); + } + } + } + + /// Open (or bring forward) the sheet set in `path`. + pub(in crate::app) fn open_sheet_set(&mut self, path: &Path, show: bool) -> Result<(), String> { + let key = ss::path_key(&path.to_string_lossy()); + if let Some(k) = self + .sheet_set + .sets + .iter() + .position(|db| db.path.as_deref().is_some_and(|p| ss::path_key(p) == key)) + { + self.sheet_set.current = Some(k); + } else { + let db = SheetSetDatabase::read(&path.to_string_lossy())?; + self.sheet_set.sets.push(db); + self.sheet_set.current = Some(self.sheet_set.sets.len() - 1); + } + if show { + self.show_sheet_set_manager(true); + } + self.sheet_sets_changed(); + Ok(()) + } + + /// Write the current set back to its `.dst`. + fn save_current_sheet_set(&mut self) { + let Some(db) = self.sheet_set.db_mut() else { + return; + }; + let Some(path) = db.path.clone() else { + return; + }; + if let Err(e) = db.write(&path) { + self.command_line.push_error(&format!("{path}: {e}")); + } + self.sheet_sets_changed(); + } + + fn open_new_sheet_set_wizard(&mut self) { + let mut n = 1; + let folder = dirs_next_documents(); + let name = loop { + let name = format!("New Sheet Set ({n})"); + if !Path::new(&folder).join(format!("{name}.dst")).exists() { + break name; + } + n += 1; + }; + let draft = SheetSetDatabase::new(&name, ""); + self.sheet_set.dialog = Some(SsDialog::Wizard(Wizard { + step: 0, + existing: false, + name, + description: String::new(), + folder, + hierarchy: false, + folders: Vec::new(), + layouts: Vec::new(), + draft, + error: None, + })); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + + /// Resolve a palette / automation target: a component id, a sheet + /// number or title, a subset name, or `set`. + pub(in crate::app) fn sheet_set_target(&self, spec: &str) -> Option { + let db = self.sheet_set.db()?; + if spec.eq_ignore_ascii_case("set") { + return Some(db.sheet_set().id().to_string()); + } + if db.find(spec).is_some() { + return Some(spec.to_string()); + } + fn go(el: &ss::Element, spec: &str) -> Option { + for c in &el.children { + match ComponentKind::of(c) { + Some(ComponentKind::Sheet) + if c.prop("Number") == Some(spec) + || c.prop("Title") == Some(spec) + || ss::number_and_title(c) == spec => + { + return Some(c.id().to_string()) + } + Some(ComponentKind::Subset) => { + if c.prop("Name") == Some(spec) { + return Some(c.id().to_string()); + } + if let Some(id) = go(c, spec) { + return Some(id); + } + } + _ => {} + } + } + None + } + go(db.sheet_set(), spec) + } + + fn component_kind(&self, id: &str) -> Option { + ComponentKind::of(self.sheet_set.db()?.find(id)?) + } + + pub(in crate::app) fn on_sheet_set(&mut self, m: SheetSetMsg) -> Task { + match m { + SheetSetMsg::PickSet(Some(k)) => { + if k < self.sheet_set.sets.len() { + self.sheet_set.current = Some(k); + } + } + SheetSetMsg::PickSet(None) => return self.dispatch_command("OPENSHEETSET"), + SheetSetMsg::Tab(t) => self.sheet_set.tab = t, + SheetSetMsg::Expand(id) => { + if !self.sheet_set.collapsed.remove(&id) { + self.sheet_set.collapsed.insert(id); + } + } + SheetSetMsg::Select(id) => self.sheet_set.selected = Some(id), + SheetSetMsg::Activate(id) => { + self.sheet_set.selected = Some(id.clone()); + if self.component_kind(&id) == Some(ComponentKind::Sheet) { + return self.open_sheet(&id); + } + if !self.sheet_set.collapsed.remove(&id) { + self.sheet_set.collapsed.insert(id); + } + } + SheetSetMsg::Refresh => { + if let Some(path) = self.sheet_set.db().and_then(|db| db.path.clone()) { + match SheetSetDatabase::read(&path) { + Ok(db) => { + if let Some(slot) = self.sheet_set.db_mut() { + *slot = db; + } + self.sheet_sets_changed(); + } + Err(e) => self.command_line.push_error(&format!("{path}: {e}")), + } + } + } + SheetSetMsg::Menu(id, action) => return self.sheet_set_menu(id, action), + SheetSetMsg::OpenPicked(Some(path)) => { + if let Err(e) = self.open_sheet_set(&path, true) { + self.command_line.push_error(&format!("{}: {e}", path.display())); + } + } + SheetSetMsg::OpenPicked(None) | SheetSetMsg::ImportPicked(None) | SheetSetMsg::TemplatePicked(None) => {} + SheetSetMsg::ImportPicked(Some(path)) => self.import_layout_form(path), + SheetSetMsg::FolderPicked(field, Some(path)) => self.dialog_folder(field, path), + SheetSetMsg::FolderPicked(_, None) => {} + SheetSetMsg::TemplatePicked(Some(path)) => self.properties_template(path), + SheetSetMsg::Browse(field) => { + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Browse for Folder").as_ref()) + .pick_folder() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + move |p| Message::SheetSet(SheetSetMsg::FolderPicked(field, p)), + ) + } + SheetSetMsg::BrowseTemplate => { + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Select Layout as Sheet Template").as_ref()) + .add_filter(crate::t!("Drawing Template (*.dwt)").as_ref(), &["dwt", "DWT"]) + .add_filter(crate::t!("Drawing (*.dwg)").as_ref(), &["dwg", "DWG"]) + .pick_file() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |p| Message::SheetSet(SheetSetMsg::TemplatePicked(p)), + ) + } + SheetSetMsg::Input(field, v) => self.dialog_input(field, v), + SheetSetMsg::Toggle(field, v) => { + if let (FieldId::Hierarchy, Some(SsDialog::Wizard(w))) = (field, self.sheet_set.dialog.as_mut()) { + w.hierarchy = v; + } + } + SheetSetMsg::Choose(field, k) => match (field, self.sheet_set.dialog.as_mut()) { + (FieldId::Layout, Some(SsDialog::Form(f))) => f.layout = k, + (FieldId::CustomOwner, Some(SsDialog::Properties(p))) => { + if let Some(a) = p.adding.as_mut() { + a.2 = k == 1; + } + } + (FieldId::Row(r), Some(SsDialog::Properties(p))) => { + if let Some(row) = p.rows.get_mut(r) { + row.value = if k == 0 { "Yes" } else { "No" }.into(); + } + } + _ => {} + }, + SheetSetMsg::Wizard(w) => self.on_wizard(w), + SheetSetMsg::WizardProperties => { + if let Some(SsDialog::Wizard(mut w)) = self.sheet_set.dialog.take() { + sync_wizard_draft(&mut w); + let set = w.draft.sheet_set().id().to_string(); + let mut p = build_properties(&w.draft, &set, None); + p.wizard = Some(Box::new(w)); + self.sheet_set.dialog = Some(SsDialog::Properties(p)); + } + } + SheetSetMsg::AddCustom => { + if let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() { + let mut n = 1; + while p.custom.iter().any(|(name, _, _)| *name == format!("New Name ({n})")) { + n += 1; + } + p.adding = Some((format!("New Name ({n})"), "Value".into(), false)); + } + } + SheetSetMsg::AddCustomCancel => { + if let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() { + p.adding = None; + } + } + SheetSetMsg::AddCustomOk => { + if let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() { + if let Some((name, value, sheet)) = p.adding.take() { + let name = name.trim().to_string(); + if name.is_empty() { + p.error = Some(crate::t!("The name cannot be empty.").into_owned()); + } else if p.custom.iter().any(|(n, _, _)| n.eq_ignore_ascii_case(&name)) { + p.error = Some(crate::t!("A custom property with this name already exists.").into_owned()); + } else { + p.error = None; + let flags = if sheet { ss::CUSTOM_SHEET_PROP } else { ss::CUSTOM_SHEET_SET_PROP }; + p.custom.push((name, value, flags)); + } + } + } + } + SheetSetMsg::CustomValue(k, v) => { + if let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() { + if let Some(c) = p.custom.get_mut(k) { + c.1 = v; + } + } + } + SheetSetMsg::Ok => return self.sheet_set_dialog_ok(), + SheetSetMsg::Cancel => self.close_sheet_set_dialog(), + SheetSetMsg::Help => self.command_line.push_info( + crate::t!("Sheet sets organize layouts from several drawings into one named set of sheets.").as_ref(), + ), + } + Task::none() + } + + /// Cancel / × of the sheet set dialogs: the Properties dialog opened from + /// the wizard goes back to it. + pub(in crate::app) fn close_sheet_set_dialog(&mut self) { + match self.sheet_set.dialog.take() { + Some(SsDialog::Properties(p)) if p.wizard.is_some() => { + self.sheet_set.dialog = p.wizard.map(|w| SsDialog::Wizard(*w)); + } + _ => self.close_active_modal(), + } + } + + fn sheet_set_menu(&mut self, id: String, action: MenuAction) -> Task { + let Some(set) = self.sheet_set.current else { + return Task::none(); + }; + self.sheet_set.selected = Some(id.clone()); + let Some(db) = self.sheet_set.db() else { + return Task::none(); + }; + match action { + MenuAction::Open => return self.open_sheet(&id), + MenuAction::NewSheet => { + // Next number: the last sheet's number + 1 when it ends in digits. + let template = template_of(db, &id) + .map(|t| format!("{} ({})", t.name, t.file_name)) + .unwrap_or_else(|| crate::t!("(default new drawing)").into_owned()); + self.sheet_set.dialog = Some(SsDialog::Form(Form { + kind: FormKind::NewSheet, + set, + component: id.clone(), + number: String::new(), + title: String::new(), + file_name: String::new(), + folder: sheet_folder_of(db, &id), + drawing: template, + layouts: Vec::new(), + layout: 0, + error: None, + })); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + MenuAction::NewSubset => { + let mut n = 1; + let parent = db.find(&id); + while parent.is_some_and(|p| { + p.children.iter().any(|c| c.prop("Name") == Some(format!("New Subset ({n})").as_str())) + }) { + n += 1; + } + self.sheet_set.dialog = Some(SsDialog::Form(Form { + kind: FormKind::NewSubset, + set, + component: id.clone(), + number: String::new(), + title: format!("New Subset ({n})"), + file_name: String::new(), + folder: sheet_folder_of(db, &id), + drawing: String::new(), + layouts: Vec::new(), + layout: 0, + error: None, + })); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + MenuAction::ImportLayout => { + self.sheet_set_import_parent = Some(id); + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Select Drawing").as_ref()) + .add_filter(crate::t!("Drawing (*.dwg)").as_ref(), &["dwg", "DWG"]) + .pick_file() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |p| Message::SheetSet(SheetSetMsg::ImportPicked(p)), + ); + } + MenuAction::Rename => { + let el = db.find(&id); + self.sheet_set.dialog = Some(SsDialog::Form(Form { + kind: FormKind::Rename, + set, + component: id.clone(), + number: el.and_then(|e| e.prop("Number")).unwrap_or("").to_string(), + title: el.and_then(|e| e.prop("Title")).unwrap_or("").to_string(), + file_name: String::new(), + folder: String::new(), + drawing: String::new(), + layouts: Vec::new(), + layout: 0, + error: None, + })); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + MenuAction::Remove => { + if let Some(db) = self.sheet_set.db_mut() { + db.remove(&id); + } + self.sheet_set.selected = None; + self.save_current_sheet_set(); + } + MenuAction::Properties => { + let p = build_properties(db, &id, Some(set)); + self.sheet_set.dialog = Some(SsDialog::Properties(p)); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + MenuAction::Close => { + self.sheet_set.sets.remove(set); + self.sheet_set.current = (!self.sheet_set.sets.is_empty()).then_some(0); + self.sheet_set.selected = None; + self.sheet_sets_changed(); + } + } + Task::none() + } + + /// Open a sheet's drawing (or switch to it) at the sheet's layout. + fn open_sheet(&mut self, id: &str) -> Task { + let Some(reference) = self.sheet_set.db().and_then(|db| db.layout_reference(id, "Layout")) else { + return Task::none(); + }; + let path = PathBuf::from(&reference.file_name); + if let Some(k) = self.tab_showing(&path) { + return Task::batch([ + self.update(Message::TabSwitch(k)), + Task::done(Message::LayoutSwitch(reference.name)), + ]); + } + if !path.exists() { + self.command_line + .push_error(crate::tf!("Sheet drawing not found: {}", path.display()).as_ref()); + return Task::none(); + } + self.sheet_set_pending_layout = Some((ss::path_key(&reference.file_name), reference.name)); + self.update(Message::OpenRecent(path)) + } + + /// After a drawing opened: switch to the sheet layout that was asked for, + /// and locate the sheet set the drawing names (SSLOCATE / SSMAUTOOPEN). + pub(in crate::app) fn sheet_set_after_open(&mut self, i: usize) -> Task { + let mut task = Task::none(); + let path = self.tabs[i].current_path.as_ref().map(|p| p.to_string_lossy().to_string()); + if let (Some(path), Some((want, layout))) = (path.as_ref(), self.sheet_set_pending_layout.as_ref()) { + if ss::path_key(path) == *want { + task = Task::done(Message::LayoutSwitch(layout.clone())); + self.sheet_set_pending_layout = None; + } + } + if self.sheet_set.settings.locate == 1 { + if let Some(data) = self.tabs[i].scene.document.sheet_set_data() { + let named = PathBuf::from(&data.sheet_set_file_name); + // A moved set is looked for next to the drawing. + let found = if named.exists() { + Some(named) + } else { + path.as_deref().and_then(|p| { + let beside = Path::new(p).parent()?.join(named.file_name()?); + beside.exists().then_some(beside) + }) + }; + self.sheet_set.found = found.as_ref().map(|f| f.to_string_lossy().to_string()).unwrap_or_default(); + if let Some(found) = found { + let show = self.sheet_set.settings.auto_open == 1; + if let Err(e) = self.open_sheet_set(&found, show) { + self.command_line.push_error(&format!("{}: {e}", found.display())); + } + } + } + } + task + } + + /// Before a native save: a drawing that is a sheet of an open set records + /// its link (`AcSheetSetData`), as the reference does when it saves a + /// sheet while its set is open. + pub(in crate::app) fn stamp_sheet_set_data(&mut self, i: usize) { + let Some(path) = self.tabs[i].current_path.as_ref().map(|p| p.to_string_lossy().to_string()) else { + return; + }; + let layout = self.tabs[i].scene.current_layout.clone(); + let layouts = paper_layouts(&self.tabs[i].scene.document); + for db in &self.sheet_set.sets { + let wanted = (!layout.eq_ignore_ascii_case("Model")).then_some(layout.as_str()); + let Some(sheet) = db.sheet_for(&path, wanted) else { + continue; + }; + let Some(reference) = db.layout_reference(sheet.id(), "Layout") else { + continue; + }; + let handle = layouts + .iter() + .find(|(n, _)| n.eq_ignore_ascii_case(&reference.name)) + .map(|(_, h)| h.to_lowercase()) + .unwrap_or_else(|| reference.handle.to_lowercase()); + let doc = &mut self.tabs[i].scene.document; + let previous = doc.sheet_set_data().unwrap_or_default(); + doc.set_sheet_set_data(&SheetSetData { + layout_handle: handle, + layout_name: reference.name, + sheet_dwg_name: path.clone(), + sheet_set_file_name: db.path.clone().unwrap_or_default(), + sheet_set_version: db.file_revision(), + update_count: previous.update_count + 1, + update_time: utc_stamp(), + }); + return; + } + } + + fn dialog_input(&mut self, field: FieldId, v: String) { + match self.sheet_set.dialog.as_mut() { + Some(SsDialog::Wizard(w)) => match field { + FieldId::Name => w.name = v, + FieldId::Description => w.description = v, + FieldId::Folder => w.folder = v, + _ => {} + }, + Some(SsDialog::Properties(p)) => match field { + FieldId::Row(r) => { + if let Some(row) = p.rows.get_mut(r) { + row.value = v; + } + } + FieldId::CustomName => { + if let Some(a) = p.adding.as_mut() { + a.0 = v; + } + } + FieldId::CustomDefault => { + if let Some(a) = p.adding.as_mut() { + a.1 = v; + } + } + _ => {} + }, + Some(SsDialog::Form(f)) => { + let file_follows = f.file_name == format!("{} {}", f.number, f.title).trim(); + match field { + FieldId::Number => f.number = v, + FieldId::Title => f.title = v, + FieldId::FileName => f.file_name = v, + FieldId::Folder => f.folder = v, + _ => {} + } + // The file name follows "Number Title" until edited. + if f.kind == FormKind::NewSheet && file_follows && field != FieldId::FileName { + f.file_name = format!("{} {}", f.number, f.title).trim().to_string(); + } + } + None => {} + } + } + + fn dialog_folder(&mut self, field: FieldId, path: PathBuf) { + let folder = path.to_string_lossy().to_string(); + if field == FieldId::AddFolder { + self.wizard_add_folder(path); + return; + } + self.dialog_input(field, folder); + } + + fn wizard_add_folder(&mut self, path: PathBuf) { + let Some(SsDialog::Wizard(w)) = self.sheet_set.dialog.as_mut() else { + return; + }; + let folder = path.to_string_lossy().to_string(); + if w.folders.contains(&folder) { + return; + } + let mut files: Vec = std::fs::read_dir(&path) + .map(|rd| { + rd.filter_map(|e| e.ok().map(|e| e.path())) + .filter(|p| p.extension().is_some_and(|x| x.eq_ignore_ascii_case("dwg"))) + .collect() + }) + .unwrap_or_default(); + files.sort(); + for file in files { + let Ok(doc) = crate::io::load_file(&file) else { + continue; + }; + for (layout, handle) in paper_layouts(&doc) { + w.layouts.push(FoundLayout { + folder: folder.clone(), + file: file.to_string_lossy().to_string(), + layout, + handle, + on: true, + }); + } + } + w.folders.push(folder); + } + + fn on_wizard(&mut self, m: WizardMsg) { + let Some(SsDialog::Wizard(w)) = self.sheet_set.dialog.as_mut() else { + return; + }; + match m { + WizardMsg::Step(s) => { + w.error = None; + if s > 1 && w.step <= 1 { + if w.name.trim().is_empty() { + w.error = Some(crate::t!("The sheet set name cannot be empty.").into_owned()); + return; + } + if !Path::new(&w.folder).is_dir() { + w.error = Some(crate::t!("The folder for the sheet set data file does not exist.").into_owned()); + return; + } + } + w.step = if s == 2 && !w.existing { 3 } else { s }; + sync_wizard_draft(w); + } + WizardMsg::Existing(on) => w.existing = on, + WizardMsg::LayoutOn(k, on) => { + if let Some(l) = w.layouts.get_mut(k) { + l.on = on; + } + } + WizardMsg::RemoveFolder(k) => { + if k < w.folders.len() { + let folder = w.folders.remove(k); + w.layouts.retain(|l| l.folder != folder); + } + } + } + } + + fn import_layout_form(&mut self, path: PathBuf) { + let (Some(set), Some(parent)) = (self.sheet_set.current, self.sheet_set_import_parent.take()) else { + return; + }; + let layouts = match crate::io::load_file(&path) { + Ok(doc) => paper_layouts(&doc), + Err(e) => { + self.command_line.push_error(&format!("{}: {e}", path.display())); + return; + } + }; + self.sheet_set.dialog = Some(SsDialog::Form(Form { + kind: FormKind::ImportLayout, + set, + component: parent, + number: String::new(), + title: String::new(), + file_name: String::new(), + folder: String::new(), + drawing: path.to_string_lossy().to_string(), + layouts, + layout: 0, + error: None, + })); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + + fn properties_template(&mut self, path: PathBuf) { + let layouts = crate::io::load_file(&path).map(|d| paper_layouts(&d)).unwrap_or_default(); + let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() else { + return; + }; + match layouts.first() { + Some((name, _)) => { + let file = path.to_string_lossy().to_string(); + if let Some(row) = p.rows.iter_mut().find(|r| r.key == RowKey::Template) { + row.value = format!("{name} ({file})"); + } + p.template = Some((file, name.clone())); + p.error = None; + } + None => p.error = Some(crate::t!("The drawing has no layout to use as a sheet template.").into_owned()), + } + } + + fn sheet_set_dialog_ok(&mut self) -> Task { + match self.sheet_set.dialog.take() { + Some(SsDialog::Wizard(w)) => self.finish_wizard(w), + Some(SsDialog::Properties(p)) => { + self.apply_properties(p); + Task::none() + } + Some(SsDialog::Form(f)) => self.form_ok(f), + None => Task::none(), + } + } + + fn finish_wizard(&mut self, mut w: Wizard) -> Task { + sync_wizard_draft(&mut w); + let path = crate::ui::window::sheet_set::wizard_dst_path(&w); + if w.name.trim().is_empty() || !Path::new(&w.folder).is_dir() { + w.error = Some(crate::t!("The folder for the sheet set data file does not exist.").into_owned()); + self.sheet_set.dialog = Some(SsDialog::Wizard(w)); + return Task::none(); + } + if Path::new(&path).exists() { + w.error = Some(crate::tf!("{} already exists.", path).into_owned()); + self.sheet_set.dialog = Some(SsDialog::Wizard(w)); + return Task::none(); + } + let mut db = w.draft.clone(); + db.path = Some(path.clone()); + let set = db.sheet_set().id().to_string(); + if db.file_reference(&set, "NewSheetLocation").is_none() { + db.set_file_reference(&set, "NewSheetLocation", &w.folder); + } + // Existing drawings: a subset per folder, a sheet per chosen layout. + if w.existing { + for folder in &w.folders { + let chosen: Vec<&FoundLayout> = w.layouts.iter().filter(|l| l.on && &l.folder == folder).collect(); + if chosen.is_empty() { + continue; + } + let name = Path::new(folder).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); + let Some(sub) = db.add_subset(&set, &name, "") else { + continue; + }; + if w.hierarchy { + db.set_file_reference(&sub, "NewSheetLocation", folder); + } + for l in chosen { + let stem = Path::new(&l.file).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default(); + if let Some(sheet) = db.add_sheet(&sub, "", &format!("{stem} - {}", l.layout), "") { + db.set_layout_reference( + &sheet, + "Layout", + &LayoutReference { file_name: l.file.clone(), name: l.layout.clone(), handle: l.handle.clone() }, + ); + } + } + } + } + if let Err(e) = db.write(&path) { + w.error = Some(e); + self.sheet_set.dialog = Some(SsDialog::Wizard(w)); + return Task::none(); + } + self.close_active_modal(); + if let Err(e) = self.open_sheet_set(Path::new(&path), true) { + self.command_line.push_error(&e); + } + Task::none() + } + + fn apply_properties(&mut self, mut p: Properties) { + // Back to the wizard: the draft takes the edits. + if let Some(mut w) = p.wizard.take() { + write_properties(&mut w.draft, &p); + let set = w.draft.sheet_set(); + w.name = set.prop("Name").unwrap_or("").to_string(); + w.description = set.prop("Desc").unwrap_or("").to_string(); + self.sheet_set.dialog = Some(SsDialog::Wizard(*w)); + return; + } + self.close_active_modal(); + let Some(db) = p.set.and_then(|k| self.sheet_set.sets.get_mut(k)) else { + return; + }; + write_properties(db, &p); + self.sheet_set.current = p.set; + self.save_current_sheet_set(); + } + + fn form_ok(&mut self, mut f: Form) -> Task { + let Some(db) = self.sheet_set.sets.get_mut(f.set) else { + self.close_active_modal(); + return Task::none(); + }; + match f.kind { + FormKind::Rename => { + if let Some(el) = db.find_mut(&f.component) { + el.set_prop("Number", f.number.trim()); + el.set_prop("Title", f.title.trim()); + } + } + FormKind::NewSubset => { + let name = f.title.trim().to_string(); + if name.is_empty() { + f.error = Some(crate::t!("The subset name cannot be empty.").into_owned()); + self.sheet_set.dialog = Some(SsDialog::Form(f)); + return Task::none(); + } + if let Some(sub) = db.add_subset(&f.component, &name, "") { + if !f.folder.trim().is_empty() { + db.set_file_reference(&sub, "NewSheetLocation", f.folder.trim()); + } + } + } + FormKind::ImportLayout => { + let Some((layout, handle)) = f.layouts.get(f.layout).cloned() else { + f.error = Some(crate::t!("The drawing has no layout to import.").into_owned()); + self.sheet_set.dialog = Some(SsDialog::Form(f)); + return Task::none(); + }; + if db.sheet_for(&f.drawing, Some(&layout)).is_some_and(|s| { + s.named("Layout").and_then(|r| r.prop("Name")).is_some_and(|n| n.eq_ignore_ascii_case(&layout)) + }) { + f.error = Some(crate::t!("The layout is already a sheet of this sheet set.").into_owned()); + self.sheet_set.dialog = Some(SsDialog::Form(f)); + return Task::none(); + } + let stem = Path::new(&f.drawing).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default(); + if let Some(sheet) = db.add_sheet(&f.component, "", &format!("{stem} - {layout}"), "") { + db.set_layout_reference( + &sheet, + "Layout", + &LayoutReference { file_name: f.drawing.clone(), name: layout, handle }, + ); + } + } + FormKind::NewSheet => return self.create_sheet(f), + } + self.close_active_modal(); + self.sheet_set.current = Some(f.set); + self.save_current_sheet_set(); + Task::none() + } + + /// New Sheet: a drawing in the sheet folder holding one layout, named + /// "Number Title", from the sheet creation template (a new drawing's + /// Layout1 when the set names none); then the sheet and its layout + /// reference, and the drawing opens at it. + fn create_sheet(&mut self, mut f: Form) -> Task { + let fail = |app: &mut Self, mut f: Form, e: String| { + f.error = Some(e); + app.sheet_set.dialog = Some(SsDialog::Form(f)); + Task::none() + }; + let title = f.title.trim().to_string(); + if title.is_empty() { + return fail(self, f, crate::t!("The sheet title cannot be empty.").into_owned()); + } + let stem = if f.file_name.trim().is_empty() { + format!("{} {}", f.number.trim(), title).trim().to_string() + } else { + f.file_name.trim().trim_end_matches(".dwg").to_string() + }; + let folder = PathBuf::from(f.folder.trim()); + if std::fs::create_dir_all(&folder).is_err() { + return fail(self, f, crate::t!("The sheet folder cannot be created.").into_owned()); + } + let path = folder.join(format!("{stem}.dwg")); + if path.exists() { + return fail(self, f, crate::tf!("{} already exists.", path.display()).into_owned()); + } + let layout_name = format!("{} {}", f.number.trim(), title).trim().to_string(); + let template = self.sheet_set.sets.get(f.set).and_then(|db| template_of(db, &f.component)); + let mut scene = crate::scene::Scene::new(); + let source_layout = match template.as_ref().and_then(|t| crate::io::load_file(Path::new(&t.file_name)).ok().map(|d| (d, t.name.clone()))) { + Some((doc, name)) => { + scene.document = doc; + name + } + None => { + scene.populate_new_drawing_defaults(); + paper_layouts(&scene.document).into_iter().next().map(|(n, _)| n).unwrap_or_else(|| "Layout1".into()) + } + }; + for (name, _) in paper_layouts(&scene.document) { + if !name.eq_ignore_ascii_case(&source_layout) { + scene.delete_layout(&name); + } + } + scene.rename_layout(&source_layout, &layout_name); + let handle = paper_layouts(&scene.document) + .into_iter() + .find(|(n, _)| *n == layout_name) + .map(|(_, h)| h) + .unwrap_or_default(); + f.number = f.number.trim().to_string(); + let Some(db) = self.sheet_set.sets.get_mut(f.set) else { + return Task::none(); + }; + let Some(sheet) = db.add_sheet(&f.component, &f.number, &title, "") else { + return Task::none(); + }; + let file = path.to_string_lossy().to_string(); + db.set_layout_reference( + &sheet, + "Layout", + &LayoutReference { file_name: file.clone(), name: layout_name.clone(), handle: handle.clone() }, + ); + let dst = db.path.clone().unwrap_or_default(); + if let Err(e) = db.write(&dst) { + self.command_line.push_error(&format!("{dst}: {e}")); + } + let revision = db.file_revision(); + scene.document.set_sheet_set_data(&SheetSetData { + layout_handle: handle.to_lowercase(), + layout_name, + sheet_dwg_name: file.clone(), + sheet_set_file_name: dst, + sheet_set_version: revision, + update_count: 1, + update_time: utc_stamp(), + }); + crate::entities::field::stamp_save_dates(&mut scene.document); + if let Err(e) = crate::io::save(&scene.document, &path) { + self.command_line.push_error(&format!("{file}: {e}")); + } + self.close_active_modal(); + self.sheet_set.current = Some(f.set); + self.sheet_set.selected = Some(sheet.clone()); + self.sheet_sets_changed(); + self.open_sheet(&sheet) + } +} + +/// The reference's default folder for new sheet set data: My Documents. +fn dirs_next_documents() -> String { + #[cfg(target_os = "windows")] + if let Ok(profile) = std::env::var("USERPROFILE") { + for sub in ["Documents", "OneDrive\\Belgeler", "OneDrive\\Documents"] { + let p = Path::new(&profile).join(sub); + if p.is_dir() { + return p.to_string_lossy().to_string(); + } + } + return profile; + } + std::env::var("HOME").unwrap_or_default() +} + +/// Copy the wizard's name and description into its draft set. +fn sync_wizard_draft(w: &mut Wizard) { + let set = w.draft.sheet_set_mut(); + set.set_prop("Name", w.name.trim()); + set.set_prop("Desc", &w.description); +} + +/// The Properties rows for component `id` of `db` (`set` = its index among +/// the open sets; `None` for the wizard's draft). +pub(in crate::app) fn build_properties(db: &SheetSetDatabase, id: &str, set: Option) -> Properties { + let el = db.find(id).cloned().unwrap_or_default(); + let kind = ComponentKind::of(&el).unwrap_or(ComponentKind::SheetSet); + let prop = |name: &str| el.prop(name).unwrap_or("").to_string(); + let row = |group: &'static str, label: &'static str, key: RowKey, value: String| PropRow { group, label, key, value }; + let template = || { + db.layout_reference(id, "DefDwtLayout") + .map(|t| format!("{} ({})", t.name, t.file_name)) + .unwrap_or_default() + }; + let prompt = || if el.prop("PromptForDwt").is_some_and(|v| v.trim() != "0") { "Yes" } else { "No" }.to_string(); + let named_file = |name: &str| el.named(name).map(|r| db.resolve_file(r)).unwrap_or_default(); + let mut rows = Vec::new(); + let title; + match kind { + ComponentKind::SheetSet => { + title = format!("{} - {}", crate::t!("Sheet Set Properties"), prop("Name")); + rows.push(row("Sheet Set", "Name", RowKey::Prop("Name"), prop("Name"))); + rows.push(row("Sheet Set", "Sheet set data file", RowKey::ReadOnly, db.path.clone().unwrap_or_default())); + rows.push(row("Sheet Set", "Description", RowKey::Prop("Desc"), prop("Desc"))); + let resources = el + .named("Resources") + .map(|r| r.children.iter().map(|c| db.resolve_file(c)).collect::>().join("; ")) + .unwrap_or_default(); + rows.push(row("Sheet Set", "Model view", RowKey::ReadOnly, resources)); + let label_block = el + .named("DefLabelBlk") + .map(|r| format!("{} ({})", r.prop("Name").unwrap_or(""), db.resolve_file(r))) + .unwrap_or_default(); + rows.push(row("Sheet Set", "Label block for views", RowKey::ReadOnly, label_block)); + let callouts = el + .named("CalloutBlocks") + .map(|r| r.children.iter().filter_map(|c| c.prop("Name")).collect::>().join("; ")) + .unwrap_or_default(); + rows.push(row("Sheet Set", "Callout blocks", RowKey::ReadOnly, callouts)); + rows.push(row("Sheet Set", "Page setup overrides file", RowKey::ReadOnly, named_file("AltPageSetups"))); + for (label, name) in [ + ("Project number", "ProjectNumber"), + ("Project name", "ProjectName"), + ("Project phase", "ProjectPhase"), + ("Project milestone", "ProjectMilestone"), + ] { + rows.push(row("Project Control", label, RowKey::Prop(name), prop(name))); + } + rows.push(row("Sheet Creation", "Sheet storage location", RowKey::Folder("NewSheetLocation"), named_file("NewSheetLocation"))); + rows.push(row("Sheet Creation", "Sheet creation template", RowKey::Template, template())); + rows.push(row("Sheet Creation", "Prompt for template", RowKey::PromptTemplate, prompt())); + } + ComponentKind::Subset => { + title = format!("{} - {}", crate::t!("Subset Properties"), prop("Name")); + rows.push(row("Subset", "Subset name", RowKey::Prop("Name"), prop("Name"))); + rows.push(row("Subset", "Description", RowKey::Prop("Desc"), prop("Desc"))); + rows.push(row("Subset", "New sheet location", RowKey::Folder("NewSheetLocation"), named_file("NewSheetLocation"))); + rows.push(row("Subset", "Sheet creation template", RowKey::Template, template())); + rows.push(row("Subset", "Prompt for template", RowKey::PromptTemplate, prompt())); + } + ComponentKind::Sheet => { + title = format!("{} - {}", crate::t!("Sheet Properties"), ss::number_and_title(&el)); + for (label, name) in [ + ("Title", "Title"), + ("Number", "Number"), + ("Description", "Desc"), + ("Revision number", "RevisionNumber"), + ("Revision date", "RevisionDate"), + ("Issue purpose", "IssuePurpose"), + ("Category", "Category"), + ] { + rows.push(row("Sheet", label, RowKey::Prop(name), prop(name))); + } + let layout = db.layout_reference(id, "Layout").unwrap_or_default(); + rows.push(row("Sheet", "Layout name", RowKey::ReadOnly, layout.name)); + rows.push(row("Sheet", "Drawing file", RowKey::ReadOnly, layout.file_name)); + } + } + // A sheet shows the set's sheet properties, with its own value when set. + let custom = match kind { + ComponentKind::Sheet => { + let own = ss::custom_properties(&el); + ss::custom_properties(db.sheet_set()) + .into_iter() + .filter(|(_, _, f)| f & ss::CUSTOM_SHEET_PROP != 0) + .map(|(n, v, f)| { + let value = own.iter().find(|(o, _, _)| *o == n).map(|(_, v, _)| v.clone()).unwrap_or(v); + (n, value, f) + }) + .collect() + } + ComponentKind::SheetSet => ss::custom_properties(&el), + ComponentKind::Subset => Vec::new(), + }; + Properties { + set, + component: id.to_string(), + kind, + title, + rows, + custom, + adding: None, + template: None, + wizard: None, + error: None, + } +} + +/// Write a Properties dialog back into its component. +fn write_properties(db: &mut SheetSetDatabase, p: &Properties) { + for r in &p.rows { + match &r.key { + RowKey::Prop(name) => { + if let Some(el) = db.find_mut(&p.component) { + el.set_prop(name, &r.value); + } + } + RowKey::Folder(name) => { + db.set_file_reference(&p.component, name, r.value.trim()); + } + RowKey::PromptTemplate => { + if let Some(el) = db.find_mut(&p.component) { + if r.value == "Yes" { + el.set_prop_vt("PromptForDwt", 3, "1"); + } else { + el.remove_named("PromptForDwt"); + } + } + } + RowKey::Template | RowKey::ReadOnly => {} + } + } + if let Some((file, name)) = &p.template { + db.set_layout_reference( + &p.component, + "DefDwtLayout", + &LayoutReference { file_name: file.clone(), name: name.clone(), handle: String::new() }, + ); + } + match p.kind { + ComponentKind::SheetSet => { + if let Some(el) = db.find_mut(&p.component) { + let old: Vec = ss::custom_properties(el).into_iter().map(|(n, _, _)| n).collect(); + for n in old.iter().filter(|n| !p.custom.iter().any(|(c, _, _)| c == *n)) { + ss::remove_custom_property(el, n); + } + for (n, v, f) in &p.custom { + ss::set_custom_property(el, n, v, *f); + } + } + } + ComponentKind::Sheet => { + if let Some(el) = db.find_mut(&p.component) { + for (n, v, f) in &p.custom { + ss::set_custom_property(el, n, v, *f); + } + } + } + ComponentKind::Subset => {} + } +} + diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index 671a08b77..0fe7272b1 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -82,6 +82,7 @@ pub(super) const NAMES: &[&str] = &[ "dialog_ok", "attdef_dialog", "field_dialog", + "sheet_set", "close_document", "toggle_properties", "toggle_layers", @@ -1019,9 +1020,103 @@ impl OpenCADStudio { "link_text" => F::HyperlinkText(v.into()), "link_url" => F::HyperlinkUrl(v.into()), "plot_scale" => F::PlotScale(index(codec::fields::PLOT_SCALE_FORMATS.len())?), + "ss_custom" => F::SsCustom(v.into()), + "ss_placeholder" => F::SsPlaceholder(index(fd::PLACEHOLDER_TYPES.len())?), + "ss_scale" => F::SsScaleFormat(index(codec::fields::PLOT_SCALE_FORMATS.len())?), _ => return Err(failure("invalid_value", "Unknown field dialog key")), }) } + // Sheet Set Manager and its dialogs: "open=", "tab=list|views|model", + // "select=", "activate=", "menu=:" + // (open, new_sheet, new_subset, import, rename, remove, properties, + // close; a target is a sheet number or title, a subset name or + // "set"), "import_file=", "new" (wizard), "existing=0|1", + // "step=", "name=", "description=", "folder=", "hierarchy=0|1", + // "add_folder=", "layout=:0|1", "wizard_props", + // "row=:", "prompt=0|1:", "template=", + // "custom=:", "add_custom", "custom_name=", "custom_default=", + // "custom_owner=0|1", "add_custom_ok", "number=", "title=", + // "file_name=", "pick_layout=", "ok", "cancel". + "sheet_set" => { + use crate::ui::window::sheet_set::{FieldId as F, MenuAction as A, SheetSetMsg as M, SsmTab, WizardMsg as W}; + let value = string(req, "value")?; + let (key, arg) = value.split_once('=').unwrap_or((value, "")); + let target = |app: &Self, spec: &str| { + app.sheet_set_target(spec).ok_or_else(|| failure("invalid_value", "No such sheet set component")) + }; + let index_value = |spec: &str| -> Result<(usize, String), Value> { + let (i, v) = spec.split_once(':').unwrap_or((spec, "")); + Ok((i.parse().map_err(|_| failure("invalid_value", "Expected :"))?, v.to_string())) + }; + Message::SheetSet(match key { + "open" => M::OpenPicked(Some(std::path::PathBuf::from(arg))), + "import_file" => M::ImportPicked(Some(std::path::PathBuf::from(arg))), + "tab" => M::Tab(match arg { + "views" => SsmTab::SheetViews, + "model" => SsmTab::ModelViews, + _ => SsmTab::SheetList, + }), + "select" => M::Select(target(self, arg)?), + "activate" => M::Activate(target(self, arg)?), + "menu" => { + let (action, spec) = arg.split_once(':').unwrap_or((arg, "set")); + let action = match action { + "open" => A::Open, + "new_sheet" => A::NewSheet, + "new_subset" => A::NewSubset, + "import" => A::ImportLayout, + "rename" => A::Rename, + "remove" => A::Remove, + "properties" => A::Properties, + "close" => A::Close, + _ => return Err(failure("invalid_value", "Unknown sheet set menu action")), + }; + if action == A::ImportLayout { + self.sheet_set_import_parent = Some(target(self, spec)?); + return Ok(Task::none()); + } + M::Menu(target(self, spec)?, action) + } + "new" => return Ok(self.dispatch_command("NEWSHEETSET")), + "existing" => M::Wizard(W::Existing(arg == "1")), + "step" => M::Wizard(W::Step(arg.parse().map_err(|_| failure("invalid_value", "step"))?)), + "layout" => { + let (i, v) = index_value(arg)?; + M::Wizard(W::LayoutOn(i, v == "1")) + } + "name" => M::Input(F::Name, arg.into()), + "description" => M::Input(F::Description, arg.into()), + "folder" => M::Input(F::Folder, arg.into()), + "hierarchy" => M::Toggle(F::Hierarchy, arg == "1"), + "add_folder" => M::FolderPicked(F::AddFolder, Some(std::path::PathBuf::from(arg))), + "wizard_props" => M::WizardProperties, + "row" => { + let (i, v) = index_value(arg)?; + M::Input(F::Row(i), v) + } + "prompt" => { + let (i, v) = index_value(arg)?; + M::Choose(F::Row(i), usize::from(v != "1")) + } + "template" => M::TemplatePicked(Some(std::path::PathBuf::from(arg))), + "custom" => { + let (i, v) = index_value(arg)?; + M::CustomValue(i, v) + } + "add_custom" => M::AddCustom, + "custom_name" => M::Input(F::CustomName, arg.into()), + "custom_default" => M::Input(F::CustomDefault, arg.into()), + "custom_owner" => M::Choose(F::CustomOwner, usize::from(arg == "1")), + "add_custom_ok" => M::AddCustomOk, + "number" => M::Input(F::Number, arg.into()), + "title" => M::Input(F::Title, arg.into()), + "file_name" => M::Input(F::FileName, arg.into()), + "pick_layout" => M::Choose(F::Layout, arg.parse().map_err(|_| failure("invalid_value", "index"))?), + "ok" => M::Ok, + "cancel" => M::Cancel, + _ => return Err(failure("invalid_value", "Unknown sheet set action")), + }) + } "ribbon_dropdown" => Message::ToggleRibbonDropdown(string(req, "value")?.into()), "pdf_dialog_ok" => { use crate::ui::window::pdf_dialogs::PdfDialogMsg; @@ -1048,6 +1143,9 @@ impl OpenCADStudio { Some(crate::app::ModalKind::AttDefEdit) => Message::AttdefDialog( crate::ui::window::attdef_dialog::AttdefDialogMsg::EditOk, ), + Some(crate::app::ModalKind::SheetSet) => Message::SheetSet( + crate::ui::window::sheet_set::SheetSetMsg::Ok, + ), Some(crate::app::ModalKind::Field) => Message::FieldDialog( crate::ui::window::field_dialog::FieldDialogMsg::Ok, ), diff --git a/src/app/mod.rs b/src/app/mod.rs index 392e1bed1..1e75e477c 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -816,6 +816,13 @@ pub(super) struct OpenCADStudio { pub(crate) show_browser: bool, /// Point Cloud Manager palette (POINTCLOUDMANAGER). pub(crate) pc_manager: crate::ui::window::pc_manager::PcManager, + /// Sheet Set Manager palette, the open sheet sets and their dialogs. + pub(crate) sheet_set: crate::ui::window::sheet_set::SheetSetManager, + /// A sheet opened from the manager: its drawing (path key) and layout, + /// switched to once the drawing has loaded. + pub(crate) sheet_set_pending_layout: Option<(String, String)>, + /// The subset / set Import Layout as Sheet adds to, while its file picker is open. + pub(crate) sheet_set_import_parent: Option, /// Which viewport background the colour wheel is editing, or `None` when /// it is closed. One slot, because only one wheel can be open at a time. pub(crate) bg_picker: Option, @@ -1988,6 +1995,8 @@ pub enum ModalKind { GpuWarning, /// Reference Manager help window (toolbar Help button). XrefHelp, + /// Sheet set wizard, properties and sheet / subset forms. + SheetSet, } /// A property group controlled by a layer state's restore mask. @@ -3897,6 +3906,7 @@ pub enum Message { FieldDialog(crate::ui::window::field_dialog::FieldDialogMsg), /// A click or search in the Point Cloud Manager. PcManager(crate::ui::window::pc_manager::PcManagerMsg), + SheetSet(crate::ui::window::sheet_set::SheetSetMsg), PdfImportPickResult(Result<(std::path::PathBuf, std::sync::Arc>), String>), /// Result of the PDFATTACH file picker. PdfAttachPickResult(Result<(std::path::PathBuf, std::sync::Arc>), String>), @@ -4222,6 +4232,9 @@ impl OpenCADStudio { show_external_references: false, show_browser: false, pc_manager: Default::default(), + sheet_set: Default::default(), + sheet_set_pending_layout: None, + sheet_set_import_parent: None, bg_picker: None, block_palette: Default::default(), xref_manager: Default::default(), diff --git a/src/app/settings.rs b/src/app/settings.rs index 690114e91..f2a23cec7 100644 --- a/src/app/settings.rs +++ b/src/app/settings.rs @@ -612,6 +612,9 @@ pub struct UserSettings { /// BLOCKNAVIGATE (default "."). #[serde(default = "default_block_navigate")] pub block_navigate: String, + /// SSMAUTOOPEN, SSLOCATE, SSMPOLLTIME, SSMSHEETSTATUS. + #[serde(default)] + pub sheet_set: crate::ui::window::sheet_set::SheetSetSettings, } /// A block the Blocks palette lists from outside the current drawing (a @@ -851,6 +854,7 @@ impl Default for UserSettings { block_mru_list: 50, block_redefine_mode: 1, block_navigate: ".".to_string(), + sheet_set: Default::default(), } } } diff --git a/src/app/update/dialog.rs b/src/app/update/dialog.rs index 943ab3d4a..02de26082 100644 --- a/src/app/update/dialog.rs +++ b/src/app/update/dialog.rs @@ -606,6 +606,10 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven PanelId::PointCloudManager => { self.pc_manager.show = false; } + PanelId::SheetSetManager => { + self.sheet_set.show = false; + self.ribbon.set_sheet_set(false); + } PanelId::Properties => { self.show_properties = false; self.ribbon.set_properties(false); @@ -711,6 +715,7 @@ pub(super) fn on_ribbon_tool_click(&mut self, tool_id: String, event: ModuleEven PanelId::Browser => self.show_browser, PanelId::NodeGraph => self.show_node_graph, PanelId::PointCloudManager => self.pc_manager.show, + PanelId::SheetSetManager => self.sheet_set.show, } } diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 2edd18786..c68ee8117 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -744,6 +744,7 @@ impl OpenCADStudio { block_mru_list: self.block_mru_list, block_redefine_mode: self.block_redefine_mode, block_navigate: self.block_navigate.clone(), + sheet_set: self.sheet_set.settings, } } @@ -874,6 +875,7 @@ impl OpenCADStudio { self.block_mru_list = s.block_mru_list.min(100); self.block_redefine_mode = s.block_redefine_mode.min(2); self.block_navigate = s.block_navigate.clone(); + self.sheet_set.settings = s.sheet_set; self.block_palette.recent = s.block_recent.clone(); self.block_palette.favorites = s.block_favorites.clone(); self.block_palette.libraries = s.block_libraries.clone(); @@ -2044,7 +2046,8 @@ impl OpenCADStudio { } else { Task::none() }; - Task::batch([thumbs_task, pending_open_task, interaction_task, startup_script_task]) + let sheet_set_task = self.sheet_set_after_open(i); + Task::batch([thumbs_task, pending_open_task, interaction_task, startup_script_task, sheet_set_task]) } pub(super) fn on_wblock_save_result_some( @@ -2149,6 +2152,7 @@ impl OpenCADStudio { self.tabs[i].scene.document.header.user_real1 = self.tabs[i].scene.annotation_scale as f64; self.sync_solid_models_for_save(i); self.tabs[i].scene.sync_native_parametric_graph(); + self.stamp_sheet_set_data(i); } #[cfg(not(target_arch = "wasm32"))] diff --git a/src/app/update/mod.rs b/src/app/update/mod.rs index fdcac3bea..cb4ad3500 100644 --- a/src/app/update/mod.rs +++ b/src/app/update/mod.rs @@ -1819,6 +1819,7 @@ impl OpenCADStudio { Message::AttdefDialog(message) => self.on_attdef_dialog(message), Message::FieldDialog(message) => self.on_field_dialog(message), Message::PcManager(message) => self.update_pc_manager(message), + Message::SheetSet(message) => self.on_sheet_set(message), Message::RibbonSelectTab(idx) => { self.ribbon.select(idx); Task::none() @@ -8521,6 +8522,10 @@ impl OpenCADStudio { } Message::CloseModal => { + if self.active_modal == Some(super::ModalKind::SheetSet) { + self.close_sheet_set_dialog(); + return Task::none(); + } if self.active_modal == Some(super::ModalKind::Field) { self.close_field_dialog(); return Task::none(); diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index ee8e0d7fc..f8b3ebc15 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -1888,6 +1888,7 @@ bg={bg_ms:.1}ms n={view_count}" crate::ui::dock::PanelId::Browser => self.show_browser, crate::ui::dock::PanelId::NodeGraph => self.show_node_graph, crate::ui::dock::PanelId::PointCloudManager => self.pc_manager.show, + crate::ui::dock::PanelId::SheetSetManager => self.sheet_set.show, } }; let edge_stack = |side: crate::app::config::DockSide| -> Option> { @@ -3030,6 +3031,9 @@ impl OpenCADStudio { width, auto_collapse, ), + crate::ui::dock::PanelId::SheetSetManager => { + crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse) + } }; let divider = dock_divider(id); match side { diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index d5f81eae6..5d1a51e6e 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -75,6 +75,7 @@ impl OpenCADStudio { Some(K::RecoveryPrompt) => crate::tr!("modal", "recovery-prompt"), Some(K::GpuWarning) => crate::tr!("gpu", "title"), Some(K::XrefHelp) => crate::t!("Reference Manager Help").into_owned(), + Some(K::SheetSet) => self.sheet_set.dialog.as_ref().map(|d| d.title()).unwrap_or_default(), None => String::new(), } } @@ -580,6 +581,14 @@ impl OpenCADStudio { let state = self.attdef_dialog.as_ref()?; sized_flow(ex, 720, 520, |flow| crate::ui::window::attdef_dialog::view(state, flow)) } + super::super::ModalKind::SheetSet => { + let (w, h) = match self.sheet_set.dialog.as_ref()? { + crate::ui::window::sheet_set::SsDialog::Wizard(_) => (720, 560), + crate::ui::window::sheet_set::SsDialog::Properties(_) => (640, 700), + crate::ui::window::sheet_set::SsDialog::Form(_) => (560, 420), + }; + sized_flow(ex, w, h, |flow| crate::ui::window::sheet_set::dialog_view(&self.sheet_set, flow)) + } super::super::ModalKind::Field => { let state = self.field_dialog.as_ref()?; sized_flow(ex, 760, 660, |flow| crate::ui::window::field_dialog::view(state, flow)) diff --git a/src/entities/field.rs b/src/entities/field.rs index c05b5ccb3..af7885e77 100644 --- a/src/entities/field.rs +++ b/src/entities/field.rs @@ -71,6 +71,14 @@ impl FieldContext for OcsFieldContext<'_> { os_date_locale() } + fn sheet_sets( + &self, + f: &mut dyn FnMut(&codec::sheet_set::SheetSetDatabase) -> Option, + ) -> Option { + let sets = SHEET_SETS.read().ok()?; + sets.iter().find_map(|db| f(db)) + } + fn getenv(&self, name: &str) -> Option { #[cfg(not(target_arch = "wasm32"))] { @@ -84,6 +92,16 @@ impl FieldContext for OcsFieldContext<'_> { } } +/// The sheet sets open in the Sheet Set Manager, for `\AcSm` fields. The +/// manager replaces the list whenever a set opens, closes or changes. +static SHEET_SETS: std::sync::RwLock> = std::sync::RwLock::new(Vec::new()); + +pub fn set_sheet_sets(sets: Vec) { + if let Ok(mut guard) = SHEET_SETS.write() { + *guard = sets; + } +} + /// Re-evaluate the field hosted by entity `host` (usually an MTEXT), or `None` /// to keep the cached text. Thin wrapper over the library engine. pub fn resolve(document: &CadDocument, host: Handle) -> Option { diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 4ec7f0109..ec64e084c 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -3,6 +3,102 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static str)> { match source { + "Create Sheet Set" => Some(("view", "ss-create-sheet-set")), + "New Sheet" => Some(("view", "ss-new-sheet")), + "Subset Properties" => Some(("view", "ss-subset-properties")), + "Rename & Renumber Sheet" => Some(("view", "ss-rename-renumber-sheet")), + "Import Layout as Sheet" => Some(("view", "ss-import-layout-as-sheet")), + "Sheet Set Manager" => Some(("view", "ss-sheet-set-manager")), + "Open..." => Some(("view", "ss-open")), + "Open a sheet set or create one with NEWSHEETSET." => Some(("view", "ss-open-a-sheet-set-or-create-one-with-newsheetset")), + "No sheet views." => Some(("view", "ss-no-sheet-views")), + "No locations." => Some(("view", "ss-no-locations")), + "Double-click opens a sheet. Right-click for more commands." => Some(("view", "ss-double-click-opens-a-sheet-right-click-for-more-commands")), + "An empty sheet set" => Some(("view", "ss-an-empty-sheet-set")), + "Existing drawings" => Some(("view", "ss-existing-drawings")), + "Create a folder hierarchy based on subsets" => Some(("view", "ss-create-a-folder-hierarchy-based-on-subsets")), + "Sheet Set Properties..." => Some(("view", "ss-sheet-set-properties")), + "Select folders containing drawings. Layouts in the drawings can be added to the sheet set." => Some(("view", "ss-select-folders-containing-drawings-layouts-in-the-drawings-c")), + "Sheet Set" => Some(("view", "ss-sheet-set")), + "Sheet set data file" => Some(("view", "ss-sheet-set-data-file")), + "Sheets to import" => Some(("view", "ss-sheets-to-import")), + "Add Custom Property..." => Some(("view", "ss-add-custom-property")), + "New Sheet..." => Some(("view", "ss-new-sheet-x")), + "New Subset..." => Some(("view", "ss-new-subset")), + "Import Layout as Sheet..." => Some(("view", "ss-import-layout-as-sheet-x")), + "Rename & Renumber..." => Some(("view", "ss-rename-renumber")), + "Close Sheet Set" => Some(("view", "ss-close-sheet-set")), + "Properties..." => Some(("view", "ss-properties")), + "Sheet List" => Some(("view", "ss-sheet-list")), + "Sheet Views" => Some(("view", "ss-sheet-views")), + "Model Views" => Some(("view", "ss-model-views")), + "Views by sheet" => Some(("view", "ss-views-by-sheet")), + "Locations" => Some(("view", "ss-locations")), + "Create a sheet set using" => Some(("view", "ss-create-a-sheet-set-using")), + "Name of new sheet set:" => Some(("view", "ss-name-of-new-sheet-set")), + "Description (optional):" => Some(("view", "ss-description-optional")), + "Store sheet set data file (.dst) here:" => Some(("view", "ss-store-sheet-set-data-file-dst-here")), + "Choose Layouts" => Some(("view", "ss-choose-layouts")), + "Confirm" => Some(("view", "ss-confirm")), + "Default value:" => Some(("view", "ss-default-value")), + "Owner" => Some(("view", "ss-owner")), + "Custom Properties" => Some(("view", "ss-custom-properties")), + "Sheet title" => Some(("view", "ss-sheet-title")), + "Folder path" => Some(("view", "ss-folder-path")), + "Sheet template" => Some(("view", "ss-sheet-template")), + "Subset name" => Some(("view", "ss-subset-name")), + "New sheet location" => Some(("view", "ss-new-sheet-location")), + "Remove Subset" => Some(("view", "ss-remove-subset")), + "Remove Sheet" => Some(("view", "ss-remove-sheet")), + "Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set." => Some(("view", "ss-lets-you-specify-one-or-more-folders-containing-drawings-the")), + "Creates a sheet set with no subsets or sheets." => Some(("view", "ss-creates-a-sheet-set-with-no-subsets-or-sheets")), + "Open Sheet Set" => Some(("view", "ss-open-sheet-set")), + "Sheet Set (*.dst)" => Some(("view", "ss-sheet-set-dst")), + "Browse for Folder" => Some(("view", "ss-browse-for-folder")), + "Select Layout as Sheet Template" => Some(("view", "ss-select-layout-as-sheet-template")), + "Drawing Template (*.dwt)" => Some(("view", "ss-drawing-template-dwt")), + "Drawing (*.dwg)" => Some(("view", "ss-drawing-dwg")), + "The name cannot be empty." => Some(("view", "ss-the-name-cannot-be-empty")), + "A custom property with this name already exists." => Some(("view", "ss-a-custom-property-with-this-name-already-exists")), + "Sheet sets organize layouts from several drawings into one named set of sheets." => Some(("view", "ss-sheet-sets-organize-layouts-from-several-drawings-into-one-n")), + "(default new drawing)" => Some(("view", "ss-default-new-drawing")), + "Select Drawing" => Some(("view", "ss-select-drawing")), + "The sheet set name cannot be empty." => Some(("view", "ss-the-sheet-set-name-cannot-be-empty")), + "The folder for the sheet set data file does not exist." => Some(("view", "ss-the-folder-for-the-sheet-set-data-file-does-not-exist")), + "The drawing has no layout to use as a sheet template." => Some(("view", "ss-the-drawing-has-no-layout-to-use-as-a-sheet-template")), + "The subset name cannot be empty." => Some(("view", "ss-the-subset-name-cannot-be-empty")), + "The drawing has no layout to import." => Some(("view", "ss-the-drawing-has-no-layout-to-import")), + "The layout is already a sheet of this sheet set." => Some(("view", "ss-the-layout-is-already-a-sheet-of-this-sheet-set")), + "The sheet title cannot be empty." => Some(("view", "ss-the-sheet-title-cannot-be-empty")), + "The sheet folder cannot be created." => Some(("view", "ss-the-sheet-folder-cannot-be-created")), + "Sheet Set Properties" => Some(("view", "ss-sheet-set-properties-x")), + "Sheet Properties" => Some(("view", "ss-sheet-properties")), + "Sheet drawing not found: {}" => Some(("view", "ss-sheet-drawing-not-found")), + "{} already exists." => Some(("view", "ss-already-exists")), + "Model view" => Some(("view", "ss-model-view")), + "Label block for views" => Some(("view", "ss-label-block-for-views")), + "Callout blocks" => Some(("view", "ss-callout-blocks")), + "Page setup overrides file" => Some(("view", "ss-page-setup-overrides-file")), + "Project Control" => Some(("view", "ss-project-control")), + "Sheet Creation" => Some(("view", "ss-sheet-creation")), + "Sheet storage location" => Some(("view", "ss-sheet-storage-location")), + "Sheet creation template" => Some(("view", "ss-sheet-creation-template")), + "Prompt for template" => Some(("view", "ss-prompt-for-template")), + "Subset" => Some(("view", "ss-subset")), + "Layout name" => Some(("view", "ss-layout-name")), + "Drawing file" => Some(("view", "ss-drawing-file")), + "Project number" => Some(("view", "ss-project-number")), + "Project name" => Some(("view", "ss-project-name")), + "Project phase" => Some(("view", "ss-project-phase")), + "Project milestone" => Some(("view", "ss-project-milestone")), + "Revision number" => Some(("view", "ss-revision-number")), + "Revision date" => Some(("view", "ss-revision-date")), + "Issue purpose" => Some(("view", "ss-issue-purpose")), + "Custom property name" => Some(("view", "ss-custom-property-name")), + "Placeholder type" => Some(("view", "ss-placeholder-type")), + "Sheet set" => Some(("view", "ss-sheet-set-x")), + "Sheet navigation tree" => Some(("view", "ss-sheet-navigation-tree")), + "SheetSet" => Some(("view", "ss-sheetset")), "Entire drawing" => Some(("common", "entire-drawing")), "Delete from drawing" => Some(("common", "delete-from-drawing")), "Destination" => Some(("common", "destination")), diff --git a/src/modules/view/sheetset.rs b/src/modules/view/sheetset.rs index c15680237..d5f582dfb 100644 --- a/src/modules/view/sheetset.rs +++ b/src/modules/view/sheetset.rs @@ -5,6 +5,6 @@ pub fn tool() -> ToolDef { id: "SHEETSET", label: "Sheet Set\nManager", icon: ICON, - event: ModuleEvent::Command("SHEETSET".to_string()), + event: ModuleEvent::Command("_SSMTOGGLE".to_string()), } } diff --git a/src/ui/dock.rs b/src/ui/dock.rs index 1e62a58cf..958b1ca85 100644 --- a/src/ui/dock.rs +++ b/src/ui/dock.rs @@ -48,6 +48,8 @@ pub enum PanelId { NodeGraph, /// Regions and scans of the drawing's point clouds. PointCloudManager, + /// The open sheet sets: subsets and sheets. + SheetSetManager, } impl PanelId { @@ -60,6 +62,7 @@ impl PanelId { PanelId::Browser => "Browser", PanelId::NodeGraph => "Node Graph", PanelId::PointCloudManager => "Point Cloud Manager", + PanelId::SheetSetManager => "Sheet Set Manager", } } @@ -72,6 +75,7 @@ impl PanelId { PanelId::Browser => 230.0, PanelId::NodeGraph => 220.0, PanelId::PointCloudManager => 280.0, + PanelId::SheetSetManager => 280.0, } } @@ -162,6 +166,7 @@ impl DockState { PanelId::Browser, PanelId::NodeGraph, PanelId::PointCloudManager, + PanelId::SheetSetManager, ] { self.panels.entry(id).or_insert_with(|| DockPanel::for_id(id)); } diff --git a/src/ui/ribbon/mod.rs b/src/ui/ribbon/mod.rs index 20989bd37..fe14d1e54 100644 --- a/src/ui/ribbon/mod.rs +++ b/src/ui/ribbon/mod.rs @@ -57,6 +57,8 @@ pub struct Ribbon { pub show_ucs_icon: bool, /// Properties panel (PROPERTIES) visibility — drives the Properties button highlight. pub show_properties: bool, + /// Sheet Set Manager palette shown (SSMSTATE). + pub show_sheet_set: bool, /// File tabs (FILETAB) visibility — drives the File Tabs button highlight. pub show_file_tabs: bool, /// Layout tabs (LAYOUTTAB) visibility — drives the Layout Tabs button highlight. @@ -173,6 +175,7 @@ impl Ribbon { show_viewcube: true, show_ucs_icon: true, show_properties: true, + show_sheet_set: false, show_file_tabs: true, show_layout_tabs: true, open_dropdown: None, @@ -330,6 +333,9 @@ impl Ribbon { pub fn set_properties(&mut self, on: bool) { self.show_properties = on; } + pub fn set_sheet_set(&mut self, on: bool) { + self.show_sheet_set = on; + } pub fn set_file_tabs(&mut self, on: bool) { self.show_file_tabs = on; } @@ -348,6 +354,7 @@ impl Ribbon { show_ucs_icon: self.show_ucs_icon, show_properties: self.show_properties, show_block_palette, + show_sheet_set: self.show_sheet_set, show_file_tabs: self.show_file_tabs, show_layout_tabs: self.show_layout_tabs, } diff --git a/src/ui/ribbon/widgets.rs b/src/ui/ribbon/widgets.rs index 74e7d9393..ef3230b66 100644 --- a/src/ui/ribbon/widgets.rs +++ b/src/ui/ribbon/widgets.rs @@ -38,6 +38,7 @@ pub(super) struct ToggleState { pub show_ucs_icon: bool, pub show_properties: bool, pub show_block_palette: bool, + pub show_sheet_set: bool, pub show_file_tabs: bool, pub show_layout_tabs: bool, } @@ -380,6 +381,7 @@ pub(super) fn is_active_tool( "UCSICON" => state.show_ucs_icon, "PROPERTIES" => state.show_properties, "BLOCKPALETTE" => state.show_block_palette, + "SHEETSET" => state.show_sheet_set, "FILETAB" => state.show_file_tabs, "LAYOUTTAB" => state.show_layout_tabs, id => active_tool.as_deref() == Some(id), diff --git a/src/ui/window/field_dialog.rs b/src/ui/window/field_dialog.rs index 0d76b6816..55fa8c911 100644 --- a/src/ui/window/field_dialog.rs +++ b/src/ui/window/field_dialog.rs @@ -38,8 +38,83 @@ pub const CATEGORIES: &[(&str, &[&str])] = &[ "Plot", &["DeviceName", "Login", "PageSetupName", "PaperSize", "PlotDate", "PlotOrientation", "PlotScale", "PlotStyleTable"], ), + ("SheetSet", SHEET_SET_FIELDS), ]; +/// The SheetSet category, in the reference's order. +pub const SHEET_SET_FIELDS: &[&str] = &[ + "CurrentSheetCategory", + "CurrentSheetCustom", + "CurrentSheetDescription", + "CurrentSheetIssuePurpose", + "CurrentSheetNumber", + "CurrentSheetNumberAndTitle", + "CurrentSheetRevisionDate", + "CurrentSheetRevisionNumber", + "CurrentSheetSet", + "CurrentSheetSetCustom", + "CurrentSheetSetDescription", + "CurrentSheetSetProjectMilestone", + "CurrentSheetSetProjectName", + "CurrentSheetSetProjectNumber", + "CurrentSheetSetProjectPhase", + "CurrentSheetSubSet", + "CurrentSheetTitle", + "SheetSet", + "SheetSetPlaceholder", + "SheetView", +]; + +/// A sheet set field's code without format: `\AcSm[.16.2] Component.Property`. +fn sheet_set_code(name: &str, custom: &str) -> String { + let (version, target) = match name { + "CurrentSheetCategory" => (".16.2", "Sheet.Category".to_string()), + "CurrentSheetCustom" => ("", format!("Sheet.{custom}")), + "CurrentSheetDescription" => ("", "Sheet.Description".into()), + "CurrentSheetIssuePurpose" => (".16.2", "Sheet.IssuePurpose".into()), + "CurrentSheetNumber" => ("", "Sheet.Number".into()), + "CurrentSheetNumberAndTitle" => ("", "Sheet.NumberAndTitle".into()), + "CurrentSheetRevisionDate" => (".16.2", "Sheet.RevisionDate".into()), + "CurrentSheetRevisionNumber" => (".16.2", "Sheet.RevisionNumber".into()), + "CurrentSheetSet" => ("", "SheetSet.Name".into()), + "CurrentSheetSetCustom" => ("", format!("SheetSet.{custom}")), + "CurrentSheetSetDescription" => ("", "SheetSet.Description".into()), + "CurrentSheetSetProjectMilestone" => (".16.2", "SheetSet.ProjectMilestone".into()), + "CurrentSheetSetProjectName" => (".16.2", "SheetSet.ProjectName".into()), + "CurrentSheetSetProjectNumber" => (".16.2", "SheetSet.ProjectNumber".into()), + "CurrentSheetSetProjectPhase" => (".16.2", "SheetSet.ProjectPhase".into()), + "CurrentSheetSubSet" => ("", "Subset.Name".into()), + "CurrentSheetTitle" => ("", "Sheet.Title".into()), + _ => return "\\AcSm".into(), + }; + format!("\\AcSm{version} {target}") +} + +/// SheetSetPlaceholder types: list label, the `?` target, field version. +pub const PLACEHOLDER_TYPES: &[(&str, &str, &str)] = &[ + ("SheetNumberAndTitle", "?Sheet.NumberAndTitle", ""), + ("SheetTitle", "?Sheet.Title", ""), + ("SheetNumber", "?Sheet.Number", ""), + ("SheetDescription", "?Sheet.Description", ""), + ("RevisionNumber", "?Sheet.RevisionNumber", ".16.2"), + ("RevisionDate", "?Sheet.RevisionDate", ".16.2"), + ("IssuePurpose", "?Sheet.IssuePurpose", ".16.2"), + ("Category", "?Sheet.Category", ".16.2"), + ("Custom", "?Sheet.", ""), + ("ViewNumberAndTitle", "?View.NumberAndTitle", ""), + ("ViewTitle", "?View.Title", ""), + ("ViewNumber", "?View.Number", ""), + ("ViewportScale", "?View.ViewportScale", ".16.2"), +]; + +/// ViewportScale placeholder formats: the PlotScale pictures. +pub fn viewport_scale_picture(k: usize) -> String { + codec::fields::PLOT_SCALE_FORMATS + .get(k) + .and_then(|(_, code)| code.find("\\f ").map(|p| code[p..].to_string())) + .unwrap_or_default() +} + /// The fields of a category; "All" lists every field once, sorted. pub fn fields_of(category: usize) -> Vec<&'static str> { if category == 0 { @@ -77,6 +152,13 @@ pub enum FieldKind { Formula, Hyperlink, PlotScale, + /// A CurrentSheet* / CurrentSheetSet* field (text case). + SheetSet, + /// CurrentSheetCustom / CurrentSheetSetCustom (text case + property). + SheetSetCustom, + SheetSetPlaceholder, + /// SheetSet / SheetView: a component picked in a sheet set. + SheetSetNavigation, } /// Formula precisions: the current one, then 0 to 8 decimals. @@ -95,6 +177,10 @@ pub fn kind_of(name: &str) -> FieldKind { "Formula" => FieldKind::Formula, "Hyperlink" => FieldKind::Hyperlink, "PlotScale" => FieldKind::PlotScale, + "CurrentSheetCustom" | "CurrentSheetSetCustom" => FieldKind::SheetSetCustom, + "SheetSetPlaceholder" => FieldKind::SheetSetPlaceholder, + "SheetSet" | "SheetView" => FieldKind::SheetSetNavigation, + n if SHEET_SET_FIELDS.contains(&n) => FieldKind::SheetSet, _ => FieldKind::Text, } } @@ -129,6 +215,16 @@ pub struct FieldDialogState { /// Index into codec PLOT_SCALE_FORMATS. pub plot_scale: usize, pub preview: String, + /// Sheet set fields: the open set's sheet (flags 2) and set (flags 1) + /// custom property names, the chosen one, the placeholder type and the + /// ViewportScale format. + pub ss_sheet_custom: Vec, + pub ss_set_custom: Vec, + pub ss_custom: String, + pub ss_placeholder: usize, + pub ss_scale_format: usize, + /// The open sheet set's name (SheetSet / SheetView fields). + pub ss_set_name: String, /// Today in each of DATE_FORMATS, for the Examples list. pub examples: Vec, } @@ -159,6 +255,12 @@ impl FieldDialogState { hyperlink_url: String::new(), plot_scale: 0, preview: String::new(), + ss_sheet_custom: Vec::new(), + ss_set_custom: Vec::new(), + ss_custom: String::new(), + ss_placeholder: 0, + ss_scale_format: 6, + ss_set_name: String::new(), examples: Vec::new(), } } @@ -211,6 +313,21 @@ impl FieldDialogState { .map_or_else(|| "\\AcVar PlotScale".to_string(), |f| f.1.to_string()), vec![], ), + FieldKind::SheetSet | FieldKind::SheetSetCustom => (case(sheet_set_code(self.name, &self.ss_custom)), vec![]), + FieldKind::SheetSetNavigation => ("\\AcSm".into(), vec![]), + FieldKind::SheetSetPlaceholder => { + let (label, target, version) = PLACEHOLDER_TYPES[self.ss_placeholder.min(PLACEHOLDER_TYPES.len() - 1)]; + let target = if label == "Custom" { format!("{target}{}", self.ss_custom) } else { target.to_string() }; + let base = format!("\\AcSm{version} {target}"); + if label == "ViewportScale" { + match viewport_scale_picture(self.ss_scale_format) { + f if f.is_empty() => (base, vec![]), + f => (format!("{base} {f}"), vec![]), + } + } else { + (case(base), 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)) { @@ -278,6 +395,9 @@ pub enum FieldDialogMsg { HyperlinkUrl(String), BrowseHyperlink, PlotScale(usize), + SsCustom(String), + SsPlaceholder(usize), + SsScaleFormat(usize), Help, Ok, } @@ -542,6 +662,72 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { ] .spacing(8) .into(), + FieldKind::SheetSet => column![text(t!("Format")).size(12), case_list(state)].spacing(8).into(), + FieldKind::SheetSetCustom => { + let names = if state.name == "CurrentSheetCustom" { &state.ss_sheet_custom } else { &state.ss_set_custom }; + column![ + text(t!("Format")).size(12), + case_list(state), + text(t!("Custom property name")).size(12), + pick_list( + (!state.ss_custom.is_empty()).then(|| state.ss_custom.clone()), + names.clone(), + |s: &String| s.clone(), + ) + .on_select(|s| msg(FieldDialogMsg::SsCustom(s))) + .text_size(12) + .padding([5, 8]) + .width(Fill), + ] + .spacing(8) + .into() + } + FieldKind::SheetSetPlaceholder => { + let kind = PLACEHOLDER_TYPES[state.ss_placeholder.min(PLACEHOLDER_TYPES.len() - 1)].0; + let types = list( + PLACEHOLDER_TYPES + .iter() + .enumerate() + .map(|(i, (n, _, _))| (n.to_string(), state.ss_placeholder == i, FieldDialogMsg::SsPlaceholder(i))) + .collect(), + 200.0, + ); + let format: Element<'a, Message> = if kind == "ViewportScale" { + list( + codec::fields::PLOT_SCALE_FORMATS + .iter() + .enumerate() + .map(|(i, (n, _))| (t!(*n).into_owned(), state.ss_scale_format == i, FieldDialogMsg::SsScaleFormat(i))) + .collect(), + 170.0, + ) + } else { + case_list(state) + }; + let mut col = column![text(t!("Placeholder type")).size(12), types, text(t!("Format")).size(12), format].spacing(8); + if kind == "Custom" { + col = col.push(text(t!("Custom property name")).size(12)).push( + pick_list( + (!state.ss_custom.is_empty()).then(|| state.ss_custom.clone()), + state.ss_sheet_custom.clone(), + |s: &String| s.clone(), + ) + .on_select(|s| msg(FieldDialogMsg::SsCustom(s))) + .text_size(12) + .padding([5, 8]) + .width(Fill), + ); + } + col.into() + } + FieldKind::SheetSetNavigation => column![ + text(t!("Sheet set")).size(12), + container(text(state.ss_set_name.clone()).size(12).style(muted_style)).padding([5, 8]).width(Fill), + text(t!("Sheet navigation tree")).size(12), + container(Space::new()).height(Length::Fixed(120.0)), + ] + .spacing(8) + .into(), FieldKind::Object => column![ row![ text(t!("Object type")).size(12).width(Length::Fixed(96.0)), diff --git a/src/ui/window/mod.rs b/src/ui/window/mod.rs index 3b3282d0f..7497baed3 100644 --- a/src/ui/window/mod.rs +++ b/src/ui/window/mod.rs @@ -27,6 +27,7 @@ pub mod alias_editor; pub mod find_replace; pub mod named_parameters; pub mod pc_manager; +pub mod sheet_set; pub mod pdf_dialogs; pub mod xref_attach; pub mod xref_help; diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs new file mode 100644 index 000000000..690de72dc --- /dev/null +++ b/src/ui/window/sheet_set.rs @@ -0,0 +1,968 @@ +//! Sheet Set Manager (SHEETSET): the open sheet sets as a tree of subsets +//! and sheets, the New Sheet Set wizard (NEWSHEETSET), the Sheet Set / +//! Subset / Sheet Properties dialog and the small New Sheet, New Subset, +//! Import Layout and Rename & Renumber forms. The tree is read from the +//! sheet set database (`.dst`) itself on every view. + +use crate::app::Message; +use crate::t; +use crate::ui::dock::PanelId; +use crate::ui::style::common::muted_style; +use crate::ui::style::form::{button_style, dialog_button, field_style}; +use crate::ui::window::pdf_dialogs::{accent_text, card_style}; +use codec::sheet_set::{self as ss, ComponentKind, Element as SsElement, SheetSetDatabase}; +use iced::widget::{ + button, checkbox, column, container, mouse_area, pick_list, row, scrollable, text, text_input, Space, +}; +use iced::{Background, Border, Element, Fill, Length, Theme}; +use serde::{Deserialize, Serialize}; +use std::collections::HashSet; + +const SET_ICON: &[u8] = include_bytes!("../../../assets/icons/sheetset.svg"); + +/// SSMAUTOOPEN, SSLOCATE, SSMPOLLTIME and SSMSHEETSTATUS: profile settings. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(default)] +pub struct SheetSetSettings { + pub auto_open: u8, + pub locate: u8, + pub poll_time: u16, + pub sheet_status: u8, +} + +impl Default for SheetSetSettings { + fn default() -> Self { + Self { auto_open: 1, locate: 1, poll_time: 60, sheet_status: 2 } + } +} + +/// The palette's three pages. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum SsmTab { + #[default] + SheetList, + SheetViews, + ModelViews, +} + +/// The palette and the open sheet sets. +#[derive(Debug, Default)] +pub struct SheetSetManager { + pub show: bool, + pub settings: SheetSetSettings, + /// Open sheet sets (each read from its `.dst`). + pub sets: Vec, + /// The set the palette shows. + pub current: Option, + pub tab: SsmTab, + /// Folded subset / set ids. + pub collapsed: HashSet, + /// Highlighted component id. + pub selected: Option, + /// SSFOUND: the `.dst` the last opened sheet drawing named, when found. + pub found: String, + /// The open dialog (shown in the `SheetSet` modal). + pub dialog: Option, +} + +impl SheetSetManager { + pub fn db(&self) -> Option<&SheetSetDatabase> { + self.sets.get(self.current?) + } + + pub fn db_mut(&mut self) -> Option<&mut SheetSetDatabase> { + self.sets.get_mut(self.current?) + } +} + +/// Right-click commands of a tree row. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum MenuAction { + Open, + NewSheet, + NewSubset, + ImportLayout, + Rename, + Remove, + Properties, + Close, +} + +#[derive(Debug, Clone)] +pub enum SheetSetMsg { + /// The set combo: a set by index, or `None` for "Open...". + PickSet(Option), + Tab(SsmTab), + Expand(String), + Select(String), + /// Double-click: open the sheet's drawing at its layout. + Activate(String), + Menu(String, MenuAction), + Refresh, + /// A `.dst` picked by Open (or given by automation). + OpenPicked(Option), + /// A drawing picked by Import Layout as Sheet. + ImportPicked(Option), + /// A folder picked for a dialog field. + FolderPicked(FieldId, Option), + /// A template drawing picked for a sheet creation template. + TemplatePicked(Option), + Browse(FieldId), + BrowseTemplate, + /// Edit a text field of the open dialog. + Input(FieldId, String), + Toggle(FieldId, bool), + Choose(FieldId, usize), + Wizard(WizardMsg), + /// Open the Properties dialog from the wizard. + WizardProperties, + AddCustom, + AddCustomOk, + AddCustomCancel, + /// A custom property row's value. + CustomValue(usize, String), + Ok, + Cancel, + Help, +} + +#[derive(Debug, Clone)] +pub enum WizardMsg { + Step(u8), + Existing(bool), + /// Toggle one drawing layout of the "existing drawings" page. + LayoutOn(usize, bool), + RemoveFolder(usize), +} + +/// Text fields, toggles and lists of the dialogs. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FieldId { + /// A Properties row by index. + Row(usize), + Name, + Description, + Folder, + Hierarchy, + Number, + Title, + FileName, + /// The layout list of Import Layout as Sheet. + Layout, + /// Add Custom Property: name, default value, owner (0 set, 1 sheet). + CustomName, + CustomDefault, + CustomOwner, + /// A wizard "existing drawings" folder to add. + AddFolder, + /// Prompt for template (Properties). + PromptTemplate, +} + +/// What a Properties row edits. +#[derive(Debug, Clone, PartialEq)] +pub enum RowKey { + /// An `AcSmProp` of the component. + Prop(&'static str), + /// A file reference property (`NewSheetLocation`). + Folder(&'static str), + /// The sheet creation template (`DefDwtLayout`): the value shows + /// `Layout (file)`; `template` holds the picked drawing and layout. + Template, + /// Yes / No: prompt for template. + PromptTemplate, + /// Shown only. + ReadOnly, +} + +#[derive(Debug, Clone)] +pub struct PropRow { + pub group: &'static str, + pub label: &'static str, + pub key: RowKey, + pub value: String, +} + +/// The Sheet Set / Subset / Sheet Properties dialog. +#[derive(Debug, Clone)] +pub struct Properties { + /// `None` edits the wizard's draft set. + pub set: Option, + pub component: String, + pub kind: ComponentKind, + pub title: String, + pub rows: Vec, + /// Name, value, flags. + pub custom: Vec<(String, String, i32)>, + /// Add Custom Property being filled: name, default value, owner is sheet. + pub adding: Option<(String, String, bool)>, + /// The template picked in this dialog: drawing and layout. + pub template: Option<(String, String)>, + /// Back to the wizard on OK / Cancel. + pub wizard: Option>, + pub error: Option, +} + +/// One layout found by the "existing drawings" wizard page. +#[derive(Debug, Clone)] +pub struct FoundLayout { + pub folder: String, + pub file: String, + pub layout: String, + pub handle: String, + pub on: bool, +} + +/// NEWSHEETSET. +#[derive(Debug, Clone)] +pub struct Wizard { + /// 0 Begin, 1 Sheet Set Details, 2 Choose Layouts (existing drawings), 3 Confirm. + pub step: u8, + pub existing: bool, + pub name: String, + pub description: String, + pub folder: String, + pub hierarchy: bool, + pub folders: Vec, + pub layouts: Vec, + /// The set being built: name, description, properties and custom + /// properties go into it; Finish writes it. + pub draft: SheetSetDatabase, + pub error: Option, +} + +/// New Sheet, New Subset, Rename & Renumber and Import Layout as Sheet. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FormKind { + NewSheet, + NewSubset, + Rename, + ImportLayout, +} + +#[derive(Debug, Clone)] +pub struct Form { + pub kind: FormKind, + pub set: usize, + /// Parent (new sheet / subset / import) or the sheet / subset renamed. + pub component: String, + pub number: String, + pub title: String, + pub file_name: String, + pub folder: String, + /// Import: the drawing and its layouts (name, handle). + pub drawing: String, + pub layouts: Vec<(String, String)>, + pub layout: usize, + pub error: Option, +} + +#[derive(Debug, Clone)] +pub enum SsDialog { + Wizard(Wizard), + Properties(Properties), + Form(Form), +} + +impl SsDialog { + pub fn title(&self) -> String { + match self { + SsDialog::Wizard(_) => t!("Create Sheet Set").into_owned(), + SsDialog::Properties(p) => p.title.clone(), + SsDialog::Form(f) => match f.kind { + FormKind::NewSheet => t!("New Sheet").into_owned(), + FormKind::NewSubset => t!("Subset Properties").into_owned(), + FormKind::Rename => t!("Rename & Renumber Sheet").into_owned(), + FormKind::ImportLayout => t!("Import Layout as Sheet").into_owned(), + }, + } + } +} + +fn msg(m: SheetSetMsg) -> Message { + Message::SheetSet(m) +} + +// ── palette ────────────────────────────────────────────────────────────────── + +#[derive(Debug, Clone, PartialEq, Eq)] +struct Pick(Option, String); + +impl std::fmt::Display for Pick { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(&self.1) + } +} + +fn menu_entry(label: String, m: Option) -> Element<'static, Message> { + button(text(label).size(12)) + .width(Fill) + .padding([5, 12]) + .on_press_maybe(m) + .style(|theme: &Theme, status| { + let mut s = button::text(theme, status); + if matches!(status, button::Status::Hovered) { + s.background = Some(Background::Color(theme.palette().primary.base.color)); + s.text_color = theme.palette().primary.base.text; + } + s + }) + .into() +} + +fn menu_separator() -> Element<'static, Message> { + container(Space::new()) + .height(1) + .width(Fill) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().background.neutral.color)), + ..Default::default() + }) + .into() +} + +/// A row's right-click menu. +fn row_menu(id: String, kind: ComponentKind) -> Element<'static, Message> { + let entry = |label: &str, action: MenuAction, on: bool| { + menu_entry(t!(label).into_owned(), on.then(|| msg(SheetSetMsg::Menu(id.clone(), action)))) + }; + let sheet = kind == ComponentKind::Sheet; + let mut items = vec![ + entry("Open", MenuAction::Open, sheet), + menu_separator(), + entry("New Sheet...", MenuAction::NewSheet, !sheet), + entry("New Subset...", MenuAction::NewSubset, !sheet), + entry("Import Layout as Sheet...", MenuAction::ImportLayout, !sheet), + menu_separator(), + entry("Rename & Renumber...", MenuAction::Rename, sheet), + entry(if kind == ComponentKind::Subset { "Remove Subset" } else { "Remove Sheet" }, MenuAction::Remove, kind != ComponentKind::SheetSet), + ]; + if kind == ComponentKind::SheetSet { + items.push(entry("Close Sheet Set", MenuAction::Close, true)); + } + items.push(menu_separator()); + items.push(entry("Properties...", MenuAction::Properties, true)); + container(iced::widget::Column::with_children(items).spacing(1)) + .padding(4) + .width(Length::Fixed(220.0)) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().background.weak.color)), + border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() }, + ..Default::default() + }) + .into() +} + +fn tree_row<'a>( + state: &SheetSetManager, + el: &SsElement, + depth: u16, + icon: &'static [u8], + label: String, + fold: Option, +) -> Element<'a, Message> { + let id = el.id().to_string(); + let kind = ComponentKind::of(el).unwrap_or(ComponentKind::Sheet); + let arrow: Element<'a, Message> = match fold { + Some(open) => button(if open { + crate::ui::icons::themed_arrow_down(10.0) + } else { + crate::ui::icons::themed_arrow_right(10.0) + }) + .on_press(msg(SheetSetMsg::Expand(id.clone()))) + .style(button::text) + .padding(2) + .into(), + None => Space::new().width(14).into(), + }; + let selected = state.selected.as_deref() == Some(id.as_str()); + let cells = row![ + Space::new().width(Length::Fixed(f32::from(depth) * 16.0)), + arrow, + crate::ui::icons::semantic(icon, 14.0), + text(label).size(12).width(Fill), + ] + .spacing(5) + .align_y(iced::Center); + let area = mouse_area(container(cells).width(Fill).padding([3, 6]).style(move |theme: &Theme| container::Style { + background: selected.then(|| Background::Color(theme.palette().primary.weak.color)), + text_color: selected.then(|| theme.palette().primary.weak.text), + ..Default::default() + })) + .on_press(msg(SheetSetMsg::Select(id.clone()))) + .on_double_click(msg(SheetSetMsg::Activate(id.clone()))); + iced_aw::ContextMenu::new(area, move || row_menu(id.clone(), kind)).into() +} + +fn push_tree<'a>( + state: &SheetSetManager, + el: &SsElement, + depth: u16, + out: &mut Vec>, +) { + for c in &el.children { + match ComponentKind::of(c) { + Some(ComponentKind::Subset) => { + let open = !state.collapsed.contains(c.id()); + out.push(tree_row(state, c, depth, crate::ui::icons::FOLDER_OPEN, c.prop("Name").unwrap_or("").to_string(), Some(open))); + if open { + push_tree(state, c, depth + 1, out); + } + } + Some(ComponentKind::Sheet) => { + out.push(tree_row(state, c, depth, crate::ui::icons::DOC, ss::number_and_title(c), None)); + } + _ => {} + } + } +} + +/// Sheet views of the set: each sheet's `AcSmSheetView` children. +fn sheet_views(db: &SheetSetDatabase) -> Vec { + db.sheets() + .into_iter() + .flat_map(|sheet| { + sheet + .named("SheetViews") + .map(|v| v.children.iter().filter(|c| c.name == "AcSmSheetView").collect::>()) + .unwrap_or_default() + .into_iter() + .map(|v| { + let number = v.prop("Number").unwrap_or(""); + let title = v.prop("Title").unwrap_or(""); + if number.is_empty() { title.to_string() } else { format!("{number} - {title}") } + }) + }) + .collect() +} + +/// Model view locations (`Resources`) of the set. +fn model_views(db: &SheetSetDatabase) -> Vec { + db.sheet_set() + .named("Resources") + .map(|r| r.children.iter().map(|c| db.resolve_file(c)).collect()) + .unwrap_or_default() +} + +fn heading(label: &str) -> Element<'static, Message> { + text(t!(label).into_owned()) + .size(11) + .font(iced::Font { weight: iced::font::Weight::Bold, ..iced::Font::DEFAULT }) + .style(|theme: &Theme| text::Style { color: Some(theme.palette().primary.base.color) }) + .width(Fill) + .into() +} + +pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> Element<'a, Message> { + let title_bar = + crate::ui::dock::title_bar(PanelId::SheetSetManager, t!("Sheet Set Manager").into_owned(), auto_collapse); + let mut picks: Vec = + state.sets.iter().enumerate().map(|(i, db)| Pick(Some(i), db.name().to_string())).collect(); + picks.push(Pick(None, t!("Open...").into_owned())); + let current = state.current.and_then(|i| picks.get(i).cloned()); + let combo = pick_list(current, picks, |p: &Pick| p.1.clone()) + .placeholder(t!("Open...").into_owned()) + .on_select(|p: Pick| msg(SheetSetMsg::PickSet(p.0))) + .text_size(12) + .padding([5, 8]) + .width(Fill); + let tab = |label: &str, t: SsmTab| { + button(text(t!(label).into_owned()).size(11).center().width(Fill)) + .on_press(msg(SheetSetMsg::Tab(t))) + .style(button_style(state.tab == t)) + .padding([5, 4]) + .width(Fill) + }; + let tabs = row![ + tab("Sheet List", SsmTab::SheetList), + tab("Sheet Views", SsmTab::SheetViews), + tab("Model Views", SsmTab::ModelViews), + ] + .spacing(2); + + let body: Element<'a, Message> = match (state.db(), state.tab) { + (None, _) => text(t!("Open a sheet set or create one with NEWSHEETSET.").into_owned()) + .size(12) + .style(muted_style) + .into(), + (Some(db), SsmTab::SheetList) => { + let set = db.sheet_set(); + let open = !state.collapsed.contains(set.id()); + let mut rows = vec![tree_row(state, set, 0, SET_ICON, db.name().to_string(), Some(open))]; + if open { + push_tree(state, set, 1, &mut rows); + } + let header = row![ + heading("Sheets"), + button(text("↻").size(13)) + .on_press(msg(SheetSetMsg::Refresh)) + .style(button::subtle) + .padding([2, 4]), + ] + .align_y(iced::Center); + column![header, scrollable(iced::widget::Column::with_children(rows).spacing(1)).height(Fill)] + .spacing(6) + .into() + } + (Some(db), SsmTab::SheetViews) => { + let views = sheet_views(db); + let list: Element<'a, Message> = if views.is_empty() { + text(t!("No sheet views.").into_owned()).size(12).style(muted_style).into() + } else { + iced::widget::Column::with_children(views.into_iter().map(|v| text(v).size(12).into())).spacing(3).into() + }; + column![heading("Views by sheet"), list].spacing(6).into() + } + (Some(db), SsmTab::ModelViews) => { + let locations = model_views(db); + let list: Element<'a, Message> = if locations.is_empty() { + text(t!("No locations.").into_owned()).size(12).style(muted_style).into() + } else { + iced::widget::Column::with_children(locations.into_iter().map(|v| text(v).size(12).into())) + .spacing(3) + .into() + }; + column![heading("Locations"), list].spacing(6).into() + } + }; + let card = container(body).padding(6).width(Fill).height(Fill).style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().background.weak.color)), + border: Border { radius: 4.0.into(), ..Default::default() }, + ..Default::default() + }); + let hint = text(t!("Double-click opens a sheet. Right-click for more commands.").into_owned()) + .size(10) + .style(muted_style); + crate::ui::dock::frame(column![title_bar, combo, tabs, card, hint].spacing(6), width) +} + +// ── dialogs ────────────────────────────────────────────────────────────────── + +fn card<'a>(title: &str, content: impl Into>) -> Element<'a, Message> { + container(column![text(t!(title).to_uppercase()).size(10).style(accent_text), content.into()].spacing(8)) + .padding([10, 12]) + .width(Fill) + .style(card_style) + .into() +} + +fn input<'a>(value: &str, field: FieldId) -> Element<'a, Message> { + text_input("", value) + .size(12) + .padding([5, 8]) + .style(field_style) + .on_input(move |v| msg(SheetSetMsg::Input(field, v))) + .into() +} + +fn labeled<'a>(label: &str, control: impl Into>) -> Element<'a, Message> { + row![text(t!(label).into_owned()).size(12).width(Length::Fixed(170.0)), control.into()] + .spacing(8) + .align_y(iced::Center) + .into() +} + +fn browse<'a>(m: SheetSetMsg) -> Element<'a, Message> { + button(text("...").size(12)).on_press(msg(m)).style(button_style(false)).padding([5, 10]).into() +} + +fn error_band<'a>(error: &Option) -> Option> { + error.as_ref().map(|e| { + container(text(e.clone()).size(12)) + .padding([6, 10]) + .width(Fill) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().danger.weak.color)), + text_color: Some(theme.palette().danger.weak.text), + border: Border { radius: 4.0.into(), ..Default::default() }, + ..Default::default() + }) + .into() + }) +} + +fn footer<'a>(left: Vec>, right: Vec>) -> Element<'a, Message> { + let mut r = row![button(text("?").size(12)).on_press(msg(SheetSetMsg::Help)).style(button_style(false)).padding([5, 11])] + .spacing(6) + .align_y(iced::Center); + for e in left { + r = r.push(e); + } + r = r.push(Space::new().width(Fill)); + for e in right { + r = r.push(e); + } + r.into() +} + +fn wizard_view<'a>(w: &'a Wizard) -> Element<'a, Message> { + let steps: Vec<(u8, &str)> = if w.existing { + vec![(0, "Begin"), (1, "Details"), (2, "Choose Layouts"), (3, "Confirm")] + } else { + vec![(0, "Begin"), (1, "Details"), (3, "Confirm")] + }; + let chips = iced::widget::Row::with_children(steps.iter().enumerate().map(|(n, (s, label))| { + button(text(format!("{} {}", n + 1, t!(*label))).size(12)) + .on_press(msg(SheetSetMsg::Wizard(WizardMsg::Step(*s)))) + .style(button_style(w.step == *s)) + .padding([4, 10]) + .into() + })) + .spacing(6); + let page: Element<'a, Message> = match w.step { + 0 => card( + "Create a sheet set using", + column![ + checkbox(!w.existing) + .label(t!("An empty sheet set").into_owned()) + .text_size(12) + .size(14) + .on_toggle(|_| msg(SheetSetMsg::Wizard(WizardMsg::Existing(false)))), + checkbox(w.existing) + .label(t!("Existing drawings").into_owned()) + .text_size(12) + .size(14) + .on_toggle(|_| msg(SheetSetMsg::Wizard(WizardMsg::Existing(true)))), + text( + t!(if w.existing { + "Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set." + } else { + "Creates a sheet set with no subsets or sheets." + }) + .into_owned() + ) + .size(11) + .style(muted_style), + ] + .spacing(8), + ), + 1 => card( + "Sheet Set", + column![ + labeled("Name of new sheet set:", input(&w.name, FieldId::Name)), + labeled("Description (optional):", input(&w.description, FieldId::Description)), + labeled( + "Store sheet set data file (.dst) here:", + row![input(&w.folder, FieldId::Folder), browse(SheetSetMsg::Browse(FieldId::Folder))].spacing(6) + ), + checkbox(w.hierarchy) + .label(t!("Create a folder hierarchy based on subsets").into_owned()) + .text_size(12) + .size(14) + .on_toggle(|v| msg(SheetSetMsg::Toggle(FieldId::Hierarchy, v))), + row![ + Space::new().width(Fill), + button(text(t!("Sheet Set Properties...").into_owned()).size(12)) + .on_press(msg(SheetSetMsg::WizardProperties)) + .style(button_style(false)) + .padding([5, 12]), + ], + ] + .spacing(8), + ), + 2 => { + let rows = w.layouts.iter().enumerate().map(|(i, l)| { + checkbox(l.on) + .label(format!("{} — {}", l.file, l.layout)) + .text_size(12) + .size(14) + .on_toggle(move |v| msg(SheetSetMsg::Wizard(WizardMsg::LayoutOn(i, v)))) + .into() + }); + let folders = w.folders.iter().enumerate().map(|(i, f)| { + row![ + text(f.clone()).size(12).width(Fill), + button(text("×").size(12)) + .on_press(msg(SheetSetMsg::Wizard(WizardMsg::RemoveFolder(i)))) + .style(button::text) + .padding([0, 6]), + ] + .into() + }); + card( + "Choose Layouts", + column![ + row![ + text(t!("Select folders containing drawings. Layouts in the drawings can be added to the sheet set.").into_owned()) + .size(11) + .style(muted_style) + .width(Fill), + button(text(t!("Browse...").into_owned()).size(12)) + .on_press(msg(SheetSetMsg::Browse(FieldId::AddFolder))) + .style(button_style(false)) + .padding([5, 12]), + ] + .spacing(8) + .align_y(iced::Center), + iced::widget::Column::with_children(folders.collect::>()).spacing(2), + container(scrollable(iced::widget::Column::with_children(rows.collect::>()).spacing(3))) + .height(Length::Fixed(180.0)), + ] + .spacing(8), + ) + } + _ => { + let mut lines = vec![ + format!("{}: {}", t!("Sheet Set"), w.name), + format!("{}: {}", t!("Sheet set data file"), wizard_dst_path(w)), + format!("{}: {}", t!("Description"), w.description), + ]; + if w.existing { + let n = w.layouts.iter().filter(|l| l.on).count(); + lines.push(format!("{}: {n}", t!("Sheets to import"))); + } + for (name, value, _) in ss::custom_properties(w.draft.sheet_set()) { + lines.push(format!("{name}: {value}")); + } + card( + "Confirm", + iced::widget::Column::with_children(lines.into_iter().map(|l| text(l).size(12).into())).spacing(4), + ) + } + }; + let next_step = match (w.step, w.existing) { + (0, _) => Some(1), + (1, true) => Some(2), + (1, false) | (2, _) => Some(3), + _ => None, + }; + let back_step = match (w.step, w.existing) { + (3, true) => Some(2), + (3, false) | (2, _) => Some(1), + (1, _) => Some(0), + _ => None, + }; + let back = button(text(format!("‹ {}", t!("Back"))).size(12)) + .on_press_maybe(back_step.map(|s| msg(SheetSetMsg::Wizard(WizardMsg::Step(s))))) + .style(button_style(false)) + .padding([6, 14]); + let next: Element<'a, Message> = match next_step { + Some(s) => button(text(format!("{} ›", t!("Next"))).size(12)) + .on_press(msg(SheetSetMsg::Wizard(WizardMsg::Step(s)))) + .style(button_style(true)) + .padding([6, 14]) + .into(), + None => dialog_button(t!("Finish"), msg(SheetSetMsg::Ok), true).into(), + }; + let mut col = column![chips, page].spacing(10); + if let Some(e) = error_band(&w.error) { + col = col.push(e); + } + col.push(footer(vec![], vec![back.into(), next, dialog_button(t!("Cancel"), msg(SheetSetMsg::Cancel), false).into()])) + .into() +} + +/// Where the wizard writes the `.dst`. +pub fn wizard_dst_path(w: &Wizard) -> String { + std::path::Path::new(&w.folder).join(format!("{}.dst", w.name.trim())).to_string_lossy().to_string() +} + +fn properties_view<'a>(p: &'a Properties) -> Element<'a, Message> { + let mut groups: Vec> = Vec::new(); + let mut current: Option<&'static str> = None; + let mut rows: Vec> = Vec::new(); + let flush = |group: Option<&'static str>, rows: &mut Vec>, groups: &mut Vec>| { + if let Some(g) = group { + groups.push(card(g, iced::widget::Column::with_children(std::mem::take(rows)).spacing(6))); + } + }; + for (i, r) in p.rows.iter().enumerate() { + if current != Some(r.group) { + flush(current, &mut rows, &mut groups); + current = Some(r.group); + } + let control: Element<'a, Message> = match r.key { + RowKey::ReadOnly => container(text(r.value.clone()).size(12).style(muted_style)) + .padding([5, 8]) + .width(Fill) + .into(), + RowKey::Folder(_) => row![input(&r.value, FieldId::Row(i)), browse(SheetSetMsg::Browse(FieldId::Row(i)))] + .spacing(6) + .into(), + RowKey::Template => row![ + container(text(r.value.clone()).size(12)).padding([5, 8]).width(Fill), + browse(SheetSetMsg::BrowseTemplate) + ] + .spacing(6) + .align_y(iced::Center) + .into(), + RowKey::PromptTemplate => { + let yes = r.value == "Yes"; + let opts = vec![t!("Yes").into_owned(), t!("No").into_owned()]; + pick_list(Some(opts[if yes { 0 } else { 1 }].clone()), opts.clone(), |s: &String| s.clone()) + .on_select(move |s: String| msg(SheetSetMsg::Choose(FieldId::Row(i), usize::from(s != t!("Yes"))))) + .text_size(12) + .padding([5, 8]) + .width(Fill) + .into() + } + RowKey::Prop(_) => input(&r.value, FieldId::Row(i)), + }; + rows.push(labeled(r.label, control)); + } + flush(current, &mut rows, &mut groups); + if p.kind != ComponentKind::Subset { + let owner = |f: i32| if f & ss::CUSTOM_SHEET_PROP != 0 { t!("Sheet") } else { t!("Sheet Set") }; + let mut custom: Vec> = p + .custom + .iter() + .enumerate() + .map(|(i, (name, value, flags))| { + let label = if p.kind == ComponentKind::SheetSet { format!("{name} ({})", owner(*flags)) } else { name.clone() }; + row![ + text(label).size(12).width(Length::Fixed(170.0)), + text_input("", value) + .size(12) + .padding([5, 8]) + .style(field_style) + .on_input(move |v| msg(SheetSetMsg::CustomValue(i, v))), + ] + .spacing(8) + .align_y(iced::Center) + .into() + }) + .collect(); + if p.kind == ComponentKind::SheetSet { + match &p.adding { + Some((name, value, sheet)) => custom.push( + container( + column![ + labeled("Name:", input(name, FieldId::CustomName)), + labeled("Default value:", input(value, FieldId::CustomDefault)), + labeled( + "Owner", + row![ + checkbox(!*sheet) + .label(t!("Sheet Set").into_owned()) + .text_size(12) + .size(14) + .on_toggle(|_| msg(SheetSetMsg::Choose(FieldId::CustomOwner, 0))), + checkbox(*sheet) + .label(t!("Sheet").into_owned()) + .text_size(12) + .size(14) + .on_toggle(|_| msg(SheetSetMsg::Choose(FieldId::CustomOwner, 1))), + ] + .spacing(12) + ), + row![ + Space::new().width(Fill), + dialog_button(t!("Cancel"), msg(SheetSetMsg::AddCustomCancel), false), + dialog_button(t!("OK"), msg(SheetSetMsg::AddCustomOk), true), + ] + .spacing(6), + ] + .spacing(6), + ) + .padding(8) + .style(|theme: &Theme| container::Style { + border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() }, + ..Default::default() + }) + .into(), + ), + None => custom.push( + row![ + Space::new().width(Fill), + button(text(t!("Add Custom Property...").into_owned()).size(12)) + .on_press(msg(SheetSetMsg::AddCustom)) + .style(button_style(false)) + .padding([5, 12]), + ] + .into(), + ), + } + } + groups.push(card("Custom Properties", iced::widget::Column::with_children(custom).spacing(6))); + } + let mut col = column![scrollable(iced::widget::Column::with_children(groups).spacing(10)).height(Fill)].spacing(10); + if let Some(e) = error_band(&p.error) { + col = col.push(e); + } + col.push(footer( + vec![], + vec![ + dialog_button(t!("Cancel"), msg(SheetSetMsg::Cancel), false).into(), + dialog_button(t!("OK"), msg(SheetSetMsg::Ok), true).into(), + ], + )) + .into() +} + +fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { + let body: Element<'a, Message> = match f.kind { + FormKind::NewSheet => column![ + labeled("Number", input(&f.number, FieldId::Number)), + labeled("Sheet title", input(&f.title, FieldId::Title)), + labeled("File name", input(&f.file_name, FieldId::FileName)), + labeled("Folder path", container(text(f.folder.clone()).size(12).style(muted_style)).padding([5, 8])), + labeled("Sheet template", container(text(f.drawing.clone()).size(12).style(muted_style)).padding([5, 8])), + ] + .spacing(8) + .into(), + FormKind::NewSubset => column![ + labeled("Subset name", input(&f.title, FieldId::Title)), + labeled( + "New sheet location", + row![input(&f.folder, FieldId::Folder), browse(SheetSetMsg::Browse(FieldId::Folder))].spacing(6) + ), + ] + .spacing(8) + .into(), + FormKind::Rename => column![ + labeled("Number", input(&f.number, FieldId::Number)), + labeled("Sheet title", input(&f.title, FieldId::Title)), + ] + .spacing(8) + .into(), + FormKind::ImportLayout => { + let rows = f.layouts.iter().enumerate().map(|(i, (name, _))| { + button(text(name.clone()).size(12)) + .on_press(msg(SheetSetMsg::Choose(FieldId::Layout, i))) + .style(button_style(f.layout == i)) + .padding([3, 8]) + .width(Fill) + .into() + }); + column![ + labeled("Drawing", container(text(f.drawing.clone()).size(12).style(muted_style)).padding([5, 8])), + text(t!("Layouts").into_owned()).size(12), + container(scrollable(iced::widget::Column::with_children(rows.collect::>()).spacing(1))) + .height(Length::Fixed(160.0)), + ] + .spacing(8) + .into() + } + }; + let mut col = column![card( + match f.kind { + FormKind::NewSheet => "Sheet", + FormKind::NewSubset => "Subset", + FormKind::Rename => "Sheet", + FormKind::ImportLayout => "Import Layout as Sheet", + }, + body + )] + .spacing(10); + if let Some(e) = error_band(&f.error) { + col = col.push(e); + } + col.push(footer( + vec![], + vec![ + dialog_button(t!("Cancel"), msg(SheetSetMsg::Cancel), false).into(), + dialog_button(t!("OK"), msg(SheetSetMsg::Ok), true).into(), + ], + )) + .into() +} + +pub fn dialog_view<'a>(state: &'a SheetSetManager, sizing: crate::ui::modal::ModalSizing) -> Element<'a, Message> { + let body = match state.dialog.as_ref() { + Some(SsDialog::Wizard(w)) => wizard_view(w), + Some(SsDialog::Properties(p)) => properties_view(p), + Some(SsDialog::Form(f)) => form_view(f), + None => Space::new().into(), + }; + container(body).padding([10, 12]).width(sizing.width).into() +} From ad5d2a8599b3fdc526afec891208bebbc62783ee Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:00:28 +0300 Subject: [PATCH 02/17] Sheet sets: wizard paths, Turkish step names and codec pin Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 1 + Cargo.toml | 2 +- locales/tr-TR/opencadstudio.ftl | 4 ++-- src/app/commands/sheet_set.rs | 27 ++++++++++++++++++++------- src/ui/window/sheet_set.rs | 2 +- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 378e66a6f..7d6d8182e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4206,6 +4206,7 @@ dependencies = [ [[package]] name = "opencadcodec" version = "0.5.5" +source = "git+https://github.com/ramox81/cadcodec.git?rev=19aec85#19aec855cf38d1dbdaf32d09c69343fc479bfbf9" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 6c34b44e3..c59d8bd1c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { path = "../cadcodec-sheetset" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "19aec85" } [patch.crates-io] iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" } diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index b7888a699..8448b40e6 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -1917,7 +1917,7 @@ common = .allow-selection-cycling = Allow selection cycling .allow-writing-to-source-file = Allow writing to source file .break-specify-second-break-point-or-first-point = BREAK Specify second break point or [First point]: - .begin = Begin + .begin = Başla .chamferedge-select-a-base-face-edge = CHAMFEREDGE: select a base face edge. .cannot-delete-beyond-this = Cannot delete beyond this. .cell-range = Cell range @@ -2168,7 +2168,7 @@ common = .select-surfaces-to-thicken = Select surfaces to thicken: .selected-objects-in-current-drawing = Selected objects in current drawing .set-cell-styles = Set cell styles - .sheet = Sheet + .sheet = Sayfa .show-associativity = Show associativity .show-command-prompting-and-command-input-near-crosshairs = Show command prompting and command input near crosshairs .show-count-column = Show count column diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index 73d7ecef8..809ffd874 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -434,6 +434,16 @@ impl OpenCADStudio { sync_wizard_draft(&mut w); let set = w.draft.sheet_set().id().to_string(); let mut p = build_properties(&w.draft, &set, None); + // The set is not written yet: show where it will be. + for row in &mut p.rows { + match row.key { + RowKey::ReadOnly if row.label == "Sheet set data file" => { + row.value = crate::ui::window::sheet_set::wizard_dst_path(&w) + } + RowKey::Folder(_) if row.value.is_empty() => row.value = w.folder.clone(), + _ => {} + } + } p.wizard = Some(Box::new(w)); self.sheet_set.dialog = Some(SsDialog::Properties(p)); } @@ -684,8 +694,8 @@ impl OpenCADStudio { doc.set_sheet_set_data(&SheetSetData { layout_handle: handle, layout_name: reference.name, - sheet_dwg_name: path.clone(), - sheet_set_file_name: db.path.clone().unwrap_or_default(), + sheet_dwg_name: ss::native_path(&path), + sheet_set_file_name: ss::native_path(db.path.as_deref().unwrap_or_default()), sheet_set_version: db.file_revision(), update_count: previous.update_count + 1, update_time: utc_stamp(), @@ -888,9 +898,12 @@ impl OpenCADStudio { let mut db = w.draft.clone(); db.path = Some(path.clone()); let set = db.sheet_set().id().to_string(); - if db.file_reference(&set, "NewSheetLocation").is_none() { - db.set_file_reference(&set, "NewSheetLocation", &w.folder); - } + // Written again now the `.dst` has a place, so it carries its relative path. + let location = db + .file_reference(&set, "NewSheetLocation") + .filter(|f| !f.is_empty()) + .unwrap_or_else(|| w.folder.clone()); + db.set_file_reference(&set, "NewSheetLocation", &location); // Existing drawings: a subset per folder, a sheet per chosen layout. if w.existing { for folder in &w.folders { @@ -1061,7 +1074,7 @@ impl OpenCADStudio { let Some(sheet) = db.add_sheet(&f.component, &f.number, &title, "") else { return Task::none(); }; - let file = path.to_string_lossy().to_string(); + let file = ss::native_path(&path.to_string_lossy()); db.set_layout_reference( &sheet, "Layout", @@ -1076,7 +1089,7 @@ impl OpenCADStudio { layout_handle: handle.to_lowercase(), layout_name, sheet_dwg_name: file.clone(), - sheet_set_file_name: dst, + sheet_set_file_name: ss::native_path(&dst), sheet_set_version: revision, update_count: 1, update_time: utc_stamp(), diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index 690de72dc..18d3f37b1 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -754,7 +754,7 @@ fn wizard_view<'a>(w: &'a Wizard) -> Element<'a, Message> { /// Where the wizard writes the `.dst`. pub fn wizard_dst_path(w: &Wizard) -> String { - std::path::Path::new(&w.folder).join(format!("{}.dst", w.name.trim())).to_string_lossy().to_string() + ss::native_path(&std::path::Path::new(&w.folder).join(format!("{}.dst", w.name.trim())).to_string_lossy()) } fn properties_view<'a>(p: &'a Properties) -> Element<'a, Message> { From 612bb26a8cd31a6bdc85ef2ab9fceac1052407b3 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 10:53:45 +0300 Subject: [PATCH 03/17] Sheet sets: reference menu order, new sheets stay closed, .dst reload, navigation fields The tree menus follow the reference per row kind (New Sheet / Import on a sheet work on its subset, Remove Subset only while empty, Rename Subset), a new sheet is not opened, an open set reloads when its .dst changes on disk, ViewportScale placeholders take the version of their format and the codec pin moves to the revision with navigation fields. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 1 + locales/bg-BG/opencadstudio.ftl | 1 + locales/cs-CZ/opencadstudio.ftl | 1 + locales/de-DE/opencadstudio.ftl | 1 + locales/el-GR/opencadstudio.ftl | 1 + locales/en-US/opencadstudio.ftl | 1 + locales/es-ES/opencadstudio.ftl | 1 + locales/fi-FI/opencadstudio.ftl | 1 + locales/fr-FR/opencadstudio.ftl | 1 + locales/hi-IN/opencadstudio.ftl | 1 + locales/hu-HU/opencadstudio.ftl | 1 + locales/it-IT/opencadstudio.ftl | 1 + locales/ja-JP/opencadstudio.ftl | 1 + locales/ko-KR/opencadstudio.ftl | 1 + locales/nl-NL/opencadstudio.ftl | 1 + locales/pl-PL/opencadstudio.ftl | 1 + locales/pt-BR/opencadstudio.ftl | 1 + locales/ru-RU/opencadstudio.ftl | 1 + locales/tr-TR/opencadstudio.ftl | 1 + locales/zh-CN/opencadstudio.ftl | 1 + locales/zh-TW/opencadstudio.ftl | 1 + src/app/commands/sheet_set.rs | 41 ++++++++++++++++++++++++++-- src/app/view/mod.rs | 11 ++++++++ src/locale_catalog.rs | 1 + src/ui/window/field_dialog.rs | 8 ++++-- src/ui/window/sheet_set.rs | 47 ++++++++++++++++++++++----------- 28 files changed, 112 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d6d8182e..864e44cb2 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=19aec85#19aec855cf38d1dbdaf32d09c69343fc479bfbf9" +source = "git+https://github.com/ramox81/cadcodec.git?rev=3f5d40a#3f5d40a82368cd2dd5e5c44388f8630647ff3533" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index c59d8bd1c..8efe2a874 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "19aec85" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "3f5d40a" } [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 e683b8007..20ccb54ce 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4406,6 +4406,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index d9c92b35f..457edab5c 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4393,6 +4393,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 26d9d317d..762a52473 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4391,6 +4391,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 0d4326f9f..14b3a2d12 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4392,6 +4392,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 088975020..e393c26e6 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4394,6 +4394,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 6a642c267..d7498da27 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4395,6 +4395,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 19f7707f2..c62ac3377 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4394,6 +4394,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 7750d75d1..cf0c70d6b 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4393,6 +4393,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 12951271d..3392b9e2a 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4394,6 +4394,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 0737d9ea8..0d2435a77 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4382,6 +4382,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7b4c83a21..7b4b9834c 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4393,6 +4393,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 8c3dbd1e5..9cf03a923 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4393,6 +4393,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 39769ff96..1479bab43 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4382,6 +4382,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 41a78c6eb..29fa2501e 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4393,6 +4393,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 881b8941a..c9b107482 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4392,6 +4392,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 04a59f5ff..1b8174e41 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4393,6 +4393,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 77d1ffb4d..f573797fe 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4394,6 +4394,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index a7688a594..61d26d9bd 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4395,6 +4395,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 8448b40e6..ff6b922ff 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4354,6 +4354,7 @@ view = .ss-subset-name = Alt küme adı .ss-new-sheet-location = Yeni sayfa konumu .ss-remove-subset = Alt Kümeyi Kaldır + .ss-rename-subset = Alt Kümeyi Yeniden Adlandır... .ss-remove-sheet = Sayfayı Kaldır .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Çizim içeren bir veya daha fazla klasör belirtmenizi sağlar. Bu çizimlerdeki düzenler sayfa kümesine otomatik olarak aktarılabilir. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Alt kümesi ve sayfası olmayan bir sayfa kümesi oluşturur. diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 6b03e2057..5943c0419 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4377,6 +4377,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 33617b666..96c93c254 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4389,6 +4389,7 @@ view = .ss-subset-name = Subset name .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset + .ss-rename-subset = Rename Subset... .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index 809ffd874..a32707a9f 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -254,6 +254,32 @@ impl OpenCADStudio { Ok(()) } + /// Reload every open set whose `.dst` was changed by someone else. + fn poll_sheet_sets(&mut self) { + let mut changed = false; + for db in &mut self.sheet_set.sets { + let Some(path) = db.path.clone() else { + continue; + }; + let Ok(modified) = std::fs::metadata(&path).and_then(|m| m.modified()) else { + continue; + }; + let key = ss::path_key(&path); + if self.sheet_set.seen.insert(key, modified).is_none_or(|t| t == modified) { + continue; + } + if let Ok(fresh) = SheetSetDatabase::read(&path) { + if fresh.root != db.root { + *db = fresh; + changed = true; + } + } + } + if changed { + self.sheet_sets_changed(); + } + } + /// Write the current set back to its `.dst`. fn save_current_sheet_set(&mut self) { let Some(db) = self.sheet_set.db_mut() else { @@ -358,6 +384,7 @@ impl OpenCADStudio { self.sheet_set.collapsed.insert(id); } } + SheetSetMsg::Poll => self.poll_sheet_sets(), SheetSetMsg::Refresh => { if let Some(path) = self.sheet_set.db().and_then(|db| db.path.clone()) { match SheetSetDatabase::read(&path) { @@ -513,6 +540,15 @@ impl OpenCADStudio { let Some(db) = self.sheet_set.db() else { return Task::none(); }; + // On a sheet, New Sheet and Import work on the subset holding it. + let id = match action { + MenuAction::NewSheet | MenuAction::ImportLayout + if db.find(&id).and_then(ComponentKind::of) == Some(ComponentKind::Sheet) => + { + db.parent_of(&id).map_or(id, |p| p.id().to_string()) + } + _ => id, + }; match action { MenuAction::Open => return self.open_sheet(&id), MenuAction::NewSheet => { @@ -1098,11 +1134,12 @@ impl OpenCADStudio { if let Err(e) = crate::io::save(&scene.document, &path) { self.command_line.push_error(&format!("{file}: {e}")); } + // The new sheet is not opened ("Open in drawing editor" is off by default). self.close_active_modal(); self.sheet_set.current = Some(f.set); - self.sheet_set.selected = Some(sheet.clone()); + self.sheet_set.selected = Some(sheet); self.sheet_sets_changed(); - self.open_sheet(&sheet) + Task::none() } } diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index f8b3ebc15..25e2df580 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -2644,6 +2644,16 @@ impl OpenCADStudio { }; #[cfg(target_arch = "wasm32")] let plugin_drain = Subscription::none(); + // Open sheet sets reload when their .dst changes on disk. + #[cfg(not(target_arch = "wasm32"))] + let sheet_set_poll = if self.sheet_set.sets.is_empty() { + Subscription::none() + } else { + iced::time::every(std::time::Duration::from_secs(1)) + .map(|_| Message::SheetSet(crate::ui::window::sheet_set::SheetSetMsg::Poll)) + }; + #[cfg(target_arch = "wasm32")] + let sheet_set_poll = Subscription::none(); let hatch_pattern_keys = if self.tabs[self.active_tab] .properties .hatch_pattern_picker_open @@ -2845,6 +2855,7 @@ impl OpenCADStudio { web_fonts, autosave, plugin_drain, + sheet_set_poll, single_instance, hatch_pattern_keys, keyboard_events, diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index c910a2d97..14452f731 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -49,6 +49,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Subset name" => Some(("view", "ss-subset-name")), "New sheet location" => Some(("view", "ss-new-sheet-location")), "Remove Subset" => Some(("view", "ss-remove-subset")), + "Rename Subset..." => Some(("view", "ss-rename-subset")), "Remove Sheet" => Some(("view", "ss-remove-sheet")), "Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set." => Some(("view", "ss-lets-you-specify-one-or-more-folders-containing-drawings-the")), "Creates a sheet set with no subsets or sheets." => Some(("view", "ss-creates-a-sheet-set-with-no-subsets-or-sheets")), diff --git a/src/ui/window/field_dialog.rs b/src/ui/window/field_dialog.rs index 49ecac3c4..6851c0612 100644 --- a/src/ui/window/field_dialog.rs +++ b/src/ui/window/field_dialog.rs @@ -328,14 +328,18 @@ impl FieldDialogState { FieldKind::SheetSetPlaceholder => { let (label, target, version) = PLACEHOLDER_TYPES[self.ss_placeholder.min(PLACEHOLDER_TYPES.len() - 1)]; let target = if label == "Custom" { format!("{target}{}", self.ss_custom) } else { target.to_string() }; - let base = format!("\\AcSm{version} {target}"); if label == "ViewportScale" { + // The version follows the format, as for PlotScale. + let version = codec::fields::PLOT_SCALE_FORMATS + .get(self.ss_scale_format) + .map_or("", |(_, code)| if code.starts_with("\\AcVar.16.2") { ".16.2" } else { "" }); + let base = format!("\\AcSm{version} {target}"); match viewport_scale_picture(self.ss_scale_format) { f if f.is_empty() => (base, vec![]), f => (format!("{base} {f}"), vec![]), } } else { - (case(base), vec![]) + (case(format!("\\AcSm{version} {target}")), vec![]) } } FieldKind::BlockPlaceholder => { diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index 18d3f37b1..a1c54c7dd 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -63,6 +63,8 @@ pub struct SheetSetManager { pub found: String, /// The open dialog (shown in the `SheetSet` modal). pub dialog: Option, + /// Last seen modification time of each open `.dst` (by path key). + pub seen: std::collections::HashMap, } impl SheetSetManager { @@ -99,6 +101,8 @@ pub enum SheetSetMsg { Activate(String), Menu(String, MenuAction), Refresh, + /// Once a second: reload sets whose `.dst` changed on disk. + Poll, /// A `.dst` picked by Open (or given by automation). OpenPicked(Option), /// A drawing picked by Import Layout as Sheet. @@ -322,23 +326,35 @@ fn menu_separator() -> Element<'static, Message> { } /// A row's right-click menu. -fn row_menu(id: String, kind: ComponentKind) -> Element<'static, Message> { +/// The reference's order per row kind; a sheet's New Sheet / Import work on +/// its subset, and only an empty subset can be removed. +fn row_menu(id: String, kind: ComponentKind, empty: bool) -> Element<'static, Message> { let entry = |label: &str, action: MenuAction, on: bool| { menu_entry(t!(label).into_owned(), on.then(|| msg(SheetSetMsg::Menu(id.clone(), action)))) }; - let sheet = kind == ComponentKind::Sheet; - let mut items = vec![ - entry("Open", MenuAction::Open, sheet), - menu_separator(), - entry("New Sheet...", MenuAction::NewSheet, !sheet), - entry("New Subset...", MenuAction::NewSubset, !sheet), - entry("Import Layout as Sheet...", MenuAction::ImportLayout, !sheet), - menu_separator(), - entry("Rename & Renumber...", MenuAction::Rename, sheet), - entry(if kind == ComponentKind::Subset { "Remove Subset" } else { "Remove Sheet" }, MenuAction::Remove, kind != ComponentKind::SheetSet), - ]; - if kind == ComponentKind::SheetSet { - items.push(entry("Close Sheet Set", MenuAction::Close, true)); + let mut items = Vec::new(); + match kind { + ComponentKind::SheetSet => items.extend([entry("Close Sheet Set", MenuAction::Close, true), menu_separator()]), + ComponentKind::Sheet => items.extend([entry("Open", MenuAction::Open, true), menu_separator()]), + ComponentKind::Subset => {} + } + items.push(entry("New Sheet...", MenuAction::NewSheet, true)); + if kind != ComponentKind::Sheet { + items.push(entry("New Subset...", MenuAction::NewSubset, true)); + } + items.push(entry("Import Layout as Sheet...", MenuAction::ImportLayout, true)); + match kind { + ComponentKind::SheetSet => {} + ComponentKind::Subset => items.extend([ + menu_separator(), + entry("Rename Subset...", MenuAction::Properties, true), + entry("Remove Subset", MenuAction::Remove, empty), + ]), + ComponentKind::Sheet => items.extend([ + menu_separator(), + entry("Rename & Renumber...", MenuAction::Rename, true), + entry("Remove Sheet", MenuAction::Remove, true), + ]), } items.push(menu_separator()); items.push(entry("Properties...", MenuAction::Properties, true)); @@ -391,7 +407,8 @@ fn tree_row<'a>( })) .on_press(msg(SheetSetMsg::Select(id.clone()))) .on_double_click(msg(SheetSetMsg::Activate(id.clone()))); - iced_aw::ContextMenu::new(area, move || row_menu(id.clone(), kind)).into() + let empty = !el.children.iter().any(|c| matches!(ComponentKind::of(c), Some(ComponentKind::Subset | ComponentKind::Sheet))); + iced_aw::ContextMenu::new(area, move || row_menu(id.clone(), kind, empty)).into() } fn push_tree<'a>( From 0a45e59c2f54f9f5530954d758d1338d70ce9b9b Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 12:08:29 +0300 Subject: [PATCH 04/17] Sheet sets: approved dialogs, sheet status, view categories, model views, navigation fields Remove Sheets confirmation, Open in drawing editor, Select Layout as Sheet Template for subsets that prompt, Import Layouts as Sheets with status and prefix, Rename & Renumber options with Previous/Next and real layout/file renames, New Subset folder hierarchy and publish setting, subset properties rows as the reference shows them, sheet status badges (SSMSHEETSTATUS / SSMPOLLTIME), Sheet Views by category with the View Category dialog, Model Views locations, Field dialog SheetSet/SheetView navigation, and .dwt/.dws read as DWG. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 39 ++ locales/bg-BG/opencadstudio.ftl | 39 ++ locales/cs-CZ/opencadstudio.ftl | 39 ++ locales/de-DE/opencadstudio.ftl | 39 ++ locales/el-GR/opencadstudio.ftl | 39 ++ locales/en-US/opencadstudio.ftl | 39 ++ locales/es-ES/opencadstudio.ftl | 39 ++ locales/fi-FI/opencadstudio.ftl | 39 ++ locales/fr-FR/opencadstudio.ftl | 39 ++ locales/hi-IN/opencadstudio.ftl | 39 ++ locales/hu-HU/opencadstudio.ftl | 39 ++ locales/it-IT/opencadstudio.ftl | 39 ++ locales/ja-JP/opencadstudio.ftl | 39 ++ locales/ko-KR/opencadstudio.ftl | 39 ++ locales/nl-NL/opencadstudio.ftl | 39 ++ locales/pl-PL/opencadstudio.ftl | 39 ++ locales/pt-BR/opencadstudio.ftl | 39 ++ locales/ru-RU/opencadstudio.ftl | 39 ++ locales/tr-TR/opencadstudio.ftl | 39 ++ locales/zh-CN/opencadstudio.ftl | 39 ++ locales/zh-TW/opencadstudio.ftl | 39 ++ src/app/commands/field.rs | 53 ++- src/app/commands/sheet_set.rs | 660 ++++++++++++++++++++++++++++---- src/app/control/actions.rs | 70 ++++ src/app/view/modal.rs | 9 +- src/io/mod.rs | 5 +- src/locale_catalog.rs | 39 ++ src/ui/window/field_dialog.rs | 175 ++++++++- src/ui/window/sheet_set.rs | 653 ++++++++++++++++++++++++++----- 31 files changed, 2287 insertions(+), 200 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 864e44cb2..5c8b0f296 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=3f5d40a#3f5d40a82368cd2dd5e5c44388f8630647ff3533" +source = "git+https://github.com/ramox81/cadcodec.git?rev=d86f57d#d86f57d5d179bc57fe7826d70454bece40c20320" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index 8efe2a874..fdb912af0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "3f5d40a" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "d86f57d" } [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 20ccb54ce..f53bed195 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4407,6 +4407,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 457edab5c..e2e9bbfa8 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4394,6 +4394,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 762a52473..72691ae7f 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4392,6 +4392,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 14b3a2d12..f7bc3e6df 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4393,6 +4393,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index e393c26e6..915802762 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4395,6 +4395,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index d7498da27..8e2758d61 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4396,6 +4396,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index c62ac3377..f73d3dfbd 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4395,6 +4395,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index cf0c70d6b..d1165dea6 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4394,6 +4394,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 3392b9e2a..2aa271973 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4395,6 +4395,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 0d2435a77..024cb6214 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4383,6 +4383,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7b4b9834c..7e3230eec 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4394,6 +4394,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 9cf03a923..9bea307db 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4394,6 +4394,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 1479bab43..8e551ef06 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4383,6 +4383,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 29fa2501e..62ed4f766 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4394,6 +4394,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index c9b107482..1cb7147ef 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4393,6 +4393,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 1b8174e41..834d3a027 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4394,6 +4394,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index f573797fe..454ca237e 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4395,6 +4395,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 61d26d9bd..b6ba273b7 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4396,6 +4396,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index ff6b922ff..c5264c1f7 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4355,6 +4355,45 @@ view = .ss-new-sheet-location = Yeni sayfa konumu .ss-remove-subset = Alt Kümeyi Kaldır .ss-rename-subset = Alt Kümeyi Yeniden Adlandır... + .ss-status-missing = yok + .ss-status-open = açık + .ss-view-by-category = Kategoriye göre + .ss-view-by-sheet = Sayfaya göre + .ss-import-layouts-as-sheets = Düzenleri Sayfa Olarak İçe Aktar + .ss-remove-sheets = Sayfaları Kaldır + .ss-view-category = Görünüm Kategorisi + .ss-new-view-category = Yeni Görünüm Kategorisi… + .ss-add-new-location = Yeni Konum Ekle… + .ss-remove-location = Konumu Kaldır + .ss-publish-by-sheet = Sayfanın 'Yayımlamaya dahil et' ayarına göre + .ss-do-not-publish-sheets = Sayfaları yayımlama + .ss-open-in-drawing-editor = Çizim düzenleyicide aç + .ss-create-folder-hierarchy = Klasör hiyerarşisi oluştur + .ss-publish-sheets-in-subset = Alt kümedeki sayfaları yayımla + .ss-rename-options = Yeniden adlandırma seçenekleri + .ss-rename-layout-to-match = Düzeni eşleşecek şekilde adlandır: + .ss-prefix-with-sheet-number = Önüne sayfa numarası + .ss-rename-drawing-file-to-match = Çizim dosyasını eşleşecek şekilde adlandır: + .ss-available-for-import = Hazır + .ss-belongs-to-sheet-set = Başka kümede: __ocs_fmt_0__ + .ss-drawings = Çizimler + .ss-select-drawing-files-containing-layouts = Düzen içeren çizim dosyalarını seçin + .ss-browse-for-drawings = Çizimlere Gözat… + .ss-a-layout-can-belong-to-only-one-sheet-set = Bir düzen yalnız bir sayfa kümesine ait olabilir. Başka bir kümeye ait düzeni içe aktarmak için düzenin kopyasını oluşturun. + .ss-prefix-sheet-titles-with-file-name = Sayfa başlıklarına dosya adını önek olarak ekle + .ss-import-checked = İşaretlileri İçe Aktar + .ss-template = Şablon + .ss-drawing-template-file-name = Çizim şablonu dosya adı + .ss-select-a-layout-to-create-new-sheets = Yeni sayfalar için bir düzen seçin + .ss-category-name = Kategori adı + .ss-select-the-callout-blocks = Bu kategoride kullanılacak açıklama blokları + .ss-selected-blocks-will-be-available = Seçilen bloklar görünüm sekmesinden açıklama bloğu eklerken sunulur. + .ss-add-blocks = Blok Ekle… + .ss-layout-and-drawing-not-deleted = (Düzen ve çizim dosyası silinmez.) + .ss-remove-sheet-question = "__ocs_fmt_0__" sayfası "__ocs_fmt_1__" sayfa kümesinden kaldırılsın mı? + .ss-drawing-open-cannot-rename = Çizim açık; dosyasını yeniden adlandırmak için kapatın. + .ss-category-name-empty = Kategori adı boş olamaz. + .ss-associate-hyperlink = Köprü ilişkilendir .ss-remove-sheet = Sayfayı Kaldır .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Çizim içeren bir veya daha fazla klasör belirtmenizi sağlar. Bu çizimlerdeki düzenler sayfa kümesine otomatik olarak aktarılabilir. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Alt kümesi ve sayfası olmayan bir sayfa kümesi oluşturur. diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 5943c0419..508d87136 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4378,6 +4378,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 96c93c254..497b58348 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4390,6 +4390,45 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-status-missing = missing + .ss-status-open = open + .ss-view-by-category = View by category + .ss-view-by-sheet = View by sheet + .ss-import-layouts-as-sheets = Import Layouts as Sheets + .ss-remove-sheets = Remove Sheets + .ss-view-category = View Category + .ss-new-view-category = New View Category... + .ss-add-new-location = Add New Location... + .ss-remove-location = Remove Location + .ss-publish-by-sheet = Publish by Sheet 'Include for Publish' Setting + .ss-do-not-publish-sheets = Do Not Publish Sheets + .ss-open-in-drawing-editor = Open in drawing editor + .ss-create-folder-hierarchy = Create folder hierarchy + .ss-publish-sheets-in-subset = Publish sheets in subset + .ss-rename-options = Rename options + .ss-rename-layout-to-match = Rename layout to match: + .ss-prefix-with-sheet-number = Prefix with sheet number + .ss-rename-drawing-file-to-match = Rename drawing file to match: + .ss-available-for-import = Available for import + .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ + .ss-drawings = Drawings + .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts + .ss-browse-for-drawings = Browse for Drawings... + .ss-a-layout-can-belong-to-only-one-sheet-set = A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it. + .ss-prefix-sheet-titles-with-file-name = Prefix sheet titles with file name + .ss-import-checked = Import Checked + .ss-template = Template + .ss-drawing-template-file-name = Drawing template file name + .ss-select-a-layout-to-create-new-sheets = Select a layout to create new sheets + .ss-category-name = Category name + .ss-select-the-callout-blocks = Select the callout blocks to be used in this category + .ss-selected-blocks-will-be-available = Selected blocks will be available for selection when you insert a callout block from the view tab. + .ss-add-blocks = Add Blocks... + .ss-layout-and-drawing-not-deleted = (The layout and the drawing file will not be deleted.) + .ss-remove-sheet-question = Are you sure that you want to remove __ocs_fmt_0__ Sheet from __ocs_fmt_1__ Sheet Set? + .ss-drawing-open-cannot-rename = The drawing is open; close it to rename its file. + .ss-category-name-empty = The category name cannot be empty. + .ss-associate-hyperlink = Associate hyperlink .ss-remove-sheet = Remove Sheet .ss-lets-you-specify-one-or-more-folders-containing-drawings-the = Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set. .ss-creates-a-sheet-set-with-no-subsets-or-sheets = Creates a sheet set with no subsets or sheets. diff --git a/src/app/commands/field.rs b/src/app/commands/field.rs index f6c1f6820..6d1cf2499 100644 --- a/src/app/commands/field.rs +++ b/src/app/commands/field.rs @@ -4,7 +4,7 @@ use crate::app::{Message, OpenCADStudio}; use crate::command::CadCommand; use crate::modules::annotate::field_cmd::{FieldObjectPickCommand, FieldPlaceCommand, FieldTablePickCommand}; -use crate::ui::window::field_dialog::{ +use crate::ui::window::field_dialog::{nav_props, fields_of, object_properties, FieldDialogMsg, FieldDialogState, FieldTarget, DATE_FORMATS, NAMED_TYPES, }; @@ -110,6 +110,8 @@ impl OpenCADStudio { state.ss_set_custom = names(codec::sheet_set::CUSTOM_SHEET_SET_PROP); state.ss_set_name = db.name().to_string(); } + state.ss_sets = self.sheet_set.sets.clone(); + state.ss_set = self.sheet_set.current.unwrap_or(0); self.field_dialog = Some(state); self.fill_named_objects(); self.refresh_field_preview(); @@ -190,8 +192,57 @@ impl OpenCADStudio { // Sheet set fields start in Title case. state.text_case = if crate::ui::window::field_dialog::SHEET_SET_FIELDS.contains(&n) { 4 } else { 0 }; state.ss_custom.clear(); + state.ss_node.clear(); + state.ss_prop.clear(); + // SheetSet starts on the set and its first property; SheetView on nothing. + if n == "SheetSet" { + if let Some(db) = state.ss_sets.get(state.ss_set) { + state.ss_node = db.sheet_set().id().to_string(); + state.ss_prop = nav_props(db, &state.ss_node).first().map(|p| p.0.clone()).unwrap_or_default(); + } + } } FieldDialogMsg::SsCustom(name) => state.ss_custom = name, + FieldDialogMsg::SsSet(k) => { + state.ss_set = k; + state.ss_node.clear(); + state.ss_prop.clear(); + } + FieldDialogMsg::SsNode(id) => { + state.ss_prop = state.ss_sets.get(state.ss_set).and_then(|db| nav_props(db, &id).first().map(|p| p.0.clone())).unwrap_or_default(); + state.ss_node = id; + } + FieldDialogMsg::SsProp(p) => state.ss_prop = p, + FieldDialogMsg::SsHref(v) => state.ss_href = v, + FieldDialogMsg::SsBrowse => { + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Open Sheet Set").as_ref()) + .add_filter(crate::t!("Sheet Set (*.dst)").as_ref(), &["dst", "DST"]) + .pick_file() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |p| Message::FieldDialog(FieldDialogMsg::SsPicked(p)), + ); + } + FieldDialogMsg::SsPicked(None) => {} + FieldDialogMsg::SsPicked(Some(path)) => match codec::sheet_set::SheetSetDatabase::read(&path.to_string_lossy()) { + Ok(db) => { + let key = codec::sheet_set::path_key(&path.to_string_lossy()); + state.ss_set = match state.ss_sets.iter().position(|d| d.path.as_deref().is_some_and(|p| codec::sheet_set::path_key(p) == key)) { + Some(k) => k, + None => { + state.ss_sets.push(db); + state.ss_sets.len() - 1 + } + }; + state.ss_node.clear(); + state.ss_prop.clear(); + } + Err(e) => self.command_line.push_error(&format!("{}: {e}", path.display())), + }, FieldDialogMsg::SsPlaceholder(k) => { state.ss_placeholder = k; state.ss_custom.clear(); diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index a32707a9f..da1036de1 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -4,8 +4,8 @@ use crate::app::{Message, OpenCADStudio}; use crate::ui::window::sheet_set::{ - FieldId, Form, FormKind, FoundLayout, MenuAction, PropRow, Properties, RowKey, SheetSetMsg, SsDialog, - Wizard, WizardMsg, + Category, FieldId, Form, FormKind, FoundLayout, ImportRow, MenuAction, PropRow, Properties, RowKey, SheetSetMsg, + SheetStatus, SsDialog, TemplatePick, Wizard, WizardMsg, }; use codec::sheet_set::{self as ss, ComponentKind, LayoutReference, SheetSetData, SheetSetDatabase}; use iced::Task; @@ -63,6 +63,77 @@ fn folder_of(path: &str) -> String { /// Template of a component for new sheets: its own `DefDwtLayout`, else /// the nearest parent's. +/// Whether new sheets of `component` prompt for their template: its own +/// "Prompt for template", else the nearest parent's. +fn prompts_for_template(db: &SheetSetDatabase, component: &str) -> bool { + let mut id = component.to_string(); + for _ in 0..32 { + let Some(el) = db.find(&id) else { return false }; + if let Some(v) = el.prop("PromptForDwt") { + return !matches!(v.trim(), "" | "0"); + } + match db.parent_of(&id) { + Some(p) => id = p.id().to_string(), + None => return false, + } + } + false +} + +/// The layouts of a drawing that can become sheets, each with the sheet set +/// it already belongs to: the drawing's sheet link, or this set. +fn import_rows(path: &Path, db: &SheetSetDatabase) -> Result, String> { + let doc = crate::io::load_file(path).map_err(|e| e.to_string())?; + let drawing = path.to_string_lossy().to_string(); + let link = doc.sheet_set_data(); + Ok(paper_layouts(&doc) + .into_iter() + .map(|(layout, handle)| { + let mine = db.sheet_for(&drawing, Some(&layout)).is_some_and(|s| { + s.named("Layout").and_then(|r| r.prop("Name")).is_some_and(|n| n.eq_ignore_ascii_case(&layout)) + }); + let owner = if mine { + Some(db.name().to_string()) + } else { + link.as_ref().filter(|l| l.layout_name.eq_ignore_ascii_case(&layout) && !l.sheet_set_file_name.is_empty()).map(|l| { + SheetSetDatabase::read(&l.sheet_set_file_name) + .map(|d| d.name().to_string()) + .unwrap_or_else(|_| Path::new(&l.sheet_set_file_name).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default()) + }) + }; + ImportRow { drawing: drawing.clone(), layout, handle, on: owner.is_none(), owner } + }) + .collect()) +} + +/// Rename & Renumber: layout and file names follow the options. +fn follow_rename(f: &mut Form) { + let name = |prefix: bool| if prefix { format!("{} {}", f.number.trim(), f.title.trim()).trim().to_string() } else { f.title.trim().to_string() }; + if f.rename[0] { + f.layout_name = name(f.rename[1]); + } + if f.rename[2] { + f.file_name = format!("{}.dwg", name(f.rename[3])); + } +} + +fn rename_form(db: &SheetSetDatabase, set: usize, id: &str) -> Form { + let el = db.find(id); + let reference = db.layout_reference(id, "Layout").unwrap_or_default(); + let file = Path::new(&reference.file_name); + Form { + kind: FormKind::Rename, + set, + component: id.to_string(), + number: el.and_then(|e| e.prop("Number")).unwrap_or("").to_string(), + title: el.and_then(|e| e.prop("Title")).unwrap_or("").to_string(), + layout_name: reference.name.clone(), + file_name: file.file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(), + folder: file.parent().map(|p| p.to_string_lossy().to_string()).unwrap_or_default(), + ..Form::default() + } +} + fn template_of(db: &SheetSetDatabase, component: &str) -> Option { let mut id = component.to_string(); for _ in 0..32 { @@ -218,6 +289,12 @@ impl OpenCADStudio { /// Publish the open sets to the field engine and redraw every field. fn sheet_sets_changed(&mut self) { crate::entities::field::set_sheet_sets(self.sheet_set.sets.clone()); + self.refresh_locations(); + if self.sheet_set.settings.sheet_status >= 1 { + self.refresh_sheet_status(); + } else { + self.sheet_set.status.clear(); + } for tab in &mut self.tabs { let changes: Vec<_> = tab .scene @@ -365,6 +442,7 @@ impl OpenCADStudio { SheetSetMsg::PickSet(Some(k)) => { if k < self.sheet_set.sets.len() { self.sheet_set.current = Some(k); + self.sheet_sets_changed(); } } SheetSetMsg::PickSet(None) => return self.dispatch_command("OPENSHEETSET"), @@ -384,7 +462,80 @@ impl OpenCADStudio { self.sheet_set.collapsed.insert(id); } } - SheetSetMsg::Poll => self.poll_sheet_sets(), + SheetSetMsg::Poll => { + self.poll_sheet_sets(); + // SSMSHEETSTATUS 2: the status is taken again every SSMPOLLTIME seconds. + let every = std::time::Duration::from_secs(u64::from(self.sheet_set.settings.poll_time.max(20))); + if self.sheet_set.settings.sheet_status == 2 && self.sheet_set.status_at.is_none_or(|t| t.elapsed() >= every) { + self.refresh_sheet_status(); + } + } + SheetSetMsg::BrowseDrawings => { + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Select Drawing").as_ref()) + .add_filter(crate::t!("Drawing (*.dwg)").as_ref(), &["dwg", "DWG"]) + .pick_files() + .await + .map(|hs| hs.iter().map(crate::sys::handle_path).collect::>()) + .unwrap_or_default() + }, + |p| Message::SheetSet(SheetSetMsg::ImportAdd(p)), + ) + } + SheetSetMsg::ImportAdd(paths) => self.import_add(paths), + SheetSetMsg::Previous => self.rename_step(false), + SheetSetMsg::Next => self.rename_step(true), + SheetSetMsg::ViewsByCategory(v) => self.sheet_set.by_category = v, + SheetSetMsg::NewCategory => self.open_category(None), + SheetSetMsg::CategoryProperties(id) => self.open_category(Some(id)), + SheetSetMsg::CategoryRemove(id) => { + if let Some(db) = self.sheet_set.db_mut() { + db.remove(&id); + } + self.save_current_sheet_set(); + } + SheetSetMsg::AddBlocks => { + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Select Drawing").as_ref()) + .add_filter(crate::t!("Drawing (*.dwg)").as_ref(), &["dwg", "DWG", "dwt", "DWT"]) + .pick_file() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |p| Message::SheetSet(SheetSetMsg::BlocksPicked(p)), + ) + } + SheetSetMsg::BlocksPicked(Some(path)) => self.category_add_blocks(path), + SheetSetMsg::BlocksPicked(None) | SheetSetMsg::LocationPicked(None) => {} + SheetSetMsg::AddLocation => { + return Task::perform( + async { + crate::sys::file_dialog() + .set_title(crate::t!("Browse for Folder").as_ref()) + .pick_folder() + .await + .map(|h| crate::sys::handle_path(&h)) + }, + |p| Message::SheetSet(SheetSetMsg::LocationPicked(p)), + ) + } + SheetSetMsg::LocationPicked(Some(path)) => { + if let Some(db) = self.sheet_set.db_mut() { + db.add_resource(&path.to_string_lossy()); + } + self.save_current_sheet_set(); + } + SheetSetMsg::RemoveLocation(id) => { + if let Some(db) = self.sheet_set.db_mut() { + db.remove(&id); + } + self.save_current_sheet_set(); + } + SheetSetMsg::OpenDrawing(path) => return self.update(Message::OpenRecent(PathBuf::from(path))), SheetSetMsg::Refresh => { if let Some(path) = self.sheet_set.db().and_then(|db| db.path.clone()) { match SheetSetDatabase::read(&path) { @@ -437,12 +588,52 @@ impl OpenCADStudio { } SheetSetMsg::Input(field, v) => self.dialog_input(field, v), SheetSetMsg::Toggle(field, v) => { - if let (FieldId::Hierarchy, Some(SsDialog::Wizard(w))) = (field, self.sheet_set.dialog.as_mut()) { - w.hierarchy = v; + let parent_folder = match self.sheet_set.dialog.as_ref() { + Some(SsDialog::Form(f)) if f.kind == FormKind::NewSubset => { + self.sheet_set.sets.get(f.set).map(|db| sheet_folder_of(db, &f.component)).unwrap_or_default() + } + _ => String::new(), + }; + match (field, self.sheet_set.dialog.as_mut()) { + (FieldId::Hierarchy, Some(SsDialog::Wizard(w))) => w.hierarchy = v, + (FieldId::Hierarchy, Some(SsDialog::Form(f))) => { + f.hierarchy = v; + f.folder = if v { Path::new(&parent_folder).join(f.title.trim()).to_string_lossy().to_string() } else { parent_folder }; + } + (FieldId::Publish, Some(SsDialog::Form(f))) => f.publish = v, + (FieldId::OpenAfter, Some(SsDialog::Form(f))) => f.open_after = v, + (FieldId::ImportPrefix, Some(SsDialog::Form(f))) => f.prefix = v, + (FieldId::ImportRow(k), Some(SsDialog::Form(f))) => { + if let Some(r) = f.rows.get_mut(k) { + r.on = v && r.owner.is_none(); + } + } + (FieldId::RenameOption(k), Some(SsDialog::Form(f))) => { + f.rename[k] = v; + // A prefix needs its "Sheet title" option. + if !f.rename[0] { + f.rename[1] = false; + } + if !f.rename[2] { + f.rename[3] = false; + } + follow_rename(f); + } + (FieldId::CategoryBlock(k), Some(SsDialog::Category(c))) => { + if let Some(b) = c.blocks.get_mut(k) { + b.2 = v; + } + } + (FieldId::Row(r), Some(SsDialog::Properties(p))) => { + if let Some(row) = p.rows.get_mut(r) { + row.value = if v { "Publish by Sheet 'Include for Publish' Setting" } else { "Do Not Publish Sheets" }.into(); + } + } + _ => {} } } SheetSetMsg::Choose(field, k) => match (field, self.sheet_set.dialog.as_mut()) { - (FieldId::Layout, Some(SsDialog::Form(f))) => f.layout = k, + (FieldId::Layout, Some(SsDialog::Template(t))) => t.layout = k, (FieldId::CustomOwner, Some(SsDialog::Properties(p))) => { if let Some(a) = p.adding.as_mut() { a.2 = k == 1; @@ -552,23 +743,26 @@ impl OpenCADStudio { match action { MenuAction::Open => return self.open_sheet(&id), MenuAction::NewSheet => { - // Next number: the last sheet's number + 1 when it ends in digits. - let template = template_of(db, &id) - .map(|t| format!("{} ({})", t.name, t.file_name)) - .unwrap_or_else(|| crate::t!("(default new drawing)").into_owned()); - self.sheet_set.dialog = Some(SsDialog::Form(Form { + let template = template_of(db, &id); + let form = Form { kind: FormKind::NewSheet, set, component: id.clone(), - number: String::new(), - title: String::new(), - file_name: String::new(), folder: sheet_folder_of(db, &id), - drawing: template, - layouts: Vec::new(), - layout: 0, - error: None, - })); + drawing: template + .as_ref() + .map(|t| format!("{} ({})", t.name, t.file_name)) + .unwrap_or_else(|| crate::t!("(default new drawing)").into_owned()), + ..Form::default() + }; + // A subset that prompts for its template asks for it first. + if prompts_for_template(db, &id) { + let file = template.map(|t| t.file_name).unwrap_or_default(); + let layouts = crate::io::load_file(Path::new(&file)).map(|d| paper_layouts(&d).into_iter().map(|(n, _)| n).collect()).unwrap_or_default(); + self.sheet_set.dialog = Some(SsDialog::Template(TemplatePick { form, file, layouts, layout: 0 })); + } else { + self.sheet_set.dialog = Some(SsDialog::Form(form)); + } self.active_modal = Some(crate::app::ModalKind::SheetSet); } MenuAction::NewSubset => { @@ -583,14 +777,10 @@ impl OpenCADStudio { kind: FormKind::NewSubset, set, component: id.clone(), - number: String::new(), title: format!("New Subset ({n})"), - file_name: String::new(), folder: sheet_folder_of(db, &id), - drawing: String::new(), - layouts: Vec::new(), - layout: 0, - error: None, + publish: true, + ..Form::default() })); self.active_modal = Some(crate::app::ModalKind::SheetSet); } @@ -609,28 +799,23 @@ impl OpenCADStudio { ); } MenuAction::Rename => { - let el = db.find(&id); - self.sheet_set.dialog = Some(SsDialog::Form(Form { - kind: FormKind::Rename, - set, - component: id.clone(), - number: el.and_then(|e| e.prop("Number")).unwrap_or("").to_string(), - title: el.and_then(|e| e.prop("Title")).unwrap_or("").to_string(), - file_name: String::new(), - folder: String::new(), - drawing: String::new(), - layouts: Vec::new(), - layout: 0, - error: None, - })); + self.sheet_set.dialog = Some(SsDialog::Form(rename_form(db, set, &id))); self.active_modal = Some(crate::app::ModalKind::SheetSet); } MenuAction::Remove => { - if let Some(db) = self.sheet_set.db_mut() { - db.remove(&id); + // A sheet asks first; a subset can only be removed while empty. + if db.find(&id).and_then(ComponentKind::of) == Some(ComponentKind::Sheet) { + let sheet = db.find(&id).map(ss::number_and_title).unwrap_or_default(); + let question = crate::tf!("Are you sure that you want to remove {} Sheet from {} Sheet Set?", sheet, db.name()).into_owned(); + self.sheet_set.dialog = Some(SsDialog::Confirm(set, id.clone(), question)); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } else if db.find(&id).is_some_and(|el| el.children.iter().all(|c| ComponentKind::of(c).is_none())) { + if let Some(db) = self.sheet_set.db_mut() { + db.remove(&id); + } + self.sheet_set.selected = None; + self.save_current_sheet_set(); } - self.sheet_set.selected = None; - self.save_current_sheet_set(); } MenuAction::Properties => { let p = build_properties(db, &id, Some(set)); @@ -766,15 +951,33 @@ impl OpenCADStudio { } _ => {} }, + Some(SsDialog::Category(c)) => { + if field == FieldId::Name { + c.name = v; + } + } + Some(SsDialog::Confirm(..)) | Some(SsDialog::Template(_)) => {} Some(SsDialog::Form(f)) => { let file_follows = f.file_name == format!("{} {}", f.number, f.title).trim(); match field { FieldId::Number => f.number = v, - FieldId::Title => f.title = v, + FieldId::Title => { + // A folder hierarchy follows the subset name. + if f.kind == FormKind::NewSubset && f.hierarchy { + if let Some(parent) = Path::new(&f.folder).parent() { + f.folder = parent.join(v.trim()).to_string_lossy().to_string(); + } + } + f.title = v + } FieldId::FileName => f.file_name = v, FieldId::Folder => f.folder = v, + FieldId::LayoutName => f.layout_name = v, _ => {} } + if f.kind == FormKind::Rename { + follow_rename(f); + } // The file name follows "Number Title" until edited. if f.kind == FormKind::NewSheet && file_follows && field != FieldId::FileName { f.file_name = format!("{} {}", f.number, f.title).trim().to_string(); @@ -865,8 +1068,9 @@ impl OpenCADStudio { let (Some(set), Some(parent)) = (self.sheet_set.current, self.sheet_set_import_parent.take()) else { return; }; - let layouts = match crate::io::load_file(&path) { - Ok(doc) => paper_layouts(&doc), + let Some(db) = self.sheet_set.db() else { return }; + let rows = match import_rows(&path, db) { + Ok(rows) => rows, Err(e) => { self.command_line.push_error(&format!("{}: {e}", path.display())); return; @@ -876,20 +1080,255 @@ impl OpenCADStudio { kind: FormKind::ImportLayout, set, component: parent, - number: String::new(), - title: String::new(), - file_name: String::new(), - folder: String::new(), - drawing: path.to_string_lossy().to_string(), - layouts, - layout: 0, - error: None, + rows, + prefix: true, + ..Form::default() })); self.active_modal = Some(crate::app::ModalKind::SheetSet); } + /// Browse for Drawings of the open Import dialog: add their layouts. + fn import_add(&mut self, paths: Vec) { + let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_ref() else { return }; + let Some(db) = self.sheet_set.sets.get(f.set) else { return }; + let mut added = Vec::new(); + for p in paths { + match import_rows(&p, db) { + Ok(rows) => added.extend(rows), + Err(e) => self.command_line.push_error(&format!("{}: {e}", p.display())), + } + } + if let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_mut() { + for r in added { + if !f.rows.iter().any(|o| ss::path_key(&o.drawing) == ss::path_key(&r.drawing) && o.layout == r.layout) { + f.rows.push(r); + } + } + } + } + + /// Rename & Renumber: number and title into the set, and the layout and + /// drawing file renamed when they changed. + fn apply_rename(&mut self, f: &Form) -> Result<(), String> { + let Some(db) = self.sheet_set.sets.get(f.set) else { return Ok(()) }; + let old = db.layout_reference(&f.component, "Layout").unwrap_or_default(); + let new_layout = f.layout_name.trim().to_string(); + let new_file = if old.file_name.is_empty() || f.file_name.trim().is_empty() { + old.file_name.clone() + } else { + Path::new(&f.folder).join(f.file_name.trim()).to_string_lossy().to_string() + }; + let file_changed = !old.file_name.is_empty() && ss::path_key(&new_file) != ss::path_key(&old.file_name); + let layout_changed = !old.name.is_empty() && !new_layout.is_empty() && new_layout != old.name; + let open_tab = self.tab_showing(Path::new(&old.file_name)); + if file_changed { + if open_tab.is_some() { + return Err(crate::t!("The drawing is open; close it to rename its file.").into_owned()); + } + if Path::new(&new_file).exists() { + return Err(crate::tf!("{} already exists.", new_file).into_owned()); + } + } + if layout_changed { + match open_tab { + Some(k) => { + self.tabs[k].scene.rename_layout(&old.name, &new_layout); + if let Some(mut link) = self.tabs[k].scene.document.sheet_set_data() { + link.layout_name = new_layout.clone(); + self.tabs[k].scene.document.set_sheet_set_data(&link); + } + } + None => { + let mut scene = crate::scene::Scene::new(); + scene.document = crate::io::load_file(Path::new(&old.file_name)).map_err(|e| e.to_string())?; + scene.rename_layout(&old.name, &new_layout); + if let Some(mut link) = scene.document.sheet_set_data() { + link.layout_name = new_layout.clone(); + scene.document.set_sheet_set_data(&link); + } + crate::io::save(&scene.document, Path::new(&old.file_name)).map_err(|e| e.to_string())?; + } + } + } + if file_changed { + std::fs::rename(&old.file_name, &new_file).map_err(|e| e.to_string())?; + } + let Some(db) = self.sheet_set.sets.get_mut(f.set) else { return Ok(()) }; + if let Some(el) = db.find_mut(&f.component) { + el.set_prop("Number", f.number.trim()); + el.set_prop("Title", f.title.trim()); + } + if layout_changed || file_changed { + db.set_layout_reference( + &f.component, + "Layout", + &LayoutReference { + file_name: ss::native_path(&new_file), + name: if layout_changed { new_layout } else { old.name }, + handle: old.handle, + }, + ); + } + Ok(()) + } + + /// Rename & Renumber < Previous / Next >: apply, then the neighbouring sheet. + fn rename_step(&mut self, forward: bool) { + let Some(SsDialog::Form(f)) = self.sheet_set.dialog.take() else { return }; + if let Err(e) = self.apply_rename(&f) { + self.sheet_set.dialog = Some(SsDialog::Form(Form { error: Some(e), ..f })); + return; + } + self.sheet_set.current = Some(f.set); + self.save_current_sheet_set(); + let Some(db) = self.sheet_set.sets.get(f.set) else { return }; + let ids: Vec = db.sheets().iter().map(|s| s.id().to_string()).collect(); + let k = ids.iter().position(|i| *i == f.component).unwrap_or(0); + let next = if forward { (k + 1).min(ids.len().saturating_sub(1)) } else { k.saturating_sub(1) }; + let mut form = rename_form(db, f.set, &ids[next]); + form.rename = f.rename; + follow_rename(&mut form); + self.sheet_set.selected = Some(ids[next].clone()); + self.sheet_set.dialog = Some(SsDialog::Form(form)); + } + + /// The View Category dialog for a new (`None`) or existing category. + fn open_category(&mut self, id: Option) { + let (Some(set), Some(db)) = (self.sheet_set.current, self.sheet_set.db()) else { return }; + let used = id.as_deref().map(|i| db.category_blocks(i)); + let blocks = db + .callout_blocks() + .iter() + .map(|b| { + let label = format!("{} ({})", b.prop("Name").unwrap_or(""), db.resolve_file(b)); + let on = used.as_ref().is_none_or(|u| u.iter().any(|x| x == b.id())); + (b.id().to_string(), label, on) + }) + .collect(); + let name = id.as_deref().and_then(|i| db.find(i)).and_then(|c| c.prop("Name")).unwrap_or("").to_string(); + self.sheet_set.dialog = Some(SsDialog::Category(Category { set, id, name, blocks, error: None })); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + + /// Add Blocks...: the named blocks of a drawing become callout blocks. + fn category_add_blocks(&mut self, path: PathBuf) { + let doc = match crate::io::load_file(&path) { + Ok(d) => d, + Err(e) => { + self.command_line.push_error(&format!("{}: {e}", path.display())); + return; + } + }; + let file = ss::native_path(&path.to_string_lossy()); + let Some(SsDialog::Category(c)) = self.sheet_set.dialog.as_mut() else { return }; + let mut names: Vec<(String, String)> = doc + .block_records + .iter() + .filter(|b| !b.name.starts_with('*') && !b.is_layout()) + .map(|b| (b.name.clone(), format!("{:X}", b.handle.value()))) + .collect(); + names.sort(); + for (name, handle) in names { + c.blocks.push((format!("new:{file}|{name}|{handle}"), format!("{name} ({file})"), true)); + } + } + + fn category_ok(&mut self, mut c: Category) { + let name = c.name.trim().to_string(); + if name.is_empty() { + c.error = Some(crate::t!("The category name cannot be empty.").into_owned()); + self.sheet_set.dialog = Some(SsDialog::Category(c)); + return; + } + self.close_active_modal(); + let Some(db) = self.sheet_set.sets.get_mut(c.set) else { return }; + let mut used = Vec::new(); + for (id, _, on) in &c.blocks { + let id = match id.strip_prefix("new:") { + Some(spec) => { + let mut p = spec.splitn(3, '|'); + let (file, block, handle) = (p.next().unwrap_or(""), p.next().unwrap_or(""), p.next().unwrap_or("")); + match db.add_callout_block(file, block, handle) { + Some(id) => id, + None => continue, + } + } + None => id.clone(), + }; + if *on { + used.push(id); + } + } + db.set_view_category(c.id.as_deref(), &name, &used); + self.sheet_set.current = Some(c.set); + self.save_current_sheet_set(); + } + + /// Model Views: each location folder of the current set and its drawings. + pub(in crate::app) fn refresh_locations(&mut self) { + let Some(db) = self.sheet_set.db() else { + self.sheet_set.locations.clear(); + return; + }; + self.sheet_set.locations = db + .sheet_set() + .named("Resources") + .map(|r| { + r.children + .iter() + .map(|c| { + let folder = db.resolve_file(c); + let mut files: Vec = std::fs::read_dir(&folder) + .map(|d| { + d.filter_map(|e| e.ok()) + .map(|e| e.file_name().to_string_lossy().to_string()) + .filter(|n| n.to_ascii_lowercase().ends_with(".dwg")) + .collect() + }) + .unwrap_or_default(); + files.sort_by_key(|n| n.to_lowercase()); + (c.id().to_string(), folder, files) + }) + .collect() + }) + .unwrap_or_default(); + } + + /// SSMSHEETSTATUS: a sheet whose drawing is missing, or open (its `.dwl` + /// lock file, or a tab of this application). + pub(in crate::app) fn refresh_sheet_status(&mut self) { + self.sheet_set.status_at = Some(std::time::Instant::now()); + self.sheet_set.status.clear(); + if self.sheet_set.settings.sheet_status == 0 { + return; + } + let Some(db) = self.sheet_set.db() else { return }; + let mut status = std::collections::HashMap::new(); + for sheet in db.sheets() { + let Some(r) = db.layout_reference(sheet.id(), "Layout") else { continue }; + let path = Path::new(&r.file_name); + let state = if !path.exists() { + Some(SheetStatus::Missing) + } else if path.with_extension("dwl").exists() || self.tab_showing(path).is_some() { + Some(SheetStatus::Locked) + } else { + None + }; + if let Some(st) = state { + status.insert(sheet.id().to_string(), st); + } + } + self.sheet_set.status = status; + } + fn properties_template(&mut self, path: PathBuf) { let layouts = crate::io::load_file(&path).map(|d| paper_layouts(&d)).unwrap_or_default(); + if let Some(SsDialog::Template(t)) = self.sheet_set.dialog.as_mut() { + t.file = path.to_string_lossy().to_string(); + t.layouts = layouts.into_iter().map(|(n, _)| n).collect(); + t.layout = 0; + return; + } let Some(SsDialog::Properties(p)) = self.sheet_set.dialog.as_mut() else { return; }; @@ -914,6 +1353,29 @@ impl OpenCADStudio { Task::none() } Some(SsDialog::Form(f)) => self.form_ok(f), + Some(SsDialog::Confirm(set, id, _)) => { + self.close_active_modal(); + if let Some(db) = self.sheet_set.sets.get_mut(set) { + db.remove(&id); + } + self.sheet_set.current = Some(set); + self.sheet_set.selected = None; + self.save_current_sheet_set(); + Task::none() + } + Some(SsDialog::Template(t)) => { + let mut form = t.form; + if let Some(layout) = t.layouts.get(t.layout) { + form.drawing = format!("{layout} ({})", t.file); + form.template = Some((t.file, layout.clone())); + } + self.sheet_set.dialog = Some(SsDialog::Form(form)); + Task::none() + } + Some(SsDialog::Category(c)) => { + self.category_ok(c); + Task::none() + } None => Task::none(), } } @@ -1004,9 +1466,10 @@ impl OpenCADStudio { }; match f.kind { FormKind::Rename => { - if let Some(el) = db.find_mut(&f.component) { - el.set_prop("Number", f.number.trim()); - el.set_prop("Title", f.title.trim()); + if let Err(e) = self.apply_rename(&f) { + f.error = Some(e); + self.sheet_set.dialog = Some(SsDialog::Form(f)); + return Task::none(); } } FormKind::NewSubset => { @@ -1016,32 +1479,43 @@ impl OpenCADStudio { self.sheet_set.dialog = Some(SsDialog::Form(f)); return Task::none(); } - if let Some(sub) = db.add_subset(&f.component, &name, "") { - if !f.folder.trim().is_empty() { - db.set_file_reference(&sub, "NewSheetLocation", f.folder.trim()); + let parent_folder = sheet_folder_of(db, &f.component); + if let Some(sub) = db.add_subset(&f.component, &name, "New subset added") { + // Folder hierarchy: the parent's location + the subset name, created now. + let folder = if f.hierarchy { + let folder = Path::new(&parent_folder).join(&name); + let _ = std::fs::create_dir_all(&folder); + folder.to_string_lossy().to_string() + } else { + f.folder.trim().to_string() + }; + if !folder.is_empty() { + db.set_file_reference(&sub, "NewSheetLocation", &folder); + } + if !f.publish { + if let Some(el) = db.find_mut(&sub) { + el.set_prop_vt("OverrideSheetPublish", 2, "-1"); + } } } } FormKind::ImportLayout => { - let Some((layout, handle)) = f.layouts.get(f.layout).cloned() else { + let chosen: Vec<&ImportRow> = f.rows.iter().filter(|r| r.on && r.owner.is_none()).collect(); + if chosen.is_empty() { f.error = Some(crate::t!("The drawing has no layout to import.").into_owned()); self.sheet_set.dialog = Some(SsDialog::Form(f)); return Task::none(); - }; - if db.sheet_for(&f.drawing, Some(&layout)).is_some_and(|s| { - s.named("Layout").and_then(|r| r.prop("Name")).is_some_and(|n| n.eq_ignore_ascii_case(&layout)) - }) { - f.error = Some(crate::t!("The layout is already a sheet of this sheet set.").into_owned()); - self.sheet_set.dialog = Some(SsDialog::Form(f)); - return Task::none(); } - let stem = Path::new(&f.drawing).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default(); - if let Some(sheet) = db.add_sheet(&f.component, "", &format!("{stem} - {layout}"), "") { - db.set_layout_reference( - &sheet, - "Layout", - &LayoutReference { file_name: f.drawing.clone(), name: layout, handle }, - ); + for r in chosen { + let stem = Path::new(&r.drawing).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default(); + let title = if f.prefix { format!("{stem} - {}", r.layout) } else { r.layout.clone() }; + if let Some(sheet) = db.add_sheet(&f.component, "", &title, "") { + db.set_layout_reference( + &sheet, + "Layout", + &LayoutReference { file_name: r.drawing.clone(), name: r.layout.clone(), handle: r.handle.clone() }, + ); + } } } FormKind::NewSheet => return self.create_sheet(f), @@ -1080,7 +1554,10 @@ impl OpenCADStudio { return fail(self, f, crate::tf!("{} already exists.", path.display()).into_owned()); } let layout_name = format!("{} {}", f.number.trim(), title).trim().to_string(); - let template = self.sheet_set.sets.get(f.set).and_then(|db| template_of(db, &f.component)); + let template = match f.template.clone() { + Some((file_name, name)) => Some(LayoutReference { file_name, name, handle: String::new() }), + None => self.sheet_set.sets.get(f.set).and_then(|db| template_of(db, &f.component)), + }; let mut scene = crate::scene::Scene::new(); let source_layout = match template.as_ref().and_then(|t| crate::io::load_file(Path::new(&t.file_name)).ok().map(|d| (d, t.name.clone()))) { Some((doc, name)) => { @@ -1134,11 +1611,14 @@ impl OpenCADStudio { if let Err(e) = crate::io::save(&scene.document, &path) { self.command_line.push_error(&format!("{file}: {e}")); } - // The new sheet is not opened ("Open in drawing editor" is off by default). + // "Open in drawing editor" (off by default) opens the new sheet. self.close_active_modal(); self.sheet_set.current = Some(f.set); - self.sheet_set.selected = Some(sheet); + self.sheet_set.selected = Some(sheet.clone()); self.sheet_sets_changed(); + if f.open_after { + return self.open_sheet(&sheet); + } Task::none() } } @@ -1218,7 +1698,12 @@ pub(in crate::app) fn build_properties(db: &SheetSetDatabase, id: &str, set: Opt ComponentKind::Subset => { title = format!("{} - {}", crate::t!("Subset Properties"), prop("Name")); rows.push(row("Subset", "Subset name", RowKey::Prop("Name"), prop("Name"))); - rows.push(row("Subset", "Description", RowKey::Prop("Desc"), prop("Desc"))); + let publish = if el.prop("OverrideSheetPublish").is_some_and(|v| v.trim() == "-1") { + "Do Not Publish Sheets" + } else { + "Publish by Sheet 'Include for Publish' Setting" + }; + rows.push(row("Subset", "Publish sheets in subset", RowKey::Publish, publish.to_string())); rows.push(row("Subset", "New sheet location", RowKey::Folder("NewSheetLocation"), named_file("NewSheetLocation"))); rows.push(row("Subset", "Sheet creation template", RowKey::Template, template())); rows.push(row("Subset", "Prompt for template", RowKey::PromptTemplate, prompt())); @@ -1284,11 +1769,22 @@ fn write_properties(db: &mut SheetSetDatabase, p: &Properties) { db.set_file_reference(&p.component, name, r.value.trim()); } RowKey::PromptTemplate => { + // The set keeps it as an int (1), a subset as a short (-1). if let Some(el) = db.find_mut(&p.component) { - if r.value == "Yes" { - el.set_prop_vt("PromptForDwt", 3, "1"); + el.remove_named("PromptForDwt"); + match (r.value == "Yes", p.kind) { + (true, ComponentKind::SheetSet) => el.set_prop_vt("PromptForDwt", 3, "1"), + (true, _) => el.set_prop_vt("PromptForDwt", 2, "-1"), + _ => {} + } + } + } + RowKey::Publish => { + if let Some(el) = db.find_mut(&p.component) { + if r.value == "Do Not Publish Sheets" { + el.set_prop_vt("OverrideSheetPublish", 2, "-1"); } else { - el.remove_named("PromptForDwt"); + el.remove_named("OverrideSheetPublish"); } } } diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index 20affcc04..6dfe48e1e 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -1135,6 +1135,21 @@ impl OpenCADStudio { "link_url" => F::HyperlinkUrl(v.into()), "plot_scale" => F::PlotScale(index(codec::fields::PLOT_SCALE_FORMATS.len())?), "ss_custom" => F::SsCustom(v.into()), + "ss_set" => F::SsSet(v.parse().map_err(|_| failure("invalid_value", "index"))?), + "ss_dst" => F::SsPicked(Some(std::path::PathBuf::from(v))), + "ss_node" => { + let state = self.field_dialog.as_ref().ok_or_else(|| failure("invalid_state", "No Field dialog"))?; + let db = state.ss_sets.get(state.ss_set).ok_or_else(|| failure("invalid_state", "No sheet set"))?; + let views = state.name == "SheetView"; + let id = fd::nav_nodes(db, views) + .into_iter() + .find(|(_, id, label, _)| label == v || id == v) + .map(|(_, id, _, _)| id) + .ok_or_else(|| failure("invalid_value", "No such node"))?; + F::SsNode(id) + } + "ss_prop" => F::SsProp(v.into()), + "ss_href" => F::SsHref(v == "1"), "ss_placeholder" => F::SsPlaceholder(index(fd::PLACEHOLDER_TYPES.len())?), "ss_scale" => F::SsScaleFormat(index(codec::fields::PLOT_SCALE_FORMATS.len())?), "placeholder" => F::PlaceholderProperty(index(codec::fields::BLOCK_PLACEHOLDER_PROPERTIES.len())?), @@ -1233,6 +1248,61 @@ impl OpenCADStudio { "title" => M::Input(F::Title, arg.into()), "file_name" => M::Input(F::FileName, arg.into()), "pick_layout" => M::Choose(F::Layout, arg.parse().map_err(|_| failure("invalid_value", "index"))?), + "refresh" => M::Refresh, + "open_after" => M::Toggle(F::OpenAfter, arg == "1"), + "row_toggle" => { + let (i, v) = index_value(arg)?; + M::Toggle(F::Row(i), v == "1") + } + "publish" => M::Toggle(F::Publish, arg == "1"), + "layout_name" => M::Input(F::LayoutName, arg.into()), + "rename_opt" => { + let (i, v) = index_value(arg)?; + M::Toggle(F::RenameOption(i.min(3)), v == "1") + } + "import_add" => M::ImportAdd(arg.split('|').map(std::path::PathBuf::from).collect()), + "import_row" => { + let (i, v) = index_value(arg)?; + M::Toggle(F::ImportRow(i), v == "1") + } + "prefix" => M::Toggle(F::ImportPrefix, arg == "1"), + "previous" => M::Previous, + "next" => M::Next, + "views_by_category" => M::ViewsByCategory(arg == "1"), + "new_category" => M::NewCategory, + "category" => { + let id = self + .sheet_set + .db() + .and_then(|db| db.view_categories().iter().find(|c| c.prop("Name") == Some(arg)).map(|c| c.id().to_string())) + .ok_or_else(|| failure("invalid_value", "No such view category"))?; + M::CategoryProperties(id) + } + "remove_category" => { + let id = self + .sheet_set + .db() + .and_then(|db| db.view_categories().iter().find(|c| c.prop("Name") == Some(arg)).map(|c| c.id().to_string())) + .ok_or_else(|| failure("invalid_value", "No such view category"))?; + M::CategoryRemove(id) + } + "category_block" => { + let (i, v) = index_value(arg)?; + M::Toggle(F::CategoryBlock(i), v == "1") + } + "add_blocks" => M::BlocksPicked(Some(std::path::PathBuf::from(arg))), + "add_location" => M::LocationPicked(Some(std::path::PathBuf::from(arg))), + "remove_location" => { + let id = self + .sheet_set + .locations + .iter() + .find(|(_, folder, _)| codec::sheet_set::path_key(folder) == codec::sheet_set::path_key(arg)) + .map(|(id, _, _)| id.clone()) + .ok_or_else(|| failure("invalid_value", "No such location"))?; + M::RemoveLocation(id) + } + "open_drawing" => M::OpenDrawing(arg.into()), "ok" => M::Ok, "cancel" => M::Cancel, _ => return Err(failure("invalid_value", "Unknown sheet set action")), diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 5d1a51e6e..4a3fc1d4e 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -585,7 +585,14 @@ impl OpenCADStudio { let (w, h) = match self.sheet_set.dialog.as_ref()? { crate::ui::window::sheet_set::SsDialog::Wizard(_) => (720, 560), crate::ui::window::sheet_set::SsDialog::Properties(_) => (640, 700), - crate::ui::window::sheet_set::SsDialog::Form(_) => (560, 420), + crate::ui::window::sheet_set::SsDialog::Form(f) => match f.kind { + crate::ui::window::sheet_set::FormKind::Rename => (560, 470), + crate::ui::window::sheet_set::FormKind::ImportLayout => (620, 470), + _ => (560, 420), + }, + crate::ui::window::sheet_set::SsDialog::Confirm(..) => (460, 190), + crate::ui::window::sheet_set::SsDialog::Template(_) => (480, 400), + crate::ui::window::sheet_set::SsDialog::Category(_) => (480, 440), }; sized_flow(ex, w, h, |flow| crate::ui::window::sheet_set::dialog_view(&self.sheet_set, flow)) } diff --git a/src/io/mod.rs b/src/io/mod.rs index 3a4c9d52a..ffaeba33b 100644 --- a/src/io/mod.rs +++ b/src/io/mod.rs @@ -759,7 +759,8 @@ pub fn load_bytes(name: &str, bytes: Vec) -> Result { ext.as_str() }; match format { - "dwg" => { + // Templates (.dwt) and standards (.dws) are drawings in the DWG format. + "dwg" | "dwt" | "dws" => { let mut doc = DwgReader::from_stream(Cursor::new(bytes)) .read() .map_err(|e| e.to_string())?; @@ -1022,7 +1023,7 @@ fn read_file_attempt( }; match effective.as_str() { - "dwg" => read_dwg_path(path, progress, failsafe), + "dwg" | "dwt" | "dws" => read_dwg_path(path, progress, failsafe), "dxf" => read_dxf_path(path, failsafe), _ => Err(ReaderFailure::terminal(format!( "Unsupported file format: .{ext}" diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 14452f731..bc088b692 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -50,6 +50,45 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "New sheet location" => Some(("view", "ss-new-sheet-location")), "Remove Subset" => Some(("view", "ss-remove-subset")), "Rename Subset..." => Some(("view", "ss-rename-subset")), + "missing" => Some(("view", "ss-status-missing")), + "open" => Some(("view", "ss-status-open")), + "View by category" => Some(("view", "ss-view-by-category")), + "View by sheet" => Some(("view", "ss-view-by-sheet")), + "Import Layouts as Sheets" => Some(("view", "ss-import-layouts-as-sheets")), + "Remove Sheets" => Some(("view", "ss-remove-sheets")), + "View Category" => Some(("view", "ss-view-category")), + "New View Category..." => Some(("view", "ss-new-view-category")), + "Add New Location..." => Some(("view", "ss-add-new-location")), + "Remove Location" => Some(("view", "ss-remove-location")), + "Publish by Sheet 'Include for Publish' Setting" => Some(("view", "ss-publish-by-sheet")), + "Do Not Publish Sheets" => Some(("view", "ss-do-not-publish-sheets")), + "Open in drawing editor" => Some(("view", "ss-open-in-drawing-editor")), + "Create folder hierarchy" => Some(("view", "ss-create-folder-hierarchy")), + "Publish sheets in subset" => Some(("view", "ss-publish-sheets-in-subset")), + "Rename options" => Some(("view", "ss-rename-options")), + "Rename layout to match:" => Some(("view", "ss-rename-layout-to-match")), + "Prefix with sheet number" => Some(("view", "ss-prefix-with-sheet-number")), + "Rename drawing file to match:" => Some(("view", "ss-rename-drawing-file-to-match")), + "Available for import" => Some(("view", "ss-available-for-import")), + "Belongs to sheet set {}" => Some(("view", "ss-belongs-to-sheet-set")), + "Drawings" => Some(("view", "ss-drawings")), + "Select drawing files containing layouts" => Some(("view", "ss-select-drawing-files-containing-layouts")), + "Browse for Drawings..." => Some(("view", "ss-browse-for-drawings")), + "A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it." => Some(("view", "ss-a-layout-can-belong-to-only-one-sheet-set")), + "Prefix sheet titles with file name" => Some(("view", "ss-prefix-sheet-titles-with-file-name")), + "Import Checked" => Some(("view", "ss-import-checked")), + "Template" => Some(("view", "ss-template")), + "Drawing template file name" => Some(("view", "ss-drawing-template-file-name")), + "Select a layout to create new sheets" => Some(("view", "ss-select-a-layout-to-create-new-sheets")), + "Category name" => Some(("view", "ss-category-name")), + "Select the callout blocks to be used in this category" => Some(("view", "ss-select-the-callout-blocks")), + "Selected blocks will be available for selection when you insert a callout block from the view tab." => Some(("view", "ss-selected-blocks-will-be-available")), + "Add Blocks..." => Some(("view", "ss-add-blocks")), + "(The layout and the drawing file will not be deleted.)" => Some(("view", "ss-layout-and-drawing-not-deleted")), + "Are you sure that you want to remove {} Sheet from {} Sheet Set?" => Some(("view", "ss-remove-sheet-question")), + "The drawing is open; close it to rename its file." => Some(("view", "ss-drawing-open-cannot-rename")), + "The category name cannot be empty." => Some(("view", "ss-category-name-empty")), + "Associate hyperlink" => Some(("view", "ss-associate-hyperlink")), "Remove Sheet" => Some(("view", "ss-remove-sheet")), "Lets you specify one or more folders containing drawings. The layouts from these drawings can be automatically imported into the sheet set." => Some(("view", "ss-lets-you-specify-one-or-more-folders-containing-drawings-the")), "Creates a sheet set with no subsets or sheets." => Some(("view", "ss-creates-a-sheet-set-with-no-subsets-or-sheets")), diff --git a/src/ui/window/field_dialog.rs b/src/ui/window/field_dialog.rs index 6851c0612..3a5e8e966 100644 --- a/src/ui/window/field_dialog.rs +++ b/src/ui/window/field_dialog.rs @@ -90,6 +90,105 @@ fn sheet_set_code(name: &str, custom: &str) -> String { format!("\\AcSm{version} {target}") } +/// The navigation tree of a SheetSet (set, subsets, sheets) or SheetView +/// (set, view categories) field: depth, component id, label, is a sheet. +pub fn nav_nodes(db: &codec::sheet_set::SheetSetDatabase, views: bool) -> Vec<(u16, String, String, bool)> { + use codec::sheet_set::{number_and_title, ComponentKind, Element as SsElement}; + let set = db.sheet_set(); + let mut out = vec![(0, set.id().to_string(), db.name().to_string(), false)]; + if views { + for c in db.view_categories() { + out.push((1, c.id().to_string(), c.prop("Name").unwrap_or("").to_string(), false)); + } + return out; + } + fn go(el: &SsElement, depth: u16, out: &mut Vec<(u16, String, String, bool)>) { + for c in &el.children { + match ComponentKind::of(c) { + Some(ComponentKind::Subset) => { + out.push((depth, c.id().to_string(), c.prop("Name").unwrap_or("").to_string(), false)); + go(c, depth + 1, out); + } + Some(ComponentKind::Sheet) => out.push((depth, c.id().to_string(), number_and_title(c), true)), + _ => {} + } + } + } + go(set, 1, &mut out); + out +} + +/// The properties a navigation node offers: list label, code property, version. +pub fn nav_props(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Vec<(String, String, &'static str)> { + use codec::sheet_set::{custom_properties, ComponentKind, CUSTOM_SHEET_PROP, CUSTOM_SHEET_SET_PROP}; + let Some(el) = db.find(id) else { return Vec::new() }; + let fixed = |list: &[(&str, &str, &'static str)]| -> Vec<(String, String, &'static str)> { + list.iter().map(|(l, p, v)| (l.to_string(), p.to_string(), *v)).collect() + }; + let custom = |flag: i32| -> Vec<(String, String, &'static str)> { + custom_properties(db.sheet_set()).into_iter().filter(|(_, _, f)| f & flag != 0).map(|(n, _, _)| (n.clone(), n, "")).collect() + }; + match ComponentKind::of(el) { + Some(ComponentKind::SheetSet) => { + let mut v = fixed(&[ + ("SheetSetName", "Name", ""), + ("SheetSetDescription", "Description", ""), + ("ProjectNumber", "ProjectNumber", ".16.2"), + ("ProjectName", "ProjectName", ".16.2"), + ("ProjectPhase", "ProjectPhase", ".16.2"), + ("ProjectMilestone", "ProjectMilestone", ".16.2"), + ]); + v.extend(custom(CUSTOM_SHEET_SET_PROP)); + v + } + Some(ComponentKind::Subset) => fixed(&[("SheetSubsetName", "Name", "")]), + Some(ComponentKind::Sheet) => { + let mut v = fixed(&[ + ("SheetNumberAndTitle", "NumberAndTitle", ""), + ("SheetTitle", "Title", ""), + ("SheetNumber", "Number", ""), + ("SheetDescription", "Description", ""), + ("RevisionNumber", "RevisionNumber", ".16.2"), + ("RevisionDate", "RevisionDate", ".16.2"), + ("IssuePurpose", "IssuePurpose", ".16.2"), + ("Category", "Category", ".16.2"), + ]); + v.extend(custom(CUSTOM_SHEET_PROP)); + v + } + None => fixed(&[("ViewCategoryName", "Name", "")]), + } +} + +impl FieldDialogState { + /// `\AcSm Database("…").SheetSet("…")[.Component("…")].Property` of the + /// picked node, with the text case and, on a sheet, the hyperlink to it. + fn navigation_code(&self) -> String { + let Some(db) = self.ss_sets.get(self.ss_set) else { return "\\AcSm".into() }; + let Some((_, prop, version)) = nav_props(db, &self.ss_node).into_iter().find(|(l, _, _)| *l == self.ss_prop) else { + return "\\AcSm".into(); + }; + let set = db.sheet_set().id().to_string(); + let path = codec::sheet_set::native_path(db.path.as_deref().unwrap_or("")); + let mut code = format!("\\AcSm{version} Database(\"{path}\").SheetSet(\"{set}\")"); + if self.ss_node != set { + code.push_str(&format!(".Component(\"{}\")", self.ss_node)); + } + code.push('.'); + code.push_str(&prop); + if self.text_case != 0 { + code.push_str(&format!(" \\f \"%tc{}\"", self.text_case)); + } + let sheet = db.find(&self.ss_node).and_then(codec::sheet_set::ComponentKind::of) == Some(codec::sheet_set::ComponentKind::Sheet); + if sheet && self.ss_href { + if let Some(r) = db.layout_reference(&self.ss_node, "Layout") { + code.push_str(&format!(" \\href \"{}#,{}##1\"", r.file_name, r.name)); + } + } + code + } +} + /// SheetSetPlaceholder types: list label, the `?` target, field version. pub const PLACEHOLDER_TYPES: &[(&str, &str, &str)] = &[ ("SheetNumberAndTitle", "?Sheet.NumberAndTitle", ""), @@ -232,6 +331,13 @@ pub struct FieldDialogState { pub ss_scale_format: usize, /// The open sheet set's name (SheetSet / SheetView fields). pub ss_set_name: String, + /// SheetSet / SheetView: the sets offered (open ones and any picked with + /// "..."), the one shown, the node and property picked, associate hyperlink. + pub ss_sets: Vec, + pub ss_set: usize, + pub ss_node: String, + pub ss_prop: String, + pub ss_href: bool, /// Today in each of DATE_FORMATS, for the Examples list. pub examples: Vec, } @@ -270,6 +376,11 @@ impl FieldDialogState { ss_placeholder: 0, ss_scale_format: 6, ss_set_name: String::new(), + ss_sets: Vec::new(), + ss_set: 0, + ss_node: String::new(), + ss_prop: String::new(), + ss_href: true, examples: Vec::new(), } } @@ -324,7 +435,7 @@ impl FieldDialogState { vec![], ), FieldKind::SheetSet | FieldKind::SheetSetCustom => (case(sheet_set_code(self.name, &self.ss_custom)), vec![]), - FieldKind::SheetSetNavigation => ("\\AcSm".into(), vec![]), + FieldKind::SheetSetNavigation => (self.navigation_code(), vec![]), FieldKind::SheetSetPlaceholder => { let (label, target, version) = PLACEHOLDER_TYPES[self.ss_placeholder.min(PLACEHOLDER_TYPES.len() - 1)]; let target = if label == "Custom" { format!("{target}{}", self.ss_custom) } else { target.to_string() }; @@ -454,6 +565,12 @@ pub enum FieldDialogMsg { SsCustom(String), SsPlaceholder(usize), SsScaleFormat(usize), + SsSet(usize), + SsNode(String), + SsProp(String), + SsHref(bool), + SsBrowse, + SsPicked(Option), Help, Ok, } @@ -814,14 +931,53 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { } col.into() } - FieldKind::SheetSetNavigation => column![ - text(t!("Sheet set")).size(12), - container(text(state.ss_set_name.clone()).size(12).style(muted_style)).padding([5, 8]).width(Fill), - text(t!("Sheet navigation tree")).size(12), - container(Space::new()).height(Length::Fixed(120.0)), - ] - .spacing(8) - .into(), + FieldKind::SheetSetNavigation => { + let picks: Vec = state.ss_sets.iter().enumerate().map(|(i, d)| Pick(i, d.name().to_string())).collect(); + let current = picks.get(state.ss_set).cloned(); + let set_row = row![ + pick_list(current, picks, |p: &Pick| p.1.clone()) + .on_select(|p: Pick| msg(FieldDialogMsg::SsSet(p.0))) + .text_size(12) + .padding([5, 8]) + .width(Fill), + button(text("...").size(12)).on_press(msg(FieldDialogMsg::SsBrowse)).style(button_style(false)).padding([5, 10]), + ] + .spacing(6); + let db = state.ss_sets.get(state.ss_set); + let nodes = db.map(|d| nav_nodes(d, state.name == "SheetView")).unwrap_or_default(); + let tree = list( + nodes + .iter() + .map(|(depth, id, label, _)| { + (format!("{}{label}", " ".repeat(usize::from(*depth))), *id == state.ss_node, FieldDialogMsg::SsNode(id.clone())) + }) + .collect(), + 118.0, + ); + let props = db.map(|d| nav_props(d, &state.ss_node)).unwrap_or_default(); + let prop_list = list( + props.iter().map(|(l, _, _)| (l.clone(), *l == state.ss_prop, FieldDialogMsg::SsProp(l.clone()))).collect(), + 92.0, + ); + let sheet = nodes.iter().any(|(_, id, _, sheet)| *sheet && *id == state.ss_node); + let mut href = checkbox(state.ss_href && sheet).label(t!("Associate hyperlink").into_owned()).text_size(12).size(14); + if sheet { + href = href.on_toggle(|v| msg(FieldDialogMsg::SsHref(v))); + } + column![ + text(t!("Sheet set")).size(12), + set_row, + text(t!("Sheet navigation tree")).size(12), + tree, + text(t!("Property")).size(12), + prop_list, + text(t!("Format")).size(12), + case_list(state), + href, + ] + .spacing(8) + .into() + } FieldKind::Object => column![ row![ text(t!("Object type")).size(12).width(Length::Fixed(96.0)), @@ -918,6 +1074,7 @@ pub fn view<'a>( .spacing(10), FieldKind::Hyperlink => column![card(t!("Hyperlink").into_owned(), format_panel(state)), preview].spacing(10), FieldKind::BlockPlaceholder => column![card(t!("Block placeholder").into_owned(), format_panel(state)), preview].spacing(10), + FieldKind::SheetSetNavigation => column![card(t!("Sheet set").into_owned(), format_panel(state)), preview].spacing(10), _ => column![card(t!("Format").into_owned(), format_panel(state)), preview].spacing(10), }; let (code, _) = state.code(); diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index a1c54c7dd..e4f0d3170 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -65,6 +65,20 @@ pub struct SheetSetManager { pub dialog: Option, /// Last seen modification time of each open `.dst` (by path key). pub seen: std::collections::HashMap, + /// Sheet status by sheet id (SSMSHEETSTATUS) and when it was taken. + pub status: std::collections::HashMap, + pub status_at: Option, + /// Sheet Views: by category (else by sheet). + pub by_category: bool, + /// Model Views: location id, folder and its drawings. + pub locations: Vec<(String, String, Vec)>, +} + +/// A sheet whose drawing is missing, or open (a `.dwl` lock file, or a tab here). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SheetStatus { + Missing, + Locked, } impl SheetSetManager { @@ -103,6 +117,24 @@ pub enum SheetSetMsg { Refresh, /// Once a second: reload sets whose `.dst` changed on disk. Poll, + /// Import: Browse for Drawings and the drawings picked. + BrowseDrawings, + ImportAdd(Vec), + /// Rename & Renumber: apply and go to the previous / next sheet. + Previous, + Next, + /// Sheet Views: by category or by sheet. + ViewsByCategory(bool), + NewCategory, + CategoryProperties(String), + CategoryRemove(String), + AddBlocks, + BlocksPicked(Option), + /// Model Views. + AddLocation, + LocationPicked(Option), + RemoveLocation(String), + OpenDrawing(String), /// A `.dst` picked by Open (or given by automation). OpenPicked(Option), /// A drawing picked by Import Layout as Sheet. @@ -161,6 +193,15 @@ pub enum FieldId { AddFolder, /// Prompt for template (Properties). PromptTemplate, + OpenAfter, + Publish, + LayoutName, + /// Rename option 0..4. + RenameOption(usize), + ImportRow(usize), + ImportPrefix, + /// View Category: a callout block's check box. + CategoryBlock(usize), } /// What a Properties row edits. @@ -175,6 +216,8 @@ pub enum RowKey { Template, /// Yes / No: prompt for template. PromptTemplate, + /// Publish Sheets in Subset (`OverrideSheetPublish`). + Publish, /// Shown only. ReadOnly, } @@ -236,15 +279,27 @@ pub struct Wizard { } /// New Sheet, New Subset, Rename & Renumber and Import Layout as Sheet. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum FormKind { + #[default] NewSheet, NewSubset, Rename, ImportLayout, } +/// One layout of Import Layouts as Sheets. #[derive(Debug, Clone)] +pub struct ImportRow { + pub drawing: String, + pub layout: String, + pub handle: String, + /// The sheet set the layout already belongs to (cannot be imported). + pub owner: Option, + pub on: bool, +} + +#[derive(Debug, Clone, Default)] pub struct Form { pub kind: FormKind, pub set: usize, @@ -254,10 +309,42 @@ pub struct Form { pub title: String, pub file_name: String, pub folder: String, - /// Import: the drawing and its layouts (name, handle). + /// The sheet template picked when the subset prompts for one (file, layout). pub drawing: String, - pub layouts: Vec<(String, String)>, + pub template: Option<(String, String)>, + /// New Sheet: open the new sheet afterwards. + pub open_after: bool, + /// New Subset: folder hierarchy; publish sheets by their own setting. + pub hierarchy: bool, + pub publish: bool, + /// Rename & Renumber: layout name and the four rename options + /// (layout = title, layout prefix, file = title, file prefix). + pub layout_name: String, + pub rename: [bool; 4], + /// Import: the layouts found and "prefix sheet titles with file name". + pub rows: Vec, + pub prefix: bool, + pub error: Option, +} + +/// Select Layout as Sheet Template (a subset that prompts for its template). +#[derive(Debug, Clone)] +pub struct TemplatePick { + pub form: Form, + pub file: String, + pub layouts: Vec, pub layout: usize, +} + +/// The View Category dialog. +#[derive(Debug, Clone)] +pub struct Category { + pub set: usize, + /// `None` creates a category. + pub id: Option, + pub name: String, + /// Callout block id, label, used. + pub blocks: Vec<(String, String, bool)>, pub error: Option, } @@ -266,6 +353,10 @@ pub enum SsDialog { Wizard(Wizard), Properties(Properties), Form(Form), + /// Remove Sheets confirmation: set, sheet id, question. + Confirm(usize, String, String), + Template(TemplatePick), + Category(Category), } impl SsDialog { @@ -277,8 +368,11 @@ impl SsDialog { FormKind::NewSheet => t!("New Sheet").into_owned(), FormKind::NewSubset => t!("Subset Properties").into_owned(), FormKind::Rename => t!("Rename & Renumber Sheet").into_owned(), - FormKind::ImportLayout => t!("Import Layout as Sheet").into_owned(), + FormKind::ImportLayout => t!("Import Layouts as Sheets").into_owned(), }, + SsDialog::Confirm(..) => t!("Remove Sheets").into_owned(), + SsDialog::Template(_) => t!("Select Layout as Sheet Template").into_owned(), + SsDialog::Category(_) => t!("View Category").into_owned(), } } } @@ -369,6 +463,63 @@ fn row_menu(id: String, kind: ComponentKind, empty: bool) -> Element<'static, Me .into() } +/// A small status badge after a sheet name. +fn badge<'a>(prefix: &str, label: &str, warn: bool) -> Element<'a, Message> { + container(text(format!("{prefix}{}", t!(label))).size(10)) + .padding([0, 5]) + .style(move |theme: &Theme| { + let p = theme.palette(); + let c = if warn { p.warning.base } else { p.secondary.base }; + container::Style { + background: Some(Background::Color(c.color)), + text_color: Some(c.text), + border: Border { radius: 7.0.into(), ..Default::default() }, + ..Default::default() + } + }) + .into() +} + +/// A tree row of the Sheet Views / Model Views pages with its own menu. +fn plain_row<'a>( + depth: u16, + icon: &'static [u8], + label: String, + double: Option, + menu: Vec<(&'static str, Option)>, +) -> Element<'a, Message> { + let cells = row![ + Space::new().width(Length::Fixed(f32::from(depth) * 16.0 + 14.0)), + crate::ui::icons::semantic(icon, 14.0), + text(label).size(12).width(Fill), + ] + .spacing(5) + .align_y(iced::Center); + let mut area = mouse_area(container(cells).width(Fill).padding([3, 6])); + if let Some(m) = double { + area = area.on_double_click(m); + } + if menu.is_empty() { + return area.into(); + } + iced_aw::ContextMenu::new(area, move || { + let items: Vec> = menu + .iter() + .map(|(label, m)| if label.is_empty() { menu_separator() } else { menu_entry(t!(*label).into_owned(), m.clone()) }) + .collect(); + container(iced::widget::Column::with_children(items).spacing(1)) + .padding(4) + .width(Length::Fixed(220.0)) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().background.weak.color)), + border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() }, + ..Default::default() + }) + .into() + }) + .into() +} + fn tree_row<'a>( state: &SheetSetManager, el: &SsElement, @@ -392,14 +543,23 @@ fn tree_row<'a>( None => Space::new().width(14).into(), }; let selected = state.selected.as_deref() == Some(id.as_str()); - let cells = row![ + let status = state.status.get(&id).copied(); + let mut cells = row![ Space::new().width(Length::Fixed(f32::from(depth) * 16.0)), arrow, crate::ui::icons::semantic(icon, 14.0), - text(label).size(12).width(Fill), ] .spacing(5) .align_y(iced::Center); + let name = text(label).size(12); + cells = match status { + // A missing drawing: grey with a "? missing" badge; an open one: a lock badge. + Some(SheetStatus::Missing) => { + cells.push(name.style(muted_style)).push(badge("? ", "missing", true)).push(Space::new().width(Fill)) + } + Some(SheetStatus::Locked) => cells.push(name).push(badge("", "open", false)).push(Space::new().width(Fill)), + None => cells.push(name.width(Fill)), + }; let area = mouse_area(container(cells).width(Fill).padding([3, 6]).style(move |theme: &Theme| container::Style { background: selected.then(|| Background::Color(theme.palette().primary.weak.color)), text_color: selected.then(|| theme.palette().primary.weak.text), @@ -434,31 +594,102 @@ fn push_tree<'a>( } } -/// Sheet views of the set: each sheet's `AcSmSheetView` children. -fn sheet_views(db: &SheetSetDatabase) -> Vec { - db.sheets() +/// A sheet's views (`AcSmSheetView`): label and the category it references. +fn views_of(sheet: &SsElement) -> Vec<(String, String)> { + sheet + .named("SheetViews") + .map(|v| v.children.iter().filter(|c| c.name == "AcSmSheetView").collect::>()) + .unwrap_or_default() .into_iter() - .flat_map(|sheet| { - sheet - .named("SheetViews") - .map(|v| v.children.iter().filter(|c| c.name == "AcSmSheetView").collect::>()) - .unwrap_or_default() - .into_iter() - .map(|v| { - let number = v.prop("Number").unwrap_or(""); - let title = v.prop("Title").unwrap_or(""); - if number.is_empty() { title.to_string() } else { format!("{number} - {title}") } - }) + .map(|v| { + let number = v.prop("Number").unwrap_or(""); + let title = v.prop("Title").unwrap_or(""); + let label = if number.is_empty() { title.to_string() } else { format!("{number} - {title}") }; + let category = v.named("Category").and_then(|c| c.prop("ReferencedObject")).unwrap_or("").to_string(); + (label, category) }) .collect() } -/// Model view locations (`Resources`) of the set. -fn model_views(db: &SheetSetDatabase) -> Vec { - db.sheet_set() - .named("Resources") - .map(|r| r.children.iter().map(|c| db.resolve_file(c)).collect()) - .unwrap_or_default() +/// The Sheet Views page: the set, then its view categories (or sheets) and their views. +fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec> { + let mut rows = vec![plain_row( + 0, + SET_ICON, + db.name().to_string(), + None, + vec![("New View Category...", Some(msg(SheetSetMsg::NewCategory)))], + )]; + if state.by_category { + let views: Vec<(String, String)> = db.sheets().into_iter().flat_map(views_of).collect(); + for c in db.view_categories() { + let id = c.id().to_string(); + rows.push(plain_row( + 1, + crate::ui::icons::FOLDER_OPEN, + c.prop("Name").unwrap_or("").to_string(), + Some(msg(SheetSetMsg::CategoryProperties(id.clone()))), + vec![ + ("New View Category...", Some(msg(SheetSetMsg::NewCategory))), + ("Properties...", Some(msg(SheetSetMsg::CategoryProperties(id.clone())))), + ("Remove", Some(msg(SheetSetMsg::CategoryRemove(id.clone())))), + ], + )); + for (label, _) in views.iter().filter(|(_, cat)| *cat == id) { + rows.push(plain_row(2, crate::ui::icons::DOC, label.clone(), None, vec![])); + } + } + } else { + for sheet in db.sheets() { + let views = views_of(sheet); + if views.is_empty() { + continue; + } + rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, vec![])); + for (label, _) in views { + rows.push(plain_row(2, crate::ui::icons::DOC, label, None, vec![])); + } + } + } + rows +} + +/// The Model Views page: each location folder and its drawings. +fn model_views_rows<'a>(state: &SheetSetManager) -> Vec> { + let mut rows = Vec::new(); + // A location shows relative to the set's folder when it can. + let base = state.db().and_then(|db| db.path.as_deref()).and_then(|p| std::path::Path::new(p).parent().map(|p| p.to_path_buf())); + for (id, folder, files) in &state.locations { + let label = base.as_deref().and_then(|b| ss::relative_path(b, std::path::Path::new(folder))).unwrap_or_else(|| folder.clone()); + rows.push(plain_row( + 0, + crate::ui::icons::FOLDER_OPEN, + label, + None, + vec![ + ("Add New Location...", Some(msg(SheetSetMsg::AddLocation))), + ("Remove Location", Some(msg(SheetSetMsg::RemoveLocation(id.clone())))), + ], + )); + for file in files { + let path = std::path::Path::new(folder).join(file).to_string_lossy().to_string(); + rows.push(plain_row( + 1, + crate::ui::icons::DOC, + file.clone(), + Some(msg(SheetSetMsg::OpenDrawing(path.clone()))), + vec![("Open", Some(msg(SheetSetMsg::OpenDrawing(path))))], + )); + } + } + rows.push( + button(text(format!("+ {}", t!("Add New Location..."))).size(12).style(muted_style)) + .on_press(msg(SheetSetMsg::AddLocation)) + .style(button::text) + .padding([3, 6]) + .into(), + ); + rows } fn heading(label: &str) -> Element<'static, Message> { @@ -522,24 +753,38 @@ pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> .into() } (Some(db), SsmTab::SheetViews) => { - let views = sheet_views(db); - let list: Element<'a, Message> = if views.is_empty() { - text(t!("No sheet views.").into_owned()).size(12).style(muted_style).into() - } else { - iced::widget::Column::with_children(views.into_iter().map(|v| text(v).size(12).into())).spacing(3).into() - }; - column![heading("Views by sheet"), list].spacing(6).into() + let by = state.by_category; + let header = row![ + heading(if by { "View by category" } else { "View by sheet" }), + button(text(format!("{} ⇄", t!(if by { "View by sheet" } else { "View by category" }))).size(11)) + .on_press(msg(SheetSetMsg::ViewsByCategory(!by))) + .style(button::subtle) + .padding([2, 4]), + button(crate::ui::icons::semantic(crate::ui::icons::PLUS, 12.0)) + .on_press(msg(SheetSetMsg::NewCategory)) + .style(button::subtle) + .padding([2, 4]), + ] + .spacing(4) + .align_y(iced::Center); + column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db)).spacing(1)).height(Fill)] + .spacing(6) + .into() } - (Some(db), SsmTab::ModelViews) => { - let locations = model_views(db); - let list: Element<'a, Message> = if locations.is_empty() { - text(t!("No locations.").into_owned()).size(12).style(muted_style).into() - } else { - iced::widget::Column::with_children(locations.into_iter().map(|v| text(v).size(12).into())) - .spacing(3) - .into() - }; - column![heading("Locations"), list].spacing(6).into() + (Some(_), SsmTab::ModelViews) => { + let header = row![ + heading("Locations"), + button(text("↻").size(13)).on_press(msg(SheetSetMsg::Refresh)).style(button::subtle).padding([2, 4]), + button(crate::ui::icons::semantic(crate::ui::icons::PLUS, 12.0)) + .on_press(msg(SheetSetMsg::AddLocation)) + .style(button::subtle) + .padding([2, 4]), + ] + .spacing(4) + .align_y(iced::Center); + column![header, scrollable(iced::widget::Column::with_children(model_views_rows(state)).spacing(1)).height(Fill)] + .spacing(6) + .into() } }; let card = container(body).padding(6).width(Fill).height(Fill).style(|theme: &Theme| container::Style { @@ -813,6 +1058,12 @@ fn properties_view<'a>(p: &'a Properties) -> Element<'a, Message> { .width(Fill) .into() } + RowKey::Publish => yes_no( + r.value != "Do Not Publish Sheets", + FieldId::Row(i), + "Publish by Sheet 'Include for Publish' Setting", + "Do Not Publish Sheets", + ), RowKey::Prop(_) => input(&r.value, FieldId::Row(i)), }; rows.push(labeled(r.label, control)); @@ -906,71 +1157,264 @@ fn properties_view<'a>(p: &'a Properties) -> Element<'a, Message> { .into() } -fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { - let body: Element<'a, Message> = match f.kind { - FormKind::NewSheet => column![ - labeled("Number", input(&f.number, FieldId::Number)), - labeled("Sheet title", input(&f.title, FieldId::Title)), - labeled("File name", input(&f.file_name, FieldId::FileName)), - labeled("Folder path", container(text(f.folder.clone()).size(12).style(muted_style)).padding([5, 8])), - labeled("Sheet template", container(text(f.drawing.clone()).size(12).style(muted_style)).padding([5, 8])), - ] - .spacing(8) - .into(), - FormKind::NewSubset => column![ - labeled("Subset name", input(&f.title, FieldId::Title)), - labeled( - "New sheet location", - row![input(&f.folder, FieldId::Folder), browse(SheetSetMsg::Browse(FieldId::Folder))].spacing(6) - ), - ] - .spacing(8) - .into(), - FormKind::Rename => column![ - labeled("Number", input(&f.number, FieldId::Number)), - labeled("Sheet title", input(&f.title, FieldId::Title)), - ] - .spacing(8) - .into(), - FormKind::ImportLayout => { - let rows = f.layouts.iter().enumerate().map(|(i, (name, _))| { - button(text(name.clone()).size(12)) - .on_press(msg(SheetSetMsg::Choose(FieldId::Layout, i))) - .style(button_style(f.layout == i)) - .padding([3, 8]) - .width(Fill) - .into() - }); - column![ - labeled("Drawing", container(text(f.drawing.clone()).size(12).style(muted_style)).padding([5, 8])), - text(t!("Layouts").into_owned()).size(12), - container(scrollable(iced::widget::Column::with_children(rows.collect::>()).spacing(1))) - .height(Length::Fixed(160.0)), - ] - .spacing(8) - .into() - } - }; - let mut col = column![card( - match f.kind { - FormKind::NewSheet => "Sheet", - FormKind::NewSubset => "Subset", - FormKind::Rename => "Sheet", - FormKind::ImportLayout => "Import Layout as Sheet", - }, - body - )] - .spacing(10); - if let Some(e) = error_band(&f.error) { - col = col.push(e); +fn check<'a>(on: bool, label: &str, field: FieldId, enabled: bool) -> Element<'a, Message> { + let c = checkbox(on).label(t!(label).into_owned()).text_size(12).size(14); + if enabled { + c.on_toggle(move |v| msg(SheetSetMsg::Toggle(field, v))).into() + } else { + c.into() } - col.push(footer( +} + +fn read_only<'a>(value: &str) -> Element<'a, Message> { + container(text(value.to_string()).size(12).style(muted_style)).padding([5, 8]).width(Fill).into() +} + +fn ok_cancel<'a>(ok: &str) -> Element<'a, Message> { + footer( vec![], vec![ dialog_button(t!("Cancel"), msg(SheetSetMsg::Cancel), false).into(), - dialog_button(t!("OK"), msg(SheetSetMsg::Ok), true).into(), + dialog_button(t!(ok), msg(SheetSetMsg::Ok), true).into(), ], - )) + ) +} + +fn yes_no<'a>(yes: bool, field: FieldId, a: &str, b: &str) -> Element<'a, Message> { + let opts = vec![t!(a).into_owned(), t!(b).into_owned()]; + let first = opts[0].clone(); + pick_list(Some(opts[if yes { 0 } else { 1 }].clone()), opts, |s: &String| s.clone()) + .on_select(move |s: String| msg(SheetSetMsg::Toggle(field, s == first))) + .text_size(12) + .padding([5, 8]) + .width(Fill) + .into() +} + +fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { + let number_title = row![ + text(t!("Number").into_owned()).size(12).width(Length::Fixed(96.0)), + container(input(&f.number, FieldId::Number)).width(Length::Fixed(110.0)), + text(t!("Sheet title").into_owned()).size(12), + input(&f.title, FieldId::Title), + ] + .spacing(8) + .align_y(iced::Center); + let short = |label: &str, control: Element<'a, Message>| -> Element<'a, Message> { + row![text(t!(label).into_owned()).size(12).width(Length::Fixed(96.0)), control].spacing(8).align_y(iced::Center).into() + }; + let cards: Vec> = match f.kind { + FormKind::NewSheet => vec![card( + "Sheet", + column![ + number_title, + short("File name", input(&f.file_name, FieldId::FileName)), + short("Folder path", read_only(&f.folder)), + short("Sheet template", read_only(&f.drawing)), + row![Space::new().width(Length::Fixed(104.0)), check(f.open_after, "Open in drawing editor", FieldId::OpenAfter, true)], + ] + .spacing(8), + )], + FormKind::NewSubset => vec![card( + "Subset", + column![ + labeled("Subset name", input(&f.title, FieldId::Title)), + labeled("Create folder hierarchy", yes_no(f.hierarchy, FieldId::Hierarchy, "Yes", "No")), + labeled( + "Publish sheets in subset", + yes_no(f.publish, FieldId::Publish, "Publish by Sheet 'Include for Publish' Setting", "Do Not Publish Sheets") + ), + labeled( + "New sheet location", + if f.hierarchy { + read_only(&f.folder) + } else { + row![input(&f.folder, FieldId::Folder), browse(SheetSetMsg::Browse(FieldId::Folder))].spacing(6).into() + } + ), + ] + .spacing(8), + )], + FormKind::Rename => { + let layout: Element<'a, Message> = if f.rename[0] { read_only(&f.layout_name) } else { input(&f.layout_name, FieldId::LayoutName) }; + let file: Element<'a, Message> = if f.rename[2] { read_only(&f.file_name) } else { input(&f.file_name, FieldId::FileName) }; + vec![ + card( + "Sheet", + column![number_title, short("Layout name", layout), short("File name", file), short("Folder path", read_only(&f.folder))] + .spacing(8), + ), + card( + "Rename options", + row![ + column![ + text(t!("Rename layout to match:").into_owned()).size(12), + check(f.rename[0], "Sheet title", FieldId::RenameOption(0), true), + row![Space::new().width(22), check(f.rename[1], "Prefix with sheet number", FieldId::RenameOption(1), f.rename[0])], + ] + .spacing(6) + .width(Fill), + column![ + text(t!("Rename drawing file to match:").into_owned()).size(12), + check(f.rename[2], "Sheet title", FieldId::RenameOption(2), true), + row![Space::new().width(22), check(f.rename[3], "Prefix with sheet number", FieldId::RenameOption(3), f.rename[2])], + ] + .spacing(6) + .width(Fill), + ] + .spacing(16), + ), + ] + } + FormKind::ImportLayout => { + let head = row![ + Space::new().width(22), + text(t!("Layout name").into_owned()).size(11).width(Fill), + text(t!("Drawing").into_owned()).size(11).width(Fill), + text(t!("Status").into_owned()).size(11).width(Fill), + ] + .spacing(6); + let mut list = vec![head.into()]; + for (k, r) in f.rows.iter().enumerate() { + let file = std::path::Path::new(&r.drawing).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); + let status: Element<'a, Message> = match &r.owner { + None => text(t!("Available for import").into_owned()).size(12).style(|t: &Theme| text::Style { color: Some(t.palette().success.base.color) }).into(), + Some(owner) => text(crate::tf!("Belongs to sheet set {}", owner).into_owned()).size(12).style(|t: &Theme| text::Style { color: Some(t.palette().warning.base.color) }).into(), + }; + let enabled = r.owner.is_none(); + let cb = checkbox(r.on).size(14); + let cb: Element<'a, Message> = if enabled { cb.on_toggle(move |v| msg(SheetSetMsg::Toggle(FieldId::ImportRow(k), v))).into() } else { cb.into() }; + let name = text(r.layout.clone()).size(12).width(Fill); + let file = text(file).size(12).width(Fill); + list.push( + row![cb, if enabled { name } else { name.style(muted_style) }, if enabled { file } else { file.style(muted_style) }, container(status).width(Fill)] + .spacing(6) + .align_y(iced::Center) + .into(), + ); + } + vec![card( + "Drawings", + column![ + row![ + text(t!("Select drawing files containing layouts").into_owned()).size(12).width(Fill), + button(text(t!("Browse for Drawings...").into_owned()).size(12)) + .on_press(msg(SheetSetMsg::BrowseDrawings)) + .style(button_style(false)) + .padding([5, 12]), + ] + .align_y(iced::Center), + text(t!("A layout can belong to only one sheet set. If a layout already belongs to a sheet set, you must create a copy of the layout to import it.").into_owned()) + .size(11) + .style(muted_style), + container(scrollable(iced::widget::Column::with_children(list).spacing(4))) + .padding(6) + .height(Length::Fixed(150.0)) + .style(card_style), + check(f.prefix, "Prefix sheet titles with file name", FieldId::ImportPrefix, true), + ] + .spacing(8), + )] + } + }; + let mut col = iced::widget::Column::with_children(cards).spacing(10); + if let Some(e) = error_band(&f.error) { + col = col.push(e); + } + let foot = match f.kind { + FormKind::Rename => footer( + vec![ + button(text(format!("‹ {}", t!("Previous"))).size(12)).on_press(msg(SheetSetMsg::Previous)).style(button_style(false)).padding([5, 12]).into(), + button(text(format!("{} ›", t!("Next"))).size(12)).on_press(msg(SheetSetMsg::Next)).style(button_style(false)).padding([5, 12]).into(), + ], + vec![ + dialog_button(t!("Cancel"), msg(SheetSetMsg::Cancel), false).into(), + dialog_button(t!("OK"), msg(SheetSetMsg::Ok), true).into(), + ], + ), + FormKind::ImportLayout => ok_cancel("Import Checked"), + _ => ok_cancel("OK"), + }; + col.push(foot).into() +} + +fn template_view<'a>(t: &'a TemplatePick) -> Element<'a, Message> { + let rows = t.layouts.iter().enumerate().map(|(i, name)| { + button(text(name.clone()).size(12)) + .on_press(msg(SheetSetMsg::Choose(FieldId::Layout, i))) + .style(button_style(t.layout == i)) + .padding([3, 8]) + .width(Fill) + .into() + }); + column![ + card( + "Template", + column![ + text(t!("Drawing template file name").into_owned()).size(12), + row![read_only(&t.file), browse(SheetSetMsg::BrowseTemplate)].spacing(6).align_y(iced::Center), + text(t!("Select a layout to create new sheets").into_owned()).size(12), + container(scrollable(iced::widget::Column::with_children(rows.collect::>()).spacing(1))) + .padding(4) + .height(Length::Fixed(130.0)) + .style(card_style), + ] + .spacing(8) + ), + ok_cancel("OK"), + ] + .spacing(10) + .into() +} + +fn category_view<'a>(c: &'a Category) -> Element<'a, Message> { + let blocks = c.blocks.iter().enumerate().map(|(i, (_, label, on))| check(*on, label, FieldId::CategoryBlock(i), true)); + let mut col = column![card( + "Category", + column![ + labeled("Category name", input(&c.name, FieldId::Name)), + text(t!("Select the callout blocks to be used in this category").into_owned()).size(12), + container(scrollable(iced::widget::Column::with_children(blocks.collect::>()).spacing(4))) + .padding(6) + .height(Length::Fixed(90.0)) + .width(Fill) + .style(card_style), + text(t!("Selected blocks will be available for selection when you insert a callout block from the view tab.").into_owned()) + .size(11) + .style(muted_style), + button(text(t!("Add Blocks...").into_owned()).size(12)) + .on_press(msg(SheetSetMsg::AddBlocks)) + .style(button_style(false)) + .padding([5, 12]), + ] + .spacing(8) + )] + .spacing(10); + if let Some(e) = error_band(&c.error) { + col = col.push(e); + } + col.push(ok_cancel("OK")).into() +} + +fn confirm_view<'a>(question: &'a str) -> Element<'a, Message> { + column![ + row![ + text("?").size(22).style(accent_text), + column![ + text(question.to_string()).size(12), + text(t!("(The layout and the drawing file will not be deleted.)").into_owned()).size(12).style(muted_style), + ] + .spacing(4), + ] + .spacing(14), + row![ + Space::new().width(Fill), + dialog_button(t!("Cancel"), msg(SheetSetMsg::Cancel), false), + dialog_button(t!("Remove"), msg(SheetSetMsg::Ok), true), + ] + .spacing(6), + ] + .spacing(14) .into() } @@ -979,6 +1423,9 @@ pub fn dialog_view<'a>(state: &'a SheetSetManager, sizing: crate::ui::modal::Mod Some(SsDialog::Wizard(w)) => wizard_view(w), Some(SsDialog::Properties(p)) => properties_view(p), Some(SsDialog::Form(f)) => form_view(f), + Some(SsDialog::Confirm(_, _, q)) => confirm_view(q), + Some(SsDialog::Template(t)) => template_view(t), + Some(SsDialog::Category(c)) => category_view(c), None => Space::new().into(), }; container(body).padding([10, 12]).width(sizing.width).into() From 5c9f19a4dce1a9086a2b5af7e3293f76a0325da3 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:08:37 +0300 Subject: [PATCH 05/17] Sheet sets: import statuses, view navigation fields, drawing lock files Import Layouts as Sheets shows the reference's statuses (already part of a sheet set / may belong to another sheet set / available) and Browse replaces the list; the Field dialog SheetView tree lists sheet views with their properties, scale formats and hyperlinks; drawings open in tabs get .dwl/.dwl2 lock files that other instances read as open; the codec pin moves to the revision with sheet views and the default output folder. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 3 +- locales/bg-BG/opencadstudio.ftl | 3 +- locales/cs-CZ/opencadstudio.ftl | 3 +- locales/de-DE/opencadstudio.ftl | 3 +- locales/el-GR/opencadstudio.ftl | 3 +- locales/en-US/opencadstudio.ftl | 3 +- locales/es-ES/opencadstudio.ftl | 3 +- locales/fi-FI/opencadstudio.ftl | 3 +- locales/fr-FR/opencadstudio.ftl | 3 +- locales/hi-IN/opencadstudio.ftl | 3 +- locales/hu-HU/opencadstudio.ftl | 3 +- locales/it-IT/opencadstudio.ftl | 3 +- locales/ja-JP/opencadstudio.ftl | 3 +- locales/ko-KR/opencadstudio.ftl | 3 +- locales/nl-NL/opencadstudio.ftl | 3 +- locales/pl-PL/opencadstudio.ftl | 3 +- locales/pt-BR/opencadstudio.ftl | 3 +- locales/ru-RU/opencadstudio.ftl | 3 +- locales/tr-TR/opencadstudio.ftl | 3 +- locales/zh-CN/opencadstudio.ftl | 3 +- locales/zh-TW/opencadstudio.ftl | 3 +- src/app/commands/sheet_set.rs | 107 +++++++++++++++++++++++++++----- src/app/update/file.rs | 4 ++ src/app/view/mod.rs | 10 ++- src/locale_catalog.rs | 3 +- src/ui/window/field_dialog.rs | 82 +++++++++++++++++++++--- src/ui/window/sheet_set.rs | 25 ++++++-- 29 files changed, 236 insertions(+), 62 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c8b0f296..f8e3c0368 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=d86f57d#d86f57d5d179bc57fe7826d70454bece40c20320" +source = "git+https://github.com/ramox81/cadcodec.git?rev=29aff7e#29aff7e8e6f4669cd1f41fc82d39521127c07d03" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index fdb912af0..dfc938973 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "d86f57d" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "29aff7e" } [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 f53bed195..4c5bdb59e 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4407,6 +4407,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4427,7 +4429,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index e2e9bbfa8..e26e17328 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 72691ae7f..4d93bc6e8 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4392,6 +4392,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4412,7 +4414,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index f7bc3e6df..420a23a23 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4393,6 +4393,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4413,7 +4415,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 915802762..96bd2fd4b 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 8e2758d61..6d902d025 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4396,6 +4396,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4416,7 +4418,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index f73d3dfbd..ac7d25f50 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index d1165dea6..d40962493 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 2aa271973..e2145f0f2 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 024cb6214..d8bb65f77 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4383,6 +4383,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4403,7 +4405,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 7e3230eec..4cd1b0160 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 9bea307db..12e30fc29 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 8e551ef06..d071f58ba 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4383,6 +4383,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4403,7 +4405,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 62ed4f766..f3eb7cef7 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 1cb7147ef..93e5149a2 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4393,6 +4393,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4413,7 +4415,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 834d3a027..1839c8d4f 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4414,7 +4416,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 454ca237e..72b4d61f5 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4415,7 +4417,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index b6ba273b7..e983502d3 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4396,6 +4396,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4416,7 +4418,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index c5264c1f7..54e4faf27 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4355,6 +4355,8 @@ view = .ss-new-sheet-location = Yeni sayfa konumu .ss-remove-subset = Alt Kümeyi Kaldır .ss-rename-subset = Alt Kümeyi Yeniden Adlandır... + .ss-layout-already-part = Bu düzen zaten bir sayfa kümesinde - içe aktarılamaz. + .ss-layout-may-belong = Uyarı: bu düzen başka bir sayfa kümesine ait olabilir. .ss-status-missing = yok .ss-status-open = açık .ss-view-by-category = Kategoriye göre @@ -4375,7 +4377,6 @@ view = .ss-prefix-with-sheet-number = Önüne sayfa numarası .ss-rename-drawing-file-to-match = Çizim dosyasını eşleşecek şekilde adlandır: .ss-available-for-import = Hazır - .ss-belongs-to-sheet-set = Başka kümede: __ocs_fmt_0__ .ss-drawings = Çizimler .ss-select-drawing-files-containing-layouts = Düzen içeren çizim dosyalarını seçin .ss-browse-for-drawings = Çizimlere Gözat… diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 508d87136..2c28c8be9 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4378,6 +4378,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4398,7 +4400,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 497b58348..8d879e2db 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4390,6 +4390,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-layout-already-part = This layout is already part of a sheet set - not available for import. + .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing .ss-status-open = open .ss-view-by-category = View by category @@ -4410,7 +4412,6 @@ view = .ss-prefix-with-sheet-number = Prefix with sheet number .ss-rename-drawing-file-to-match = Rename drawing file to match: .ss-available-for-import = Available for import - .ss-belongs-to-sheet-set = Belongs to sheet set __ocs_fmt_0__ .ss-drawings = Drawings .ss-select-drawing-files-containing-layouts = Select drawing files containing layouts .ss-browse-for-drawings = Browse for Drawings... diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index da1036de1..9b236444a 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -9,6 +9,7 @@ use crate::ui::window::sheet_set::{ }; use codec::sheet_set::{self as ss, ComponentKind, LayoutReference, SheetSetData, SheetSetDatabase}; use iced::Task; +use std::collections::HashSet; use std::path::{Path, PathBuf}; /// The variables this family answers for. @@ -63,6 +64,55 @@ fn folder_of(path: &str) -> String { /// Template of a component for new sheets: its own `DefDwtLayout`, else /// the nearest parent's. +/// The lock files' date: the long date of the user's locale, two spaces and +/// the 24-hour time (`5 Ekim 2026 Pazartesi 09:26:39`). +#[cfg(windows)] +fn lock_stamp() -> String { + use windows_sys::Win32::Globalization::{GetDateFormatEx, GetTimeFormatEx, DATE_LONGDATE}; + let wide = |s: &str| s.encode_utf16().chain(std::iter::once(0)).collect::>(); + let mut date = [0u16; 128]; + let mut time = [0u16; 64]; + let format = wide("HH:mm:ss"); + // SAFETY: the buffers outlive the calls and their lengths are passed. + let (d, t) = unsafe { + ( + GetDateFormatEx(std::ptr::null(), DATE_LONGDATE, std::ptr::null(), std::ptr::null(), date.as_mut_ptr(), date.len() as i32, std::ptr::null()), + GetTimeFormatEx(std::ptr::null(), 0, std::ptr::null(), format.as_ptr(), time.as_mut_ptr(), time.len() as i32), + ) + }; + let text = |b: &[u16], n: i32| String::from_utf16_lossy(&b[..(n.max(1) as usize - 1)]); + format!("{} {}", text(&date, d), text(&time, t)) +} + +#[cfg(not(windows))] +fn lock_stamp() -> String { + String::new() +} + +/// `.dwl` (user, machine, date lines) and `.dwl2` (the same +/// as XML), as the reference writes them for a drawing it has open. +// ponytail: .dwl is written as UTF-8; the reference's encoding of non-ASCII +// day names was not measured. +#[cfg(not(target_arch = "wasm32"))] +fn write_lock(path: &Path) -> std::io::Result<()> { + let user = std::env::var("USERNAME").or_else(|_| std::env::var("USER")).unwrap_or_default(); + let machine = std::env::var("COMPUTERNAME").unwrap_or_default(); + let stamp = lock_stamp(); + std::fs::write(path.with_extension("dwl"), format!("{user}\n{machine} \n{stamp}"))?; + std::fs::write( + path.with_extension("dwl2"), + format!( + "\n\n{user}\n{machine} \n\n{stamp}\n" + ), + ) +} + +#[cfg(not(target_arch = "wasm32"))] +pub(in crate::app) fn release_lock(path: &Path) { + let _ = std::fs::remove_file(path.with_extension("dwl")); + let _ = std::fs::remove_file(path.with_extension("dwl2")); +} + /// Whether new sheets of `component` prompt for their template: its own /// "Prompt for template", else the nearest parent's. fn prompts_for_template(db: &SheetSetDatabase, component: &str) -> bool { @@ -92,16 +142,15 @@ fn import_rows(path: &Path, db: &SheetSetDatabase) -> Result, Str let mine = db.sheet_for(&drawing, Some(&layout)).is_some_and(|s| { s.named("Layout").and_then(|r| r.prop("Name")).is_some_and(|n| n.eq_ignore_ascii_case(&layout)) }); - let owner = if mine { - Some(db.name().to_string()) - } else { - link.as_ref().filter(|l| l.layout_name.eq_ignore_ascii_case(&layout) && !l.sheet_set_file_name.is_empty()).map(|l| { - SheetSetDatabase::read(&l.sheet_set_file_name) - .map(|d| d.name().to_string()) - .unwrap_or_else(|_| Path::new(&l.sheet_set_file_name).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default()) - }) - }; - ImportRow { drawing: drawing.clone(), layout, handle, on: owner.is_none(), owner } + // A layout of this set is taken; one the drawing's sheet link + // names for another set is only warned about (still importable). + let warn = !mine + && link.as_ref().is_some_and(|l| { + l.layout_name.eq_ignore_ascii_case(&layout) + && !l.sheet_set_file_name.is_empty() + && db.path.as_deref().is_none_or(|p| ss::path_key(p) != ss::path_key(&l.sheet_set_file_name)) + }); + ImportRow { drawing: drawing.clone(), layout, handle, on: !mine, taken: mine, warn } }) .collect()) } @@ -331,6 +380,30 @@ impl OpenCADStudio { Ok(()) } + /// Lock files for the drawings open in tabs, as other instances expect + /// them (the sheet status shows such a sheet as open); a drawing that + /// already has someone else's lock file is left alone. + #[cfg(not(target_arch = "wasm32"))] + fn sync_drawing_locks(&mut self) { + let open: HashSet = self.tabs.iter().filter_map(|t| t.current_path.clone()).filter(|p| p.exists()).collect(); + let gone: Vec = self.sheet_set.locks.difference(&open).cloned().collect(); + for p in gone { + release_lock(&p); + self.sheet_set.locks.remove(&p); + } + for p in open { + if self.sheet_set.locks.contains(&p) || p.with_extension("dwl").exists() { + continue; + } + if write_lock(&p).is_ok() { + self.sheet_set.locks.insert(p); + } + } + } + + #[cfg(target_arch = "wasm32")] + fn sync_drawing_locks(&mut self) {} + /// Reload every open set whose `.dst` was changed by someone else. fn poll_sheet_sets(&mut self) { let mut changed = false; @@ -463,6 +536,7 @@ impl OpenCADStudio { } } SheetSetMsg::Poll => { + self.sync_drawing_locks(); self.poll_sheet_sets(); // SSMSHEETSTATUS 2: the status is taken again every SSMPOLLTIME seconds. let every = std::time::Duration::from_secs(u64::from(self.sheet_set.settings.poll_time.max(20))); @@ -605,7 +679,7 @@ impl OpenCADStudio { (FieldId::ImportPrefix, Some(SsDialog::Form(f))) => f.prefix = v, (FieldId::ImportRow(k), Some(SsDialog::Form(f))) => { if let Some(r) = f.rows.get_mut(k) { - r.on = v && r.owner.is_none(); + r.on = v && !r.taken; } } (FieldId::RenameOption(k), Some(SsDialog::Form(f))) => { @@ -1098,11 +1172,10 @@ impl OpenCADStudio { Err(e) => self.command_line.push_error(&format!("{}: {e}", p.display())), } } + // The drawings picked replace the list. if let Some(SsDialog::Form(f)) = self.sheet_set.dialog.as_mut() { - for r in added { - if !f.rows.iter().any(|o| ss::path_key(&o.drawing) == ss::path_key(&r.drawing) && o.layout == r.layout) { - f.rows.push(r); - } + if !added.is_empty() { + f.rows = added; } } } @@ -1309,7 +1382,7 @@ impl OpenCADStudio { let path = Path::new(&r.file_name); let state = if !path.exists() { Some(SheetStatus::Missing) - } else if path.with_extension("dwl").exists() || self.tab_showing(path).is_some() { + } else if path.with_extension("dwl").exists() { Some(SheetStatus::Locked) } else { None @@ -1500,7 +1573,7 @@ impl OpenCADStudio { } } FormKind::ImportLayout => { - let chosen: Vec<&ImportRow> = f.rows.iter().filter(|r| r.on && r.owner.is_none()).collect(); + let chosen: Vec<&ImportRow> = f.rows.iter().filter(|r| r.on && !r.taken).collect(); if chosen.is_empty() { f.error = Some(crate::t!("The drawing has no layout to import.").into_owned()); self.sheet_set.dialog = Some(SsDialog::Form(f)); diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 7e6218914..0e26bcc77 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -3308,6 +3308,10 @@ impl OpenCADStudio { for i in 0..self.tabs.len() { let _ = std::fs::remove_file(self.autosave_target(i)); } + #[cfg(not(target_arch = "wasm32"))] + for path in &self.sheet_set.locks { + crate::app::commands::sheet_set::release_lock(path); + } } /// Remove the autosave recovery files, then quit the application. diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index 25e2df580..f875ffd8f 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -2646,12 +2646,10 @@ impl OpenCADStudio { let plugin_drain = Subscription::none(); // Open sheet sets reload when their .dst changes on disk. #[cfg(not(target_arch = "wasm32"))] - let sheet_set_poll = if self.sheet_set.sets.is_empty() { - Subscription::none() - } else { - iced::time::every(std::time::Duration::from_secs(1)) - .map(|_| Message::SheetSet(crate::ui::window::sheet_set::SheetSetMsg::Poll)) - }; + // Open sheet sets reload when their .dst changes on disk; the + // drawings' .dwl lock files follow the open tabs. + let sheet_set_poll = iced::time::every(std::time::Duration::from_secs(1)) + .map(|_| Message::SheetSet(crate::ui::window::sheet_set::SheetSetMsg::Poll)); #[cfg(target_arch = "wasm32")] let sheet_set_poll = Subscription::none(); let hatch_pattern_keys = if self.tabs[self.active_tab] diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index bc088b692..eb7532270 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -50,6 +50,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "New sheet location" => Some(("view", "ss-new-sheet-location")), "Remove Subset" => Some(("view", "ss-remove-subset")), "Rename Subset..." => Some(("view", "ss-rename-subset")), + "This layout is already part of a sheet set - not available for import." => Some(("view", "ss-layout-already-part")), + "Warning: this layout may belong to another sheet set." => Some(("view", "ss-layout-may-belong")), "missing" => Some(("view", "ss-status-missing")), "open" => Some(("view", "ss-status-open")), "View by category" => Some(("view", "ss-view-by-category")), @@ -70,7 +72,6 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "Prefix with sheet number" => Some(("view", "ss-prefix-with-sheet-number")), "Rename drawing file to match:" => Some(("view", "ss-rename-drawing-file-to-match")), "Available for import" => Some(("view", "ss-available-for-import")), - "Belongs to sheet set {}" => Some(("view", "ss-belongs-to-sheet-set")), "Drawings" => Some(("view", "ss-drawings")), "Select drawing files containing layouts" => Some(("view", "ss-select-drawing-files-containing-layouts")), "Browse for Drawings..." => Some(("view", "ss-browse-for-drawings")), diff --git a/src/ui/window/field_dialog.rs b/src/ui/window/field_dialog.rs index 3a5e8e966..f66ace484 100644 --- a/src/ui/window/field_dialog.rs +++ b/src/ui/window/field_dialog.rs @@ -97,8 +97,21 @@ pub fn nav_nodes(db: &codec::sheet_set::SheetSetDatabase, views: bool) -> Vec<(u let set = db.sheet_set(); let mut out = vec![(0, set.id().to_string(), db.name().to_string(), false)]; if views { - for c in db.view_categories() { + // Views of the unnamed (default) category sit under the set, then each + // named category with its views. + let all: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect(); + let named: Vec<&SsElement> = db.view_categories(); + let in_named = |v: &SsElement| { + codec::sheet_set::SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c)) + }; + for v in all.iter().filter(|v| !in_named(v)) { + out.push((1, v.id().to_string(), number_and_title(v), true)); + } + for c in &named { out.push((1, c.id().to_string(), c.prop("Name").unwrap_or("").to_string(), false)); + for v in all.iter().filter(|v| codec::sheet_set::SheetSetDatabase::view_category_of(v) == Some(c.id())) { + out.push((2, v.id().to_string(), number_and_title(v), true)); + } } return out; } @@ -156,10 +169,32 @@ pub fn nav_props(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Vec<(Stri v.extend(custom(CUSTOM_SHEET_PROP)); v } + None if el.name == "AcSmSheetView" => fixed(&[ + ("ViewNumberAndTitle", "NumberAndTitle", ""), + ("ViewTitle", "Title", ""), + ("ViewNumber", "Number", ""), + ("ViewportScale", "ViewportScale", ".16.2"), + ]), None => fixed(&[("ViewCategoryName", "Name", "")]), } } +/// The sheet drawing, named view and layout a sheet or sheet view node links to. +pub fn nav_link(db: &codec::sheet_set::SheetSetDatabase, id: &str) -> Option<(String, String, String)> { + let el = db.find(id)?; + let (sheet, view) = if el.name == "AcSmSheetView" { + let views = db.parent_of(id)?; + let sheet = db.parent_of(views.id())?; + (sheet.id().to_string(), el.named("NamedView").and_then(|v| v.prop("Name")).unwrap_or("").to_string()) + } else if codec::sheet_set::ComponentKind::of(el) == Some(codec::sheet_set::ComponentKind::Sheet) { + (id.to_string(), String::new()) + } else { + return None; + }; + let r = db.layout_reference(&sheet, "Layout")?; + Some((r.file_name, view, r.name)) +} + impl FieldDialogState { /// `\AcSm Database("…").SheetSet("…")[.Component("…")].Property` of the /// picked node, with the text case and, on a sheet, the hyperlink to it. @@ -170,19 +205,36 @@ impl FieldDialogState { }; let set = db.sheet_set().id().to_string(); let path = codec::sheet_set::native_path(db.path.as_deref().unwrap_or("")); + // ViewportScale takes a scale format (and its version), not a text case. + let scale = prop == "ViewportScale"; + let version = if scale { + codec::fields::PLOT_SCALE_FORMATS + .get(self.ss_scale_format) + .map_or("", |(_, c)| if c.starts_with("\\AcVar.16.2") { ".16.2" } else { "" }) + } else { + version + }; let mut code = format!("\\AcSm{version} Database(\"{path}\").SheetSet(\"{set}\")"); if self.ss_node != set { code.push_str(&format!(".Component(\"{}\")", self.ss_node)); } code.push('.'); code.push_str(&prop); + if scale { + let f = viewport_scale_picture(self.ss_scale_format); + if !f.is_empty() { + code.push(' '); + code.push_str(&f); + } + return code; + } if self.text_case != 0 { code.push_str(&format!(" \\f \"%tc{}\"", self.text_case)); } - let sheet = db.find(&self.ss_node).and_then(codec::sheet_set::ComponentKind::of) == Some(codec::sheet_set::ComponentKind::Sheet); - if sheet && self.ss_href { - if let Some(r) = db.layout_reference(&self.ss_node, "Layout") { - code.push_str(&format!(" \\href \"{}#,{}##1\"", r.file_name, r.name)); + // A sheet links to its layout, a view to its named view on the sheet. + if self.ss_href { + if let Some((file, view, layout)) = nav_link(db, &self.ss_node) { + code.push_str(&format!(" \\href \"{file}#{view},{layout}##1\"")); } } code @@ -959,11 +1011,23 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { props.iter().map(|(l, _, _)| (l.clone(), *l == state.ss_prop, FieldDialogMsg::SsProp(l.clone()))).collect(), 92.0, ); - let sheet = nodes.iter().any(|(_, id, _, sheet)| *sheet && *id == state.ss_node); - let mut href = checkbox(state.ss_href && sheet).label(t!("Associate hyperlink").into_owned()).text_size(12).size(14); - if sheet { + let linkable = db.is_some_and(|d| nav_link(d, &state.ss_node).is_some()) && state.ss_prop != "ViewportScale"; + let mut href = checkbox(state.ss_href && linkable).label(t!("Associate hyperlink").into_owned()).text_size(12).size(14); + if linkable { href = href.on_toggle(|v| msg(FieldDialogMsg::SsHref(v))); } + let format: Element<'a, Message> = if state.ss_prop == "ViewportScale" { + list( + codec::fields::PLOT_SCALE_FORMATS + .iter() + .enumerate() + .map(|(i, (n, _))| (t!(*n).into_owned(), state.ss_scale_format == i, FieldDialogMsg::SsScaleFormat(i))) + .collect(), + 120.0, + ) + } else { + case_list(state) + }; column![ text(t!("Sheet set")).size(12), set_row, @@ -972,7 +1036,7 @@ fn format_panel<'a>(state: &'a FieldDialogState) -> Element<'a, Message> { text(t!("Property")).size(12), prop_list, text(t!("Format")).size(12), - case_list(state), + format, href, ] .spacing(8) diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index e4f0d3170..418e9efd9 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -72,6 +72,8 @@ pub struct SheetSetManager { pub by_category: bool, /// Model Views: location id, folder and its drawings. pub locations: Vec<(String, String, Vec)>, + /// Drawings this application holds `.dwl` / `.dwl2` lock files for. + pub locks: HashSet, } /// A sheet whose drawing is missing, or open (a `.dwl` lock file, or a tab here). @@ -294,8 +296,10 @@ pub struct ImportRow { pub drawing: String, pub layout: String, pub handle: String, - /// The sheet set the layout already belongs to (cannot be imported). - pub owner: Option, + /// Already a sheet of this set: cannot be imported. + pub taken: bool, + /// The drawing's sheet link names another sheet set (importable, warned). + pub warn: bool, pub on: bool, } @@ -1277,11 +1281,20 @@ fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { let mut list = vec![head.into()]; for (k, r) in f.rows.iter().enumerate() { let file = std::path::Path::new(&r.drawing).file_name().map(|n| n.to_string_lossy().to_string()).unwrap_or_default(); - let status: Element<'a, Message> = match &r.owner { - None => text(t!("Available for import").into_owned()).size(12).style(|t: &Theme| text::Style { color: Some(t.palette().success.base.color) }).into(), - Some(owner) => text(crate::tf!("Belongs to sheet set {}", owner).into_owned()).size(12).style(|t: &Theme| text::Style { color: Some(t.palette().warning.base.color) }).into(), + let (label, warn) = if r.taken { + ("This layout is already part of a sheet set - not available for import.", true) + } else if r.warn { + ("Warning: this layout may belong to another sheet set.", true) + } else { + ("Available for import", false) }; - let enabled = r.owner.is_none(); + let status: Element<'a, Message> = text(t!(label).into_owned()) + .size(12) + .style(move |t: &Theme| text::Style { + color: Some(if warn { t.palette().warning.base.color } else { t.palette().success.base.color }), + }) + .into(); + let enabled = !r.taken; let cb = checkbox(r.on).size(14); let cb: Element<'a, Message> = if enabled { cb.on_toggle(move |v| msg(SheetSetMsg::Toggle(FieldId::ImportRow(k), v))).into() } else { cb.into() }; let name = text(r.layout.clone()).size(12).width(Fill); From 0d0fbafaa86d28a61d432397822e5ec59033248d Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 15:13:33 +0300 Subject: [PATCH 06/17] Sheet sets: Place on Sheet, sheet view menus, block lists, missing status, field refresh Model Views lists named model views and places them on the current sheet (xref, viewport, paper-space named view, view label with navigation fields, sheet view record); sheet view rows get Display, Rename & Renumber View, Set category, Place Callout Block and Place View Label Block; the View Category dialog's Add Blocks opens List of Blocks / Select Block; a sheet whose relative path is gone shows missing; sheet set fields store their values when a set changes; named views keep their centre relative to the target. Codec pin moves to the revision with these field helpers. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 21 ++ locales/bg-BG/opencadstudio.ftl | 21 ++ locales/cs-CZ/opencadstudio.ftl | 21 ++ locales/de-DE/opencadstudio.ftl | 21 ++ locales/el-GR/opencadstudio.ftl | 21 ++ locales/en-US/opencadstudio.ftl | 21 ++ locales/es-ES/opencadstudio.ftl | 21 ++ locales/fi-FI/opencadstudio.ftl | 21 ++ locales/fr-FR/opencadstudio.ftl | 21 ++ locales/hi-IN/opencadstudio.ftl | 21 ++ locales/hu-HU/opencadstudio.ftl | 21 ++ locales/it-IT/opencadstudio.ftl | 21 ++ locales/ja-JP/opencadstudio.ftl | 21 ++ locales/ko-KR/opencadstudio.ftl | 21 ++ locales/nl-NL/opencadstudio.ftl | 21 ++ locales/pl-PL/opencadstudio.ftl | 21 ++ locales/pt-BR/opencadstudio.ftl | 21 ++ locales/ru-RU/opencadstudio.ftl | 21 ++ locales/tr-TR/opencadstudio.ftl | 21 ++ locales/zh-CN/opencadstudio.ftl | 21 ++ locales/zh-TW/opencadstudio.ftl | 21 ++ src/app/commands/sheet_set.rs | 598 ++++++++++++++++++++++++++++++-- src/app/control/actions.rs | 53 ++- src/app/update/file.rs | 9 +- src/app/view/modal.rs | 3 + src/entities/field.rs | 12 + src/locale_catalog.rs | 21 ++ src/scene/camera_ops.rs | 10 +- src/ui/window/sheet_set.rs | 307 +++++++++++++--- 31 files changed, 1370 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8e3c0368..0566733cc 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=29aff7e#29aff7e8e6f4669cd1f41fc82d39521127c07d03" +source = "git+https://github.com/ramox81/cadcodec.git?rev=1ace49e#1ace49ea940cd4e1337befac1004e57a7d36015b" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index dfc938973..f8681b4b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "29aff7e" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "1ace49e" } [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 4c5bdb59e..41567568c 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4407,6 +4407,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index e26e17328..6f7495bcd 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4394,6 +4394,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 4d93bc6e8..a58827e80 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4392,6 +4392,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 420a23a23..340317851 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4393,6 +4393,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index 96bd2fd4b..db6b247ef 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4395,6 +4395,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 6d902d025..15e519cee 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4396,6 +4396,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index ac7d25f50..0eaaea507 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4395,6 +4395,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index d40962493..b4ccc4c7d 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4394,6 +4394,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index e2145f0f2..6fb8ac0b3 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4395,6 +4395,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index d8bb65f77..d80f62fb9 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4383,6 +4383,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 4cd1b0160..695be5f54 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4394,6 +4394,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 12e30fc29..508b9031d 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4394,6 +4394,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index d071f58ba..cc0bfdc1a 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4383,6 +4383,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index f3eb7cef7..c5d43cada 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4394,6 +4394,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 93e5149a2..c3dd02ee0 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4393,6 +4393,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 1839c8d4f..6dac7e7c0 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4394,6 +4394,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 72b4d61f5..e8980d72f 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4395,6 +4395,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index e983502d3..14816ca6b 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4396,6 +4396,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index 54e4faf27..fd7d85d19 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4355,6 +4355,27 @@ view = .ss-new-sheet-location = Yeni sayfa konumu .ss-remove-subset = Alt Kümeyi Kaldır .ss-rename-subset = Alt Kümeyi Yeniden Adlandır... + .ss-rename-renumber-view = Görünümü Yeniden Adlandır ve Numaralandır + .ss-list-of-blocks = Blok Listesi + .ss-select-block = Blok Seç + .ss-set-category = Kategori ata + .ss-place-callout-block = Açıklama Bloğu Yerleştir + .ss-place-view-label-block = Görünüm Etiketi Bloğu Yerleştir + .ss-place-on-sheet = Sayfaya Yerleştir + .ss-see-model-space-views = Model Uzayı Görünümlerini Göster + .ss-view-title = Görünüm başlığı + .ss-add-x = Ekle… + .ss-blocks-associated = Bu sayfa kümesiyle ilişkili bloklar + .ss-enter-drawing-file-name = Çizim dosyası adı + .ss-enter-drawing-file-name-msg = Çizim dosyası adını girin. + .ss-select-drawing-as-block = Çizim dosyasını blok olarak seç + .ss-choose-blocks-in-drawing = Çizim dosyasındaki blokları seç + .ss-preview = önizleme + .ss-open-sheet-to-place = Görünüm yerleştirmek için bu kümenin bir sayfasını kendi düzeninde açın. + .ss-open-view-sheet = Blok yerleştirmek için görünümün sayfasını kendi düzeninde açın. + .ss-no-label-block = Sayfa kümesinin görünüm etiketi bloğu yok. + .ss-no-view-to-place = __ocs_fmt_0__: yerleştirilecek görünüm yok. + .ss-cannot-be-read = __ocs_fmt_0__: okunamıyor. .ss-layout-already-part = Bu düzen zaten bir sayfa kümesinde - içe aktarılamaz. .ss-layout-may-belong = Uyarı: bu düzen başka bir sayfa kümesine ait olabilir. .ss-status-missing = yok diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 2c28c8be9..6e84dd631 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4378,6 +4378,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 8d879e2db..c9066a585 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4390,6 +4390,27 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-rename-renumber-view = Rename & Renumber View + .ss-list-of-blocks = List of Blocks + .ss-select-block = Select Block + .ss-set-category = Set category + .ss-place-callout-block = Place Callout Block + .ss-place-view-label-block = Place View Label Block + .ss-place-on-sheet = Place on Sheet + .ss-see-model-space-views = See Model Space Views + .ss-view-title = View title + .ss-add-x = Add... + .ss-blocks-associated = The following blocks are associated with this sheet set: + .ss-enter-drawing-file-name = Enter the drawing file name: + .ss-enter-drawing-file-name-msg = Enter the drawing file name. + .ss-select-drawing-as-block = Select the drawing file as a block + .ss-choose-blocks-in-drawing = Choose blocks in the drawing file: + .ss-preview = preview + .ss-open-sheet-to-place = Open a sheet of this sheet set at its layout to place a view on it. + .ss-open-view-sheet = Open the view's sheet at its layout to place a block on it. + .ss-no-label-block = The sheet set has no label block for views. + .ss-no-view-to-place = __ocs_fmt_0__: no view to place. + .ss-cannot-be-read = __ocs_fmt_0__: cannot be read. .ss-layout-already-part = This layout is already part of a sheet set - not available for import. .ss-layout-may-belong = Warning: this layout may belong to another sheet set. .ss-status-missing = missing diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index 9b236444a..c14522311 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -4,9 +4,13 @@ use crate::app::{Message, OpenCADStudio}; use crate::ui::window::sheet_set::{ - Category, FieldId, Form, FormKind, FoundLayout, ImportRow, MenuAction, PropRow, Properties, RowKey, SheetSetMsg, - SheetStatus, SsDialog, TemplatePick, Wizard, WizardMsg, + BlockList, Category, FieldId, Form, FormKind, FoundLayout, ImportRow, MenuAction, Placing, PropRow, Properties, + RowKey, SelectBlock, SheetSetMsg, SheetStatus, SsDialog, TemplatePick, ViewAction, Wizard, WizardMsg, }; +use crate::command::{CadCommand, CmdOption, CmdResult}; +use codec::entities::Entity as _; +use crate::scene::model::wire_model::WireModel; +use glam::DVec3; use codec::sheet_set::{self as ss, ComponentKind, LayoutReference, SheetSetData, SheetSetDatabase}; use iced::Task; use std::collections::HashSet; @@ -113,6 +117,113 @@ pub(in crate::app) fn release_lock(path: &Path) { let _ = std::fs::remove_file(path.with_extension("dwl2")); } +/// The insertion point of Place on Sheet (with the scale, picked from the +/// right-click list) or of a callout / view label block. +struct SheetSetPlaceCommand { + /// The view's size in model units (zero for a block). + size: (f64, f64), + scale: f64, + scales: Vec<(String, f64)>, +} + +impl CadCommand for SheetSetPlaceCommand { + fn name(&self) -> &'static str { + "SSMPLACE" + } + + fn prompt(&self) -> String { + "Specify insertion point:".into() + } + + fn options(&self) -> Vec { + self.scales.iter().map(|(name, _)| CmdOption::new(name, name)).collect() + } + + fn wants_text_input(&self) -> bool { + !self.scales.is_empty() + } + + fn on_text_input(&mut self, text: &str) -> Option { + let (_, scale) = self.scales.iter().find(|(name, _)| name.eq_ignore_ascii_case(text.trim()))?; + self.scale = *scale; + Some(CmdResult::NeedPoint) + } + + fn on_point(&mut self, pt: DVec3) -> CmdResult { + CmdResult::Dispatch(format!("_SSMPLACEPOINT {} {} {}", pt.x, pt.y, self.scale)) + } + + fn on_enter(&mut self) -> CmdResult { + CmdResult::Cancel + } + + fn on_escape(&mut self) -> CmdResult { + CmdResult::Cancel + } + + /// The viewport's outline follows the cursor at its lower-left corner. + fn on_preview_wires(&mut self, pt: DVec3) -> Vec { + let (w, h) = (self.size.0 * self.scale, self.size.1 * self.scale); + if w <= 0.0 || h <= 0.0 { + return Vec::new(); + } + let p = |x: f64, y: f64| [pt.x + x, pt.y + y, pt.z]; + vec![WireModel::solid_f64( + "ssm_place".to_string(), + vec![p(0.0, 0.0), p(w, 0.0), p(w, h), p(0.0, h), p(0.0, 0.0)], + WireModel::CYAN, + false, + )] + } +} + +/// A model view to place: centre (world), width, height, target, direction. +struct ModelView { + center: codec::types::Vector3, + width: f64, + height: f64, + target: codec::types::Vector3, + direction: codec::types::Vector3, +} + +/// Named view `view` of a model drawing, or its extents. +// ponytail: plan views only (centre = target + view centre); a twisted or 3D +// view would need the full DCS transform. +fn model_view(doc: &codec::CadDocument, view: Option<&str>) -> Option { + use codec::types::Vector3; + if let Some(name) = view { + let v = doc.views.iter().find(|v| !v.paper_space && v.name.eq_ignore_ascii_case(name))?; + return Some(ModelView { + center: Vector3::new(v.target.x + v.center.x, v.target.y + v.center.y, v.target.z), + width: v.width.abs().max(1e-6), + height: v.height.abs().max(1e-6), + target: v.target.clone(), + direction: v.direction.clone(), + }); + } + let (lo, hi) = (&doc.header.model_space_extents_min, &doc.header.model_space_extents_max); + let (w, h) = (hi.x - lo.x, hi.y - lo.y); + if !(w > 0.0 && h > 0.0 && w.is_finite() && h.is_finite()) { + return None; + } + Some(ModelView { + center: Vector3::new((lo.x + hi.x) / 2.0, (lo.y + hi.y) / 2.0, 0.0), + width: w, + height: h, + target: Vector3::ZERO, + direction: Vector3::new(0.0, 0.0, 1.0), + }) +} + +/// The `Database("…").SheetSet("…").Component("…").` prefix of a navigation field. +fn component_prefix(db: &SheetSetDatabase, id: &str) -> String { + format!( + "Database(\"{}\").SheetSet(\"{}\").Component(\"{id}\").", + ss::native_path(db.path.as_deref().unwrap_or("")), + db.sheet_set().id() + ) +} + /// Whether new sheets of `component` prompt for their template: its own /// "Prompt for template", else the nearest parent's. fn prompts_for_template(db: &SheetSetDatabase, component: &str) -> bool { @@ -166,6 +277,18 @@ fn follow_rename(f: &mut Form) { } } +fn view_form(db: &SheetSetDatabase, set: usize, id: &str) -> Form { + let el = db.find(id); + Form { + kind: FormKind::RenameView, + set, + component: id.to_string(), + number: el.and_then(|e| e.prop("Number")).unwrap_or("").to_string(), + title: el.and_then(|e| e.prop("Title")).unwrap_or("").to_string(), + ..Form::default() + } +} + fn rename_form(db: &SheetSetDatabase, set: usize, id: &str) -> Form { let el = db.find(id); let reference = db.layout_reference(id, "Layout").unwrap_or_default(); @@ -236,6 +359,13 @@ impl OpenCADStudio { self.open_new_sheet_set_wizard(); Some(Task::none()) } + "_SSMPLACEPOINT" => { + let v: Vec = arg.split_whitespace().filter_map(|t| t.parse().ok()).collect(); + if let [x, y, scale] = v[..] { + self.place_at(i, x, y, scale); + } + Some(Task::none()) + } "OPENSHEETSET" => { if !arg.is_empty() { return Some(self.update(Message::SheetSet(SheetSetMsg::OpenPicked(Some(PathBuf::from( @@ -345,6 +475,8 @@ impl OpenCADStudio { self.sheet_set.status.clear(); } for tab in &mut self.tabs { + // The fields keep the new values, as when the reference updates them. + crate::entities::field::refresh_sheet_set_fields(&mut tab.scene.document); let changes: Vec<_> = tab .scene .document @@ -571,6 +703,36 @@ impl OpenCADStudio { self.save_current_sheet_set(); } SheetSetMsg::AddBlocks => { + if let Some(SsDialog::Category(c)) = self.sheet_set.dialog.take() { + let blocks = c.blocks.iter().map(|(id, label, _)| (id.clone(), label.clone())).collect(); + self.sheet_set.dialog = Some(SsDialog::BlockList(BlockList { category: Box::new(c), blocks, selected: None })); + } + } + SheetSetMsg::BlocksPicked(_) | SheetSetMsg::LocationPicked(None) => {} + SheetSetMsg::BlockListSelect(k) => { + if let Some(SsDialog::BlockList(b)) = self.sheet_set.dialog.as_mut() { + b.selected = Some(k); + } + } + SheetSetMsg::BlockListDelete => { + if let Some(SsDialog::BlockList(b)) = self.sheet_set.dialog.as_mut() { + if let Some(k) = b.selected.take().filter(|k| *k < b.blocks.len()) { + b.blocks.remove(k); + } + } + } + SheetSetMsg::BlockListAdd => { + if let Some(SsDialog::BlockList(b)) = self.sheet_set.dialog.take() { + self.sheet_set.dialog = Some(SsDialog::SelectBlock(SelectBlock { + list: Box::new(b), + file: String::new(), + whole: true, + names: Vec::new(), + error: None, + })); + } + } + SheetSetMsg::SelectBlockBrowse => { return Task::perform( async { crate::sys::file_dialog() @@ -580,11 +742,36 @@ impl OpenCADStudio { .await .map(|h| crate::sys::handle_path(&h)) }, - |p| Message::SheetSet(SheetSetMsg::BlocksPicked(p)), + |p| Message::SheetSet(SheetSetMsg::SelectBlockPicked(p)), ) } - SheetSetMsg::BlocksPicked(Some(path)) => self.category_add_blocks(path), - SheetSetMsg::BlocksPicked(None) | SheetSetMsg::LocationPicked(None) => {} + SheetSetMsg::SelectBlockPicked(None) => {} + SheetSetMsg::SelectBlockPicked(Some(path)) => self.select_block_file(path), + SheetSetMsg::SelectBlockWhole(v) => { + if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() { + b.whole = v; + } + } + SheetSetMsg::SelectBlockCheck(k, v) => { + if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() { + if let Some(n) = b.names.get_mut(k) { + n.2 = v; + } + } + } + SheetSetMsg::ToggleDrawing(path) => { + let key = ss::path_key(&path); + if !self.sheet_set.open_drawings.remove(&key) { + self.load_model_views(&path); + self.sheet_set.open_drawings.insert(key); + } + } + SheetSetMsg::SeeViews(path) => { + self.load_model_views(&path); + self.sheet_set.open_drawings.insert(ss::path_key(&path)); + } + SheetSetMsg::PlaceOnSheet(path, view) => self.start_place_view(path, view), + SheetSetMsg::ViewMenu(id, action) => return self.view_menu(id, action), SheetSetMsg::AddLocation => { return Task::perform( async { @@ -790,6 +977,8 @@ impl OpenCADStudio { /// the wizard goes back to it. pub(in crate::app) fn close_sheet_set_dialog(&mut self) { match self.sheet_set.dialog.take() { + Some(SsDialog::BlockList(b)) => self.sheet_set.dialog = Some(SsDialog::Category(*b.category)), + Some(SsDialog::SelectBlock(b)) => self.sheet_set.dialog = Some(SsDialog::BlockList(*b.list)), Some(SsDialog::Properties(p)) if p.wizard.is_some() => { self.sheet_set.dialog = p.wizard.map(|w| SsDialog::Wizard(*w)); } @@ -936,6 +1125,9 @@ impl OpenCADStudio { if ss::path_key(path) == *want { task = Task::done(Message::LayoutSwitch(layout.clone())); self.sheet_set_pending_layout = None; + if let Some(view) = self.sheet_set.pending_view.take() { + task = task.chain(Task::done(Message::Command(format!("VIEW RESTORE {view}")))); + } } } if self.sheet_set.settings.locate == 1 { @@ -1030,7 +1222,7 @@ impl OpenCADStudio { c.name = v; } } - Some(SsDialog::Confirm(..)) | Some(SsDialog::Template(_)) => {} + Some(SsDialog::Confirm(..)) | Some(SsDialog::Template(_)) | Some(SsDialog::BlockList(_)) | Some(SsDialog::SelectBlock(_)) => {} Some(SsDialog::Form(f)) => { let file_follows = f.file_name == format!("{} {}", f.number, f.title).trim(); match field { @@ -1248,6 +1440,25 @@ impl OpenCADStudio { /// Rename & Renumber < Previous / Next >: apply, then the neighbouring sheet. fn rename_step(&mut self, forward: bool) { let Some(SsDialog::Form(f)) = self.sheet_set.dialog.take() else { return }; + if f.kind == FormKind::RenameView { + let Some(db) = self.sheet_set.sets.get_mut(f.set) else { return }; + if let Some(el) = db.find_mut(&f.component) { + if f.number.trim().is_empty() { + el.remove_named("Number"); + } else { + el.set_prop("Number", f.number.trim()); + } + el.set_prop("Title", f.title.trim()); + } + let ids: Vec = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).map(|v| v.id().to_string()).collect(); + let k = ids.iter().position(|i| *i == f.component).unwrap_or(0); + let next = if forward { (k + 1).min(ids.len().saturating_sub(1)) } else { k.saturating_sub(1) }; + let form = view_form(db, f.set, &ids[next]); + self.sheet_set.current = Some(f.set); + self.save_current_sheet_set(); + self.sheet_set.dialog = Some(SsDialog::Form(form)); + return; + } if let Err(e) = self.apply_rename(&f) { self.sheet_set.dialog = Some(SsDialog::Form(Form { error: Some(e), ..f })); return; @@ -1279,33 +1490,10 @@ impl OpenCADStudio { }) .collect(); let name = id.as_deref().and_then(|i| db.find(i)).and_then(|c| c.prop("Name")).unwrap_or("").to_string(); - self.sheet_set.dialog = Some(SsDialog::Category(Category { set, id, name, blocks, error: None })); + self.sheet_set.dialog = Some(SsDialog::Category(Category { set, id, name, blocks, removed: Vec::new(), error: None })); self.active_modal = Some(crate::app::ModalKind::SheetSet); } - /// Add Blocks...: the named blocks of a drawing become callout blocks. - fn category_add_blocks(&mut self, path: PathBuf) { - let doc = match crate::io::load_file(&path) { - Ok(d) => d, - Err(e) => { - self.command_line.push_error(&format!("{}: {e}", path.display())); - return; - } - }; - let file = ss::native_path(&path.to_string_lossy()); - let Some(SsDialog::Category(c)) = self.sheet_set.dialog.as_mut() else { return }; - let mut names: Vec<(String, String)> = doc - .block_records - .iter() - .filter(|b| !b.name.starts_with('*') && !b.is_layout()) - .map(|b| (b.name.clone(), format!("{:X}", b.handle.value()))) - .collect(); - names.sort(); - for (name, handle) in names { - c.blocks.push((format!("new:{file}|{name}|{handle}"), format!("{name} ({file})"), true)); - } - } - fn category_ok(&mut self, mut c: Category) { let name = c.name.trim().to_string(); if name.is_empty() { @@ -1315,6 +1503,21 @@ impl OpenCADStudio { } self.close_active_modal(); let Some(db) = self.sheet_set.sets.get_mut(c.set) else { return }; + // Blocks deleted in List of Blocks leave the set and every category. + if !c.removed.is_empty() { + let cats: Vec<(String, String, Vec)> = db + .view_categories() + .iter() + .map(|k| (k.id().to_string(), k.prop("Name").unwrap_or("").to_string(), db.category_blocks(k.id()))) + .collect(); + for (id, name, blocks) in cats { + let kept: Vec = blocks.into_iter().filter(|b| !c.removed.contains(b)).collect(); + db.set_view_category(Some(&id), &name, &kept); + } + for id in &c.removed { + db.remove(id); + } + } let mut used = Vec::new(); for (id, _, on) in &c.blocks { let id = match id.strip_prefix("new:") { @@ -1337,6 +1540,280 @@ impl OpenCADStudio { self.save_current_sheet_set(); } + /// Named model views of a drawing (read once, when its row is opened). + fn load_model_views(&mut self, path: &str) { + let key = ss::path_key(path); + if self.sheet_set.model_views.contains_key(&key) { + return; + } + let views = crate::io::load_file(Path::new(path)) + .map(|d| d.views.iter().filter(|v| !v.paper_space).map(|v| v.name.clone()).collect()) + .unwrap_or_default(); + self.sheet_set.model_views.insert(key, views); + } + + /// The current set's sheet shown by the active tab at its current layout. + fn current_sheet(&self) -> Option<(usize, String)> { + let set = self.sheet_set.current?; + let db = self.sheet_set.db()?; + let tab = &self.tabs[self.active_tab]; + let path = tab.current_path.as_ref()?.to_string_lossy().to_string(); + let layout = tab.scene.current_layout.clone(); + let sheet = db.sheet_for(&path, Some(&layout))?; + sheet + .named("Layout") + .and_then(|r| r.prop("Name")) + .is_some_and(|n| n.eq_ignore_ascii_case(&layout)) + .then(|| (set, sheet.id().to_string())) + } + + /// Start the insertion point of Place on Sheet / a callout or label block. + fn start_placing(&mut self, placing: Placing, size: (f64, f64)) { + let i = self.active_tab; + let scales: Vec<(String, f64)> = if size.0 > 0.0 { + self.tabs[i] + .scene + .scale_list() + .into_iter() + .filter(|(_, _, factor)| *factor > 0.0) + .map(|(name, _, factor)| (name, factor)) + .collect() + } else { + Vec::new() + }; + self.sheet_set.placing = Some(placing); + let command = SheetSetPlaceCommand { size, scale: 1.0, scales }; + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + } + + /// Place on Sheet: only on a sheet of this set open at its layout. + fn start_place_view(&mut self, drawing: String, view: Option) { + let Some((set, sheet)) = self.current_sheet() else { + self.command_line + .push_error(crate::t!("Open a sheet of this sheet set at its layout to place a view on it.").as_ref()); + return; + }; + let size = crate::io::load_file(Path::new(&drawing)) + .ok() + .and_then(|d| model_view(&d, view.as_deref())) + .map(|v| (v.width, v.height)); + let Some(size) = size else { + self.command_line.push_error(crate::tf!("{}: no view to place.", drawing).as_ref()); + return; + }; + self.start_placing(Placing::View { set, sheet, drawing, view }, size); + } + + /// A sheet view's commands. + fn view_menu(&mut self, id: String, action: ViewAction) -> Task { + let Some(set) = self.sheet_set.current else { return Task::none() }; + let Some(db) = self.sheet_set.db() else { return Task::none() }; + let Some(sheet) = db.parent_of(&id).and_then(|v| db.parent_of(v.id())).map(|s| s.id().to_string()) else { + return Task::none(); + }; + let view_name = db.find(&id).and_then(|v| v.named("NamedView")).and_then(|r| r.prop("Name")).unwrap_or("").to_string(); + match action { + ViewAction::Display => { + let open = db.layout_reference(&sheet, "Layout").map(|r| self.tab_showing(Path::new(&r.file_name)).is_some()).unwrap_or(false); + let task = self.open_sheet(&sheet); + if open { + return task.chain(Task::done(Message::Command(format!("VIEW RESTORE {view_name}")))); + } + self.sheet_set.pending_view = Some(view_name); + return task; + } + ViewAction::Rename => { + self.sheet_set.dialog = Some(SsDialog::Form(view_form(db, set, &id))); + self.active_modal = Some(crate::app::ModalKind::SheetSet); + } + ViewAction::Category(category) => { + if let Some(db) = self.sheet_set.db_mut() { + if let Some(v) = db.find_mut(&id) { + let mut r = ss::object("AcSmObjectReference", ss::CLSID_OBJECT_REFERENCE, Some("Category")); + r.set_prop_vt("ReferencedObject", -1, &category); + v.put_named(r); + } + } + self.save_current_sheet_set(); + } + ViewAction::Callout(_) | ViewAction::Label if self.current_sheet().map(|s| s.1) != Some(sheet.clone()) => { + self.command_line + .push_error(crate::t!("Open the view's sheet at its layout to place a block on it.").as_ref()); + } + ViewAction::Callout(block) => { + let Some(b) = db.find(&block) else { return Task::none() }; + let (file, name) = (db.resolve_file(b), b.prop("Name").unwrap_or("").to_string()); + self.start_placing(Placing::Block { set, sheet, view: id, file, name }, (0.0, 0.0)); + } + ViewAction::Label => { + let Some(b) = db.sheet_set().named("DefLabelBlk") else { + self.command_line.push_error(crate::t!("The sheet set has no label block for views.").as_ref()); + return Task::none(); + }; + let (file, name) = (db.resolve_file(b), b.prop("Name").unwrap_or("").to_string()); + self.start_placing(Placing::Block { set, sheet, view: id, file, name }, (0.0, 0.0)); + } + } + Task::none() + } + + /// Bring block `name` of drawing `file` into tab `i` (when missing) and + /// insert it at `at` with its attributes; their `?View.` / `?Sheet.` + /// fields become the view's and sheet's navigation fields. + fn insert_set_block(&mut self, i: usize, file: &str, name: &str, at: codec::types::Vector3, view: &str, sheet: &str, set: usize) { + let Ok(source) = crate::io::load_file(Path::new(file)) else { + self.command_line.push_error(crate::tf!("{}: cannot be read.", file).as_ref()); + return; + }; + let probe = codec::EntityType::Insert(codec::entities::Insert::new(name.to_string(), codec::types::Vector3::ZERO)); + let deps = crate::app::ClipboardDeps::capture(&source, std::slice::from_ref(&probe)); + self.merge_dependencies(i, &deps); + for def in deps.blocks { + let scene = &mut self.tabs[i].scene; + if scene.document.block_records.get(&def.name).is_none() { + scene.define_block_raw(&def.name, def.base_point, def.entities); + } + } + let units = source.block_records.get(name).map(|b| b.units).filter(|u| *u != 0).unwrap_or(source.header.insertion_units); + let host = self.tabs[i].scene.document.header.insertion_units; + let k = crate::app::properties::insert_unit_scale(host, units).unwrap_or(1.0); + let mut insert = codec::entities::Insert::new(name.to_string(), at).with_scale(k, k, k); + let transform = insert.get_transform(); + let doc = &self.tabs[i].scene.document; + let attdefs: Vec = doc + .block_records + .get(name) + .map(|br| { + br.entity_handles + .iter() + .filter_map(|h| match doc.get_entity(*h) { + Some(codec::EntityType::AttributeDefinition(d)) if !d.flags.constant => Some(d.clone()), + _ => None, + }) + .collect() + }) + .unwrap_or_default(); + for d in &attdefs { + let mut a = codec::entities::AttributeEntity::from_definition(d, None); + a.apply_transform(&transform); + insert.attributes.push(a); + } + let handle = self.tabs[i].scene.add_entity(codec::EntityType::Insert(insert)); + let Some(db) = self.sheet_set.sets.get(set) else { return }; + let (view_prefix, sheet_prefix) = (component_prefix(db, view), component_prefix(db, sheet)); + crate::entities::field::attach_attribute_fields_mapped(&mut self.tabs[i].scene.document, handle, &|code: &str| { + code.replace("?View.", &view_prefix).replace("?Sheet.", &sheet_prefix) + }); + self.tabs[i].scene.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]); + } + + /// The insertion point was picked: place the view (xref, viewport, paper + /// named view, view label, sheet view record) or the block. + fn place_at(&mut self, i: usize, x: f64, y: f64, scale: f64) { + use codec::types::Vector3; + let Some(placing) = self.sheet_set.placing.take() else { return }; + self.push_undo_snapshot(i, "Place on Sheet"); + match placing { + Placing::Block { set, sheet, view, file, name } => { + self.insert_set_block(i, &file, &name, Vector3::new(x, y, 0.0), &view, &sheet, set); + } + Placing::View { set, sheet, drawing, view } => { + let Some(mv) = crate::io::load_file(Path::new(&drawing)).ok().and_then(|d| model_view(&d, view.as_deref())) else { + return; + }; + let title = view.clone().unwrap_or_else(|| { + Path::new(&drawing).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default() + }); + // The model drawing is attached to the sheet's model space. + let host = self.tabs[i].current_path.clone(); + let request = crate::modules::insert::xattach::XrefAttachRequest { + path: drawing.clone(), + overlay: false, + path_type: crate::io::xref_model::Pathtype::Relative, + }; + let scene = &mut self.tabs[i].scene; + let xref = crate::modules::insert::xattach::prepare_xref_definition(scene, &request, host.as_deref()); + let model = scene.document.header.model_space_block_handle; + let inserted = scene.document.entities().any(|e| { + e.common().owner_handle == model && matches!(e, codec::EntityType::Insert(ins) if ins.block_name.eq_ignore_ascii_case(&xref)) + }); + if !inserted { + let saved = std::mem::replace(&mut scene.current_layout, "Model".to_string()); + scene.add_entity(codec::EntityType::Insert(codec::entities::Insert::new(xref, Vector3::ZERO))); + scene.current_layout = saved; + } + // The viewport: lower-left corner at the point, the view at the scale. + let (w, h) = (mv.width * scale, mv.height * scale); + let mut vp = codec::entities::Viewport::new(); + vp.center = Vector3::new(x + w / 2.0, y + h / 2.0, 0.0); + vp.width = w; + vp.height = h; + vp.id = 2; + vp.view_target = mv.target.clone(); + vp.view_direction = mv.direction.clone(); + vp.view_center = Vector3::new(mv.center.x - mv.target.x, mv.center.y - mv.target.y, 0.0); + vp.view_height = mv.height; + vp.custom_scale = scale; + scene.add_entity(codec::EntityType::Viewport(vp)); + // A paper-space named view of the same name frames it. + let mut named = codec::tables::View::new(title.as_str()); + named.handle = scene.document.allocate_handle(); + named.paper_space = true; + named.center = Vector3::new(x + w / 2.0, y + h / 2.0, 0.0); + named.width = w; + named.height = h; + named.direction = Vector3::new(0.0, 0.0, 1.0); + let view_handle = format!("{:X}", named.handle.value()); + scene.document.views.add_or_replace(named); + let file = host.map(|p| ss::native_path(&p.to_string_lossy())).unwrap_or_default(); + let Some(db) = self.sheet_set.sets.get_mut(set) else { return }; + // The view joins the set's default (unnamed) category. + let category = db + .sheet_set() + .named("ViewCategories") + .and_then(|c| c.children.iter().find(|k| k.name == "AcSmViewCategory" && k.prop("Name").is_none())) + .map(|k| k.id().to_string()); + let reference = LayoutReference { file_name: file, name: title.clone(), handle: view_handle }; + let Some(view_id) = db.add_sheet_view(&sheet, category.as_deref(), &reference, &title) else { return }; + let label = db.sheet_set().named("DefLabelBlk").map(|b| (db.resolve_file(b), b.prop("Name").unwrap_or("").to_string())); + // The set is written first, so the label's fields find the new view. + self.sheet_set.current = Some(set); + self.save_current_sheet_set(); + if let Some((file, name)) = label { + self.insert_set_block(i, &file, &name, Vector3::new(x, y, 0.0), &view_id, &sheet, set); + } + } + } + self.tabs[i].dirty = true; + self.refresh_xref_manager(); + } + + /// Select Block: read a drawing's named blocks. + fn select_block_file(&mut self, path: PathBuf) { + let names: Vec<(String, String, bool)> = match crate::io::load_file(&path) { + Ok(doc) => { + let mut n: Vec<(String, String, bool)> = doc + .block_records + .iter() + .filter(|b| !b.name.starts_with('*') && !b.is_layout()) + .map(|b| (b.name.clone(), format!("{:X}", b.handle.value()), false)) + .collect(); + n.sort(); + n + } + Err(e) => { + self.command_line.push_error(&format!("{}: {e}", path.display())); + return; + } + }; + if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() { + b.file = path.to_string_lossy().to_string(); + b.names = names; + b.error = None; + } + } + /// Model Views: each location folder of the current set and its drawings. pub(in crate::app) fn refresh_locations(&mut self) { let Some(db) = self.sheet_set.db() else { @@ -1376,11 +1853,20 @@ impl OpenCADStudio { return; } let Some(db) = self.sheet_set.db() else { return }; + let folder = db.path.as_deref().and_then(|p| Path::new(p).parent().map(Path::to_path_buf)); let mut status = std::collections::HashMap::new(); for sheet in db.sheets() { let Some(r) = db.layout_reference(sheet.id(), "Layout") else { continue }; let path = Path::new(&r.file_name); - let state = if !path.exists() { + // The reference finds a sheet through its relative path: when that + // names no file the sheet is missing, even if the stored absolute + // path still exists. + let relative = sheet.named("Layout").and_then(|l| l.prop("Relative_FileName")).zip(folder.as_deref()); + let found = match relative { + Some((rel, folder)) => folder.join(rel.replace('\\', std::path::MAIN_SEPARATOR_STR)).exists(), + None => path.exists(), + }; + let state = if !found { Some(SheetStatus::Missing) } else if path.with_extension("dwl").exists() { Some(SheetStatus::Locked) @@ -1449,6 +1935,46 @@ impl OpenCADStudio { self.category_ok(c); Task::none() } + // List of Blocks: the list goes back to the View Category dialog. + Some(SsDialog::BlockList(b)) => { + let mut c = *b.category; + for (id, _, _) in &c.blocks { + if !id.starts_with("new:") && !b.blocks.iter().any(|(k, _)| k == id) { + c.removed.push(id.clone()); + } + } + c.blocks = b + .blocks + .into_iter() + .map(|(id, label)| { + let on = c.blocks.iter().find(|(k, _, _)| *k == id).is_none_or(|(_, _, on)| *on); + (id, label, on) + }) + .collect(); + self.sheet_set.dialog = Some(SsDialog::Category(c)); + Task::none() + } + // Select Block: the whole drawing or its checked blocks join the list. + Some(SsDialog::SelectBlock(mut b)) => { + if b.file.is_empty() { + b.error = Some(crate::t!("Enter the drawing file name.").into_owned()); + self.sheet_set.dialog = Some(SsDialog::SelectBlock(b)); + return Task::none(); + } + let file = ss::native_path(&b.file); + let mut list = *b.list; + if b.whole { + let stem = Path::new(&file).file_stem().map(|s| s.to_string_lossy().to_string()).unwrap_or_default(); + list.blocks.push((format!("new:{file}|{stem}|"), format!("{stem} ({file})"))); + } else { + for (name, handle, on) in b.names.into_iter().filter(|n| n.2) { + let _ = on; + list.blocks.push((format!("new:{file}|{name}|{handle}"), format!("{name} ({file})"))); + } + } + self.sheet_set.dialog = Some(SsDialog::BlockList(list)); + Task::none() + } None => Task::none(), } } @@ -1592,6 +2118,16 @@ impl OpenCADStudio { } } FormKind::NewSheet => return self.create_sheet(f), + FormKind::RenameView => { + if let Some(el) = db.find_mut(&f.component) { + if f.number.trim().is_empty() { + el.remove_named("Number"); + } else { + el.set_prop("Number", f.number.trim()); + } + el.set_prop("Title", f.title.trim()); + } + } } self.close_active_modal(); self.sheet_set.current = Some(f.set); diff --git a/src/app/control/actions.rs b/src/app/control/actions.rs index 6dfe48e1e..ba73af29a 100644 --- a/src/app/control/actions.rs +++ b/src/app/control/actions.rs @@ -1290,7 +1290,6 @@ impl OpenCADStudio { let (i, v) = index_value(arg)?; M::Toggle(F::CategoryBlock(i), v == "1") } - "add_blocks" => M::BlocksPicked(Some(std::path::PathBuf::from(arg))), "add_location" => M::LocationPicked(Some(std::path::PathBuf::from(arg))), "remove_location" => { let id = self @@ -1303,6 +1302,58 @@ impl OpenCADStudio { M::RemoveLocation(id) } "open_drawing" => M::OpenDrawing(arg.into()), + "toggle_drawing" => M::ToggleDrawing(arg.into()), + "see_views" => M::SeeViews(arg.into()), + // place=[|] + "place" => { + let (drawing, view) = arg.split_once('|').map_or((arg, None), |(d, v)| (d, Some(v.to_string()))); + M::PlaceOnSheet(drawing.into(), view) + } + // view=:|callout=> + "view" => { + use crate::ui::window::sheet_set::ViewAction as VA; + let (spec, action) = arg.rsplit_once(':').ok_or_else(|| failure("invalid_value", "view=:"))?; + let db = self.sheet_set.db().ok_or_else(|| failure("invalid_state", "No sheet set"))?; + let view = db + .sheets() + .into_iter() + .flat_map(|s| db.sheet_views(s)) + .find(|v| codec::sheet_set::number_and_title(v) == spec || v.prop("Title") == Some(spec)) + .map(|v| v.id().to_string()) + .ok_or_else(|| failure("invalid_value", "No such sheet view"))?; + let (verb, name) = action.split_once('=').unwrap_or((action, "")); + let a = match verb { + "display" => VA::Display, + "rename" => VA::Rename, + "label" => VA::Label, + "category" => VA::Category( + db.view_categories() + .iter() + .find(|c| c.prop("Name") == Some(name)) + .map(|c| c.id().to_string()) + .ok_or_else(|| failure("invalid_value", "No such view category"))?, + ), + "callout" => VA::Callout( + db.callout_blocks() + .iter() + .find(|b| b.prop("Name") == Some(name)) + .map(|b| b.id().to_string()) + .ok_or_else(|| failure("invalid_value", "No such callout block"))?, + ), + _ => return Err(failure("invalid_value", "display|rename|label|category=|callout=")), + }; + M::ViewMenu(view, a) + } + "add_blocks" => M::AddBlocks, + "block_list_select" => M::BlockListSelect(arg.parse().map_err(|_| failure("invalid_value", "index"))?), + "block_list_add" => M::BlockListAdd, + "block_list_delete" => M::BlockListDelete, + "select_block_file" => M::SelectBlockPicked(Some(std::path::PathBuf::from(arg))), + "select_block_whole" => M::SelectBlockWhole(arg == "1"), + "select_block_check" => { + let (i, v) = index_value(arg)?; + M::SelectBlockCheck(i, v == "1") + } "ok" => M::Ok, "cancel" => M::Cancel, _ => return Err(failure("invalid_value", "Unknown sheet set action")), diff --git a/src/app/update/file.rs b/src/app/update/file.rs index 0e26bcc77..bfa173077 100644 --- a/src/app/update/file.rs +++ b/src/app/update/file.rs @@ -5520,12 +5520,9 @@ impl OpenCADStudio { let half_w = view.width.abs() * 0.5; let half_h = view.height.abs() * 0.5; (half_w > 1e-9 && half_h > 1e-9).then_some(())?; - self.area_plot_job(( - view.center.x - half_w, - view.center.y - half_h, - view.center.x + half_w, - view.center.y + half_h, - )) + // The view centre is relative to its target (plan views). + let (cx, cy) = (view.target.x + view.center.x, view.target.y + view.center.y); + self.area_plot_job((cx - half_w, cy - half_h, cx + half_w, cy + half_h)) } pub(in crate::app) fn extents_plot_job(&self) -> Option { diff --git a/src/app/view/modal.rs b/src/app/view/modal.rs index 4a3fc1d4e..4aba5becb 100644 --- a/src/app/view/modal.rs +++ b/src/app/view/modal.rs @@ -587,12 +587,15 @@ impl OpenCADStudio { crate::ui::window::sheet_set::SsDialog::Properties(_) => (640, 700), crate::ui::window::sheet_set::SsDialog::Form(f) => match f.kind { crate::ui::window::sheet_set::FormKind::Rename => (560, 470), + crate::ui::window::sheet_set::FormKind::RenameView => (520, 200), crate::ui::window::sheet_set::FormKind::ImportLayout => (620, 470), _ => (560, 420), }, crate::ui::window::sheet_set::SsDialog::Confirm(..) => (460, 190), crate::ui::window::sheet_set::SsDialog::Template(_) => (480, 400), crate::ui::window::sheet_set::SsDialog::Category(_) => (480, 440), + crate::ui::window::sheet_set::SsDialog::BlockList(_) => (540, 330), + crate::ui::window::sheet_set::SsDialog::SelectBlock(_) => (580, 390), }; sized_flow(ex, w, h, |flow| crate::ui::window::sheet_set::dialog_view(&self.sheet_set, flow)) } diff --git a/src/entities/field.rs b/src/entities/field.rs index bb1eff50f..f584e1280 100644 --- a/src/entities/field.rs +++ b/src/entities/field.rs @@ -387,6 +387,18 @@ pub fn attach_attribute_fields(document: &mut CadDocument, insert: Handle) -> Ve document.attach_attribute_fields(insert, &context) } +/// Store fresh values for the sheet set fields (after a sheet set changed). +pub fn refresh_sheet_set_fields(document: &mut CadDocument) -> Vec { + let context = OcsFieldContext(None); + document.refresh_sheet_set_fields(&context) +} + +/// [`attach_attribute_fields`] with every field code passed through `map`. +pub fn attach_attribute_fields_mapped(document: &mut CadDocument, insert: Handle, map: &dyn Fn(&str) -> String) -> Vec { + let context = OcsFieldContext(None); + document.attach_attribute_fields_mapped(insert, &context, map) +} + /// Whether any attribute definition of `block` hosts a field. pub fn block_has_attribute_fields(document: &CadDocument, block: &str) -> bool { let Some(record) = document.block_records.iter().find(|r| r.name.eq_ignore_ascii_case(block)) else { diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index eb7532270..204e15eec 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -50,6 +50,27 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "New sheet location" => Some(("view", "ss-new-sheet-location")), "Remove Subset" => Some(("view", "ss-remove-subset")), "Rename Subset..." => Some(("view", "ss-rename-subset")), + "Rename & Renumber View" => Some(("view", "ss-rename-renumber-view")), + "List of Blocks" => Some(("view", "ss-list-of-blocks")), + "Select Block" => Some(("view", "ss-select-block")), + "Set category" => Some(("view", "ss-set-category")), + "Place Callout Block" => Some(("view", "ss-place-callout-block")), + "Place View Label Block" => Some(("view", "ss-place-view-label-block")), + "Place on Sheet" => Some(("view", "ss-place-on-sheet")), + "See Model Space Views" => Some(("view", "ss-see-model-space-views")), + "View title" => Some(("view", "ss-view-title")), + "Add..." => Some(("view", "ss-add-x")), + "The following blocks are associated with this sheet set:" => Some(("view", "ss-blocks-associated")), + "Enter the drawing file name:" => Some(("view", "ss-enter-drawing-file-name")), + "Enter the drawing file name." => Some(("view", "ss-enter-drawing-file-name-msg")), + "Select the drawing file as a block" => Some(("view", "ss-select-drawing-as-block")), + "Choose blocks in the drawing file:" => Some(("view", "ss-choose-blocks-in-drawing")), + "preview" => Some(("view", "ss-preview")), + "Open a sheet of this sheet set at its layout to place a view on it." => Some(("view", "ss-open-sheet-to-place")), + "Open the view's sheet at its layout to place a block on it." => Some(("view", "ss-open-view-sheet")), + "The sheet set has no label block for views." => Some(("view", "ss-no-label-block")), + "{}: no view to place." => Some(("view", "ss-no-view-to-place")), + "{}: cannot be read." => Some(("view", "ss-cannot-be-read")), "This layout is already part of a sheet set - not available for import." => Some(("view", "ss-layout-already-part")), "Warning: this layout may belong to another sheet set." => Some(("view", "ss-layout-may-belong")), "missing" => Some(("view", "ss-status-missing")), diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index a1f0ffc2e..bac7b6622 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -202,6 +202,9 @@ impl Scene { }; // Build rotation: canonical eye is +Z, rotate to eye_dir. cam.rotation = glam::Quat::from_rotation_arc(Vec3::Z, eye_dir); + // The view centre is an offset in view coordinates from the target. + let offset = cam.rotation * Vec3::new(view.center.x as f32, view.center.y as f32, 0.0); + cam.target += glam::DVec3::new(offset.x as f64, offset.y as f64, offset.z as f64); // Sync yaw/pitch from new rotation (for ViewCube). let pitch = eye_dir.z.clamp(-1.0, 1.0).asin(); let yaw = eye_dir.x.atan2(eye_dir.y); @@ -226,11 +229,8 @@ impl Scene { let height = cam.ortho_size() * 2.0; let width = height; // caller can adjust; rough square let mut view = codec::tables::View::new(name); - view.center = Vector3 { - x: cam.target.x as f64, - y: cam.target.y as f64, - z: 0.0, - }; + // The centre is in view (DCS) coordinates, relative to the target. + view.center = Vector3 { x: 0.0, y: 0.0, z: 0.0 }; view.target = Vector3 { x: cam.target.x as f64, y: cam.target.y as f64, diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index 418e9efd9..15bdec0c3 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -74,6 +74,36 @@ pub struct SheetSetManager { pub locations: Vec<(String, String, Vec)>, /// Drawings this application holds `.dwl` / `.dwl2` lock files for. pub locks: HashSet, + /// Model Views: drawings shown open (path keys) and their named model + /// views, read when a drawing is opened in the tree. + pub open_drawings: HashSet, + pub model_views: std::collections::HashMap>, + /// What the pending insertion point places. + pub placing: Option, + /// A named view to show once the sheet drawing being opened is up. + pub pending_view: Option, +} + +/// What a sheet set point pick places on the current sheet. +#[derive(Debug, Clone)] +pub enum Placing { + /// Place on Sheet: a model drawing and its named view (`None`: the + /// drawing's extents). + View { set: usize, sheet: String, drawing: String, view: Option }, + /// A callout or view label block (file, block name) for sheet view `view`. + Block { set: usize, sheet: String, view: String, file: String, name: String }, +} + +/// Commands of a sheet view row. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ViewAction { + Display, + Rename, + /// Set the view's category. + Category(String), + /// Place a callout block (its id in the set's callout blocks). + Callout(String), + Label, } /// A sheet whose drawing is missing, or open (a `.dwl` lock file, or a tab here). @@ -137,6 +167,21 @@ pub enum SheetSetMsg { LocationPicked(Option), RemoveLocation(String), OpenDrawing(String), + /// Model Views: open / fold a drawing (its named views), See Model Space + /// Views, Place on Sheet (drawing, named view). + ToggleDrawing(String), + SeeViews(String), + PlaceOnSheet(String, Option), + /// Sheet Views: a view row's command. + ViewMenu(String, ViewAction), + /// List of Blocks / Select Block. + BlockListSelect(usize), + BlockListAdd, + BlockListDelete, + SelectBlockBrowse, + SelectBlockPicked(Option), + SelectBlockWhole(bool), + SelectBlockCheck(usize, bool), /// A `.dst` picked by Open (or given by automation). OpenPicked(Option), /// A drawing picked by Import Layout as Sheet. @@ -288,6 +333,8 @@ pub enum FormKind { NewSubset, Rename, ImportLayout, + /// Rename & Renumber View. + RenameView, } /// One layout of Import Layouts as Sheets. @@ -347,8 +394,29 @@ pub struct Category { /// `None` creates a category. pub id: Option, pub name: String, - /// Callout block id, label, used. + /// Callout block id (`new:file|name|handle` until saved), label, used. pub blocks: Vec<(String, String, bool)>, + /// Callout blocks deleted in List of Blocks. + pub removed: Vec, + pub error: Option, +} + +/// List of Blocks: the set's callout blocks (id, label). +#[derive(Debug, Clone)] +pub struct BlockList { + pub category: Box, + pub blocks: Vec<(String, String)>, + pub selected: Option, +} + +/// Select Block: a drawing, taken whole or by its named blocks. +#[derive(Debug, Clone)] +pub struct SelectBlock { + pub list: Box, + pub file: String, + pub whole: bool, + /// Block name, handle, checked. + pub names: Vec<(String, String, bool)>, pub error: Option, } @@ -361,6 +429,8 @@ pub enum SsDialog { Confirm(usize, String, String), Template(TemplatePick), Category(Category), + BlockList(BlockList), + SelectBlock(SelectBlock), } impl SsDialog { @@ -373,7 +443,10 @@ impl SsDialog { FormKind::NewSubset => t!("Subset Properties").into_owned(), FormKind::Rename => t!("Rename & Renumber Sheet").into_owned(), FormKind::ImportLayout => t!("Import Layouts as Sheets").into_owned(), + FormKind::RenameView => t!("Rename & Renumber View").into_owned(), }, + SsDialog::BlockList(_) => t!("List of Blocks").into_owned(), + SsDialog::SelectBlock(_) => t!("Select Block").into_owned(), SsDialog::Confirm(..) => t!("Remove Sheets").into_owned(), SsDialog::Template(_) => t!("Select Layout as Sheet Template").into_owned(), SsDialog::Category(_) => t!("View Category").into_owned(), @@ -489,11 +562,21 @@ fn plain_row<'a>( depth: u16, icon: &'static [u8], label: String, + fold: Option<(bool, Message)>, double: Option, - menu: Vec<(&'static str, Option)>, + menu: Vec<(String, Option)>, ) -> Element<'a, Message> { + let arrow: Element<'a, Message> = match fold { + Some((open, m)) => button(if open { crate::ui::icons::themed_arrow_down(10.0) } else { crate::ui::icons::themed_arrow_right(10.0) }) + .on_press(m) + .style(button::text) + .padding(2) + .into(), + None => Space::new().width(14).into(), + }; let cells = row![ - Space::new().width(Length::Fixed(f32::from(depth) * 16.0 + 14.0)), + Space::new().width(Length::Fixed(f32::from(depth) * 16.0)), + arrow, crate::ui::icons::semantic(icon, 14.0), text(label).size(12).width(Fill), ] @@ -509,11 +592,11 @@ fn plain_row<'a>( iced_aw::ContextMenu::new(area, move || { let items: Vec> = menu .iter() - .map(|(label, m)| if label.is_empty() { menu_separator() } else { menu_entry(t!(*label).into_owned(), m.clone()) }) + .map(|(label, m)| if label.is_empty() { menu_separator() } else { menu_entry(label.clone(), m.clone()) }) .collect(); container(iced::widget::Column::with_children(items).spacing(1)) .padding(4) - .width(Length::Fixed(220.0)) + .width(Length::Fixed(240.0)) .style(|theme: &Theme| container::Style { background: Some(Background::Color(theme.palette().background.weak.color)), border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() }, @@ -524,6 +607,11 @@ fn plain_row<'a>( .into() } +/// A menu item: translated label and message (an empty label is a separator). +fn mi(label: &str, m: Option) -> (String, Option) { + (if label.is_empty() { String::new() } else { t!(label).into_owned() }, m) +} + fn tree_row<'a>( state: &SheetSetManager, el: &SsElement, @@ -598,67 +686,71 @@ fn push_tree<'a>( } } -/// A sheet's views (`AcSmSheetView`): label and the category it references. -fn views_of(sheet: &SsElement) -> Vec<(String, String)> { - sheet - .named("SheetViews") - .map(|v| v.children.iter().filter(|c| c.name == "AcSmSheetView").collect::>()) - .unwrap_or_default() - .into_iter() - .map(|v| { - let number = v.prop("Number").unwrap_or(""); - let title = v.prop("Title").unwrap_or(""); - let label = if number.is_empty() { title.to_string() } else { format!("{number} - {title}") }; - let category = v.named("Category").and_then(|c| c.prop("ReferencedObject")).unwrap_or("").to_string(); - (label, category) - }) - .collect() +/// A sheet view row and its menu: Display, Rename & Renumber, Set category +/// (the categories as sub-items), Place Callout Block (the set's callout +/// blocks as sub-items) and Place View Label Block. +fn view_row<'a>(db: &SheetSetDatabase, view: &SsElement, depth: u16) -> Element<'a, Message> { + let id = view.id().to_string(); + let act = |a: ViewAction| Some(msg(SheetSetMsg::ViewMenu(id.clone(), a))); + let mut menu = vec![mi("Display", act(ViewAction::Display)), mi("Rename & Renumber...", act(ViewAction::Rename)), mi("", None)]; + menu.push((format!("{} ▸", t!("Set category")), None)); + for c in db.view_categories() { + menu.push((format!(" {}", c.prop("Name").unwrap_or("")), act(ViewAction::Category(c.id().to_string())))); + } + menu.push((format!("{} ▸", t!("Place Callout Block")), None)); + for b in db.callout_blocks() { + menu.push((format!(" {}", b.prop("Name").unwrap_or("")), act(ViewAction::Callout(b.id().to_string())))); + } + menu.push(mi("Place View Label Block", act(ViewAction::Label))); + plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu) } /// The Sheet Views page: the set, then its view categories (or sheets) and their views. fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec> { - let mut rows = vec![plain_row( - 0, - SET_ICON, - db.name().to_string(), - None, - vec![("New View Category...", Some(msg(SheetSetMsg::NewCategory)))], - )]; + let new_category = || mi("New View Category...", Some(msg(SheetSetMsg::NewCategory))); + let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()])]; + let views: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect(); if state.by_category { - let views: Vec<(String, String)> = db.sheets().into_iter().flat_map(views_of).collect(); - for c in db.view_categories() { + let named = db.view_categories(); + // Views of the unnamed (default) category sit directly under the set. + for v in views.iter().filter(|v| !SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))) { + rows.push(view_row(db, v, 1)); + } + for c in named { let id = c.id().to_string(); rows.push(plain_row( 1, crate::ui::icons::FOLDER_OPEN, c.prop("Name").unwrap_or("").to_string(), + None, Some(msg(SheetSetMsg::CategoryProperties(id.clone()))), vec![ - ("New View Category...", Some(msg(SheetSetMsg::NewCategory))), - ("Properties...", Some(msg(SheetSetMsg::CategoryProperties(id.clone())))), - ("Remove", Some(msg(SheetSetMsg::CategoryRemove(id.clone())))), + new_category(), + mi("Properties...", Some(msg(SheetSetMsg::CategoryProperties(id.clone())))), + mi("Remove", Some(msg(SheetSetMsg::CategoryRemove(id.clone())))), ], )); - for (label, _) in views.iter().filter(|(_, cat)| *cat == id) { - rows.push(plain_row(2, crate::ui::icons::DOC, label.clone(), None, vec![])); + for v in views.iter().filter(|v| SheetSetDatabase::view_category_of(v) == Some(id.as_str())) { + rows.push(view_row(db, v, 2)); } } } else { for sheet in db.sheets() { - let views = views_of(sheet); + let views = db.sheet_views(sheet); if views.is_empty() { continue; } - rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, vec![])); - for (label, _) in views { - rows.push(plain_row(2, crate::ui::icons::DOC, label, None, vec![])); + rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, None, vec![])); + for v in views { + rows.push(view_row(db, v, 2)); } } } rows } -/// The Model Views page: each location folder and its drawings. +/// The Model Views page: each location folder, its drawings and, for an +/// opened drawing, its named model views. fn model_views_rows<'a>(state: &SheetSetManager) -> Vec> { let mut rows = Vec::new(); // A location shows relative to the set's folder when it can. @@ -670,20 +762,45 @@ fn model_views_rows<'a>(state: &SheetSetManager) -> Vec> { crate::ui::icons::FOLDER_OPEN, label, None, + None, vec![ - ("Add New Location...", Some(msg(SheetSetMsg::AddLocation))), - ("Remove Location", Some(msg(SheetSetMsg::RemoveLocation(id.clone())))), + mi("Add New Location...", Some(msg(SheetSetMsg::AddLocation))), + mi("Remove Location", Some(msg(SheetSetMsg::RemoveLocation(id.clone())))), ], )); for file in files { let path = std::path::Path::new(folder).join(file).to_string_lossy().to_string(); + let key = ss::path_key(&path); + let open = state.open_drawings.contains(&key); rows.push(plain_row( 1, crate::ui::icons::DOC, file.clone(), + Some((open, msg(SheetSetMsg::ToggleDrawing(path.clone())))), Some(msg(SheetSetMsg::OpenDrawing(path.clone()))), - vec![("Open", Some(msg(SheetSetMsg::OpenDrawing(path))))], + vec![ + mi("Open", Some(msg(SheetSetMsg::OpenDrawing(path.clone())))), + mi("", None), + mi("Place on Sheet", Some(msg(SheetSetMsg::PlaceOnSheet(path.clone(), None)))), + mi("See Model Space Views", Some(msg(SheetSetMsg::SeeViews(path.clone())))), + ], )); + if open { + for v in state.model_views.get(&key).cloned().unwrap_or_default() { + rows.push(plain_row( + 2, + crate::ui::icons::DOC, + v.clone(), + None, + None, + vec![ + mi("Open", Some(msg(SheetSetMsg::OpenDrawing(path.clone())))), + mi("", None), + mi("Place on Sheet", Some(msg(SheetSetMsg::PlaceOnSheet(path.clone(), Some(v))))), + ], + )); + } + } } } rows.push( @@ -1270,6 +1387,17 @@ fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { ), ] } + FormKind::RenameView => vec![card( + "View", + row![ + text(t!("Number").into_owned()).size(12).width(Length::Fixed(70.0)), + container(input(&f.number, FieldId::Number)).width(Length::Fixed(90.0)), + text(t!("View title").into_owned()).size(12), + input(&f.title, FieldId::Title), + ] + .spacing(8) + .align_y(iced::Center), + )], FormKind::ImportLayout => { let head = row![ Space::new().width(22), @@ -1335,7 +1463,7 @@ fn form_view<'a>(f: &'a Form) -> Element<'a, Message> { col = col.push(e); } let foot = match f.kind { - FormKind::Rename => footer( + FormKind::Rename | FormKind::RenameView => footer( vec![ button(text(format!("‹ {}", t!("Previous"))).size(12)).on_press(msg(SheetSetMsg::Previous)).style(button_style(false)).padding([5, 12]).into(), button(text(format!("{} ›", t!("Next"))).size(12)).on_press(msg(SheetSetMsg::Next)).style(button_style(false)).padding([5, 12]).into(), @@ -1409,6 +1537,97 @@ fn category_view<'a>(c: &'a Category) -> Element<'a, Message> { col.push(ok_cancel("OK")).into() } +fn preview_box<'a>() -> Element<'a, Message> { + container(text(t!("preview").into_owned()).size(11).style(muted_style)) + .center_x(Fill) + .center_y(Fill) + .style(card_style) + .into() +} + +fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> { + let rows = b.blocks.iter().enumerate().map(|(i, (_, label))| { + button(text(label.clone()).size(12)) + .on_press(msg(SheetSetMsg::BlockListSelect(i))) + .style(button_style(b.selected == Some(i))) + .padding([3, 8]) + .width(Fill) + .into() + }); + let side = column![ + button(text(t!("Add...").into_owned()).size(12).center().width(Fill)) + .on_press(msg(SheetSetMsg::BlockListAdd)) + .style(button_style(false)) + .padding([5, 12]) + .width(Fill), + button(text(t!("Delete").into_owned()).size(12).center().width(Fill)) + .on_press_maybe(b.selected.map(|_| msg(SheetSetMsg::BlockListDelete))) + .style(button_style(false)) + .padding([5, 12]) + .width(Fill), + preview_box(), + ] + .spacing(6) + .width(Length::Fixed(130.0)); + column![ + card( + "Blocks", + column![ + text(t!("The following blocks are associated with this sheet set:").into_owned()).size(12), + row![ + container(scrollable(iced::widget::Column::with_children(rows.collect::>()).spacing(1))) + .padding(4) + .height(Length::Fixed(150.0)) + .width(Fill) + .style(card_style), + side, + ] + .spacing(10) + .height(Length::Fixed(150.0)), + ] + .spacing(8) + ), + ok_cancel("OK"), + ] + .spacing(10) + .into() +} + +fn select_block_view<'a>(b: &'a SelectBlock) -> Element<'a, Message> { + let names = b.names.iter().enumerate().map(|(i, (name, _, on))| { + let c = checkbox(*on).label(name.clone()).text_size(12).size(14); + if b.whole { c.into() } else { c.on_toggle(move |v| msg(SheetSetMsg::SelectBlockCheck(i, v))).into() } + }); + let radio = |on: bool, label: &str, whole: bool| { + checkbox(on).label(t!(label).into_owned()).text_size(12).size(14).on_toggle(move |_| msg(SheetSetMsg::SelectBlockWhole(whole))) + }; + let mut col = column![card( + "Drawing", + column![ + text(t!("Enter the drawing file name:").into_owned()).size(12), + row![read_only(&b.file), browse(SheetSetMsg::SelectBlockBrowse)].spacing(6).align_y(iced::Center), + radio(b.whole, "Select the drawing file as a block", true), + radio(!b.whole, "Choose blocks in the drawing file:", false), + row![ + Space::new().width(22), + container(scrollable(iced::widget::Column::with_children(names.collect::>()).spacing(4))) + .padding(6) + .height(Length::Fixed(110.0)) + .width(Fill) + .style(card_style), + container(preview_box()).width(Length::Fixed(130.0)).height(Length::Fixed(110.0)), + ] + .spacing(10), + ] + .spacing(8) + )] + .spacing(10); + if let Some(e) = error_band(&b.error) { + col = col.push(e); + } + col.push(ok_cancel("OK")).into() +} + fn confirm_view<'a>(question: &'a str) -> Element<'a, Message> { column![ row![ @@ -1439,6 +1658,8 @@ pub fn dialog_view<'a>(state: &'a SheetSetManager, sizing: crate::ui::modal::Mod Some(SsDialog::Confirm(_, _, q)) => confirm_view(q), Some(SsDialog::Template(t)) => template_view(t), Some(SsDialog::Category(c)) => category_view(c), + Some(SsDialog::BlockList(b)) => block_list_view(b), + Some(SsDialog::SelectBlock(b)) => select_block_view(b), None => Space::new().into(), }; container(body).padding([10, 12]).width(sizing.width).into() From a3c79be278a0eef7fbc66503e6a33ffc9c8c3f11 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:05:47 +0300 Subject: [PATCH 07/17] Sheet sets: fly-out submenus, block previews, window-aspect named views - Row menus open Set category / Place Callout Block as fly-out submenus (hover or right press; leftwards when the palette is docked right). - List of Blocks and Select Block show the Blocks palette's preview. - VIEW SAVE keeps the window's width/height ratio. - Placing view content in model space is refused with the reference message. - Drawings read for views, placement and blocks are cached by modification time. Co-Authored-By: Claude Opus 5.5 --- locales/ar-SA/opencadstudio.ftl | 2 + locales/bg-BG/opencadstudio.ftl | 2 + locales/cs-CZ/opencadstudio.ftl | 2 + locales/de-DE/opencadstudio.ftl | 2 + locales/el-GR/opencadstudio.ftl | 2 + locales/en-US/opencadstudio.ftl | 2 + locales/es-ES/opencadstudio.ftl | 2 + locales/fi-FI/opencadstudio.ftl | 2 + locales/fr-FR/opencadstudio.ftl | 2 + locales/hi-IN/opencadstudio.ftl | 2 + locales/hu-HU/opencadstudio.ftl | 2 + locales/it-IT/opencadstudio.ftl | 2 + locales/ja-JP/opencadstudio.ftl | 2 + locales/ko-KR/opencadstudio.ftl | 2 + locales/nl-NL/opencadstudio.ftl | 2 + locales/pl-PL/opencadstudio.ftl | 2 + locales/pt-BR/opencadstudio.ftl | 2 + locales/ru-RU/opencadstudio.ftl | 2 + locales/tr-TR/opencadstudio.ftl | 2 + locales/zh-CN/opencadstudio.ftl | 2 + locales/zh-TW/opencadstudio.ftl | 2 + src/app/commands/sheet_set.rs | 103 ++++++++++++++-- src/app/view/mod.rs | 2 +- src/locale_catalog.rs | 2 + src/scene/camera_ops.rs | 3 +- src/ui/window/block_palette.rs | 5 + src/ui/window/sheet_set.rs | 201 ++++++++++++++++++++++++-------- 27 files changed, 296 insertions(+), 62 deletions(-) diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index 41567568c..0ae5d73de 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -4407,6 +4407,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 6f7495bcd..ab15d932f 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index a58827e80..63e9ca38f 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -4392,6 +4392,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index 340317851..c79f391e1 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -4393,6 +4393,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index db6b247ef..39d72c969 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index 15e519cee..4128886b8 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -4396,6 +4396,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 0eaaea507..550e14aaa 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index b4ccc4c7d..0125df9df 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index 6fb8ac0b3..63df1df75 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index d80f62fb9..44b3dec61 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -4383,6 +4383,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index 695be5f54..ee1d0d144 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 508b9031d..ea2938074 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index cc0bfdc1a..d665753ca 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -4383,6 +4383,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index c5d43cada..4cea5fb8b 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index c3dd02ee0..105fa4833 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -4393,6 +4393,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index 6dac7e7c0..52584e53e 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -4394,6 +4394,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index e8980d72f..6e1b5d464 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -4395,6 +4395,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index 14816ca6b..3179ecc33 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -4396,6 +4396,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index fd7d85d19..6065f397f 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -4355,6 +4355,8 @@ view = .ss-new-sheet-location = Yeni sayfa konumu .ss-remove-subset = Alt Kümeyi Kaldır .ss-rename-subset = Alt Kümeyi Yeniden Adlandır... + .ss-content-model-space = İçerik model uzayına yerleştirilemez. Kağıt uzayına geçip yeniden deneyin. + .ss-cannot-read = okunamıyor .ss-rename-renumber-view = Görünümü Yeniden Adlandır ve Numaralandır .ss-list-of-blocks = Blok Listesi .ss-select-block = Blok Seç diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 6e84dd631..f6710359a 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -4378,6 +4378,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index c9066a585..c5c4e42e6 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -4390,6 +4390,8 @@ view = .ss-new-sheet-location = New sheet location .ss-remove-subset = Remove Subset .ss-rename-subset = Rename Subset... + .ss-content-model-space = Content cannot be placed into model space. Please switch to paper space and try again. + .ss-cannot-read = cannot be read .ss-rename-renumber-view = Rename & Renumber View .ss-list-of-blocks = List of Blocks .ss-select-block = Select Block diff --git a/src/app/commands/sheet_set.rs b/src/app/commands/sheet_set.rs index c14522311..1adc56b9f 100644 --- a/src/app/commands/sheet_set.rs +++ b/src/app/commands/sheet_set.rs @@ -705,13 +705,24 @@ impl OpenCADStudio { SheetSetMsg::AddBlocks => { if let Some(SsDialog::Category(c)) = self.sheet_set.dialog.take() { let blocks = c.blocks.iter().map(|(id, label, _)| (id.clone(), label.clone())).collect(); - self.sheet_set.dialog = Some(SsDialog::BlockList(BlockList { category: Box::new(c), blocks, selected: None })); + self.sheet_set.dialog = Some(SsDialog::BlockList(BlockList { category: Box::new(c), blocks, selected: None, preview: None })); } } SheetSetMsg::BlocksPicked(_) | SheetSetMsg::LocationPicked(None) => {} + SheetSetMsg::Submenu(key) => self.sheet_set.submenu = key, SheetSetMsg::BlockListSelect(k) => { + let target = match self.sheet_set.dialog.as_ref() { + Some(SsDialog::BlockList(b)) => b.blocks.get(k).and_then(|(id, _)| self.listed_block(b.category.set, id)), + _ => None, + }; + // A whole drawing taken as a block shows its model space. + let preview = target.and_then(|(file, name)| { + let whole = self.sheet_set.drawings.get(&file).is_some_and(|d| d.block_records.get(&name).is_none()); + self.block_preview(&file, if whole { "" } else { &name }) + }); if let Some(SsDialog::BlockList(b)) = self.sheet_set.dialog.as_mut() { b.selected = Some(k); + b.preview = preview; } } SheetSetMsg::BlockListDelete => { @@ -728,6 +739,7 @@ impl OpenCADStudio { file: String::new(), whole: true, names: Vec::new(), + preview: None, error: None, })); } @@ -748,15 +760,28 @@ impl OpenCADStudio { SheetSetMsg::SelectBlockPicked(None) => {} SheetSetMsg::SelectBlockPicked(Some(path)) => self.select_block_file(path), SheetSetMsg::SelectBlockWhole(v) => { + let file = match self.sheet_set.dialog.as_ref() { + Some(SsDialog::SelectBlock(b)) => b.file.clone(), + _ => String::new(), + }; + let preview = if v && !file.is_empty() { self.block_preview(&file, "") } else { None }; if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() { b.whole = v; + b.preview = preview; } } SheetSetMsg::SelectBlockCheck(k, v) => { + let picked = match self.sheet_set.dialog.as_ref() { + Some(SsDialog::SelectBlock(b)) => b.names.get(k).map(|n| (b.file.clone(), n.0.clone())), + _ => None, + }; + // The block just checked is previewed. + let preview = picked.and_then(|(file, name)| self.block_preview(&file, &name)); if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() { if let Some(n) = b.names.get_mut(k) { n.2 = v; } + b.preview = preview; } } SheetSetMsg::ToggleDrawing(path) => { @@ -771,7 +796,10 @@ impl OpenCADStudio { self.sheet_set.open_drawings.insert(ss::path_key(&path)); } SheetSetMsg::PlaceOnSheet(path, view) => self.start_place_view(path, view), - SheetSetMsg::ViewMenu(id, action) => return self.view_menu(id, action), + SheetSetMsg::ViewMenu(id, action) => { + self.sheet_set.submenu = None; + return self.view_menu(id, action); + } SheetSetMsg::AddLocation => { return Task::perform( async { @@ -1540,13 +1568,55 @@ impl OpenCADStudio { self.save_current_sheet_set(); } + /// A block preview (`name` empty: the drawing's model space), cached by + /// `file|name`; returns its key. + fn block_preview(&mut self, file: &str, name: &str) -> Option { + let key = format!("{}|{}", ss::path_key(file), name); + if self.sheet_set.previews.contains_key(&key) { + return Some(key); + } + let doc = self.sheet_set.drawings.get(file)?; + let mut scene = crate::scene::Scene::new(); + scene.document = (*doc).clone(); + let wires = if name.is_empty() { + let model = scene.document.header.model_space_block_handle; + let entities: Vec = scene + .document + .entities() + .filter(|e| e.common().owner_handle == model) + .filter(|e| !matches!(e, codec::EntityType::Block(_) | codec::EntityType::BlockEnd(_))) + .take(20_000) + .cloned() + .collect(); + scene.wires_for_entities(&entities) + } else { + scene.block_preview_wires(name) + }; + self.sheet_set.previews.insert(key.clone(), wires); + Some(key) + } + + /// The file and block name behind a List of Blocks id. + fn listed_block(&self, set: usize, id: &str) -> Option<(String, String)> { + if let Some(spec) = id.strip_prefix("new:") { + let mut p = spec.splitn(3, '|'); + return Some((p.next()?.to_string(), p.next()?.to_string())); + } + let db = self.sheet_set.sets.get(set)?; + let b = db.find(id)?; + Some((db.resolve_file(b), b.prop("Name").unwrap_or("").to_string())) + } + /// Named model views of a drawing (read once, when its row is opened). fn load_model_views(&mut self, path: &str) { let key = ss::path_key(path); if self.sheet_set.model_views.contains_key(&key) { return; } - let views = crate::io::load_file(Path::new(path)) + let views = self + .sheet_set + .drawings + .get(path) .map(|d| d.views.iter().filter(|v| !v.paper_space).map(|v| v.name.clone()).collect()) .unwrap_or_default(); self.sheet_set.model_views.insert(key, views); @@ -1589,13 +1659,20 @@ impl OpenCADStudio { /// Place on Sheet: only on a sheet of this set open at its layout. fn start_place_view(&mut self, drawing: String, view: Option) { + if self.tabs[self.active_tab].scene.current_layout.eq_ignore_ascii_case("Model") { + self.command_line + .push_error(crate::t!("Content cannot be placed into model space. Please switch to paper space and try again.").as_ref()); + return; + } let Some((set, sheet)) = self.current_sheet() else { self.command_line .push_error(crate::t!("Open a sheet of this sheet set at its layout to place a view on it.").as_ref()); return; }; - let size = crate::io::load_file(Path::new(&drawing)) - .ok() + let size = self + .sheet_set + .drawings + .get(&drawing) .and_then(|d| model_view(&d, view.as_deref())) .map(|v| (v.width, v.height)); let Some(size) = size else { @@ -1637,6 +1714,10 @@ impl OpenCADStudio { } self.save_current_sheet_set(); } + ViewAction::Callout(_) | ViewAction::Label if self.tabs[self.active_tab].scene.current_layout.eq_ignore_ascii_case("Model") => { + self.command_line + .push_error(crate::t!("Content cannot be placed into model space. Please switch to paper space and try again.").as_ref()); + } ViewAction::Callout(_) | ViewAction::Label if self.current_sheet().map(|s| s.1) != Some(sheet.clone()) => { self.command_line .push_error(crate::t!("Open the view's sheet at its layout to place a block on it.").as_ref()); @@ -1662,7 +1743,7 @@ impl OpenCADStudio { /// insert it at `at` with its attributes; their `?View.` / `?Sheet.` /// fields become the view's and sheet's navigation fields. fn insert_set_block(&mut self, i: usize, file: &str, name: &str, at: codec::types::Vector3, view: &str, sheet: &str, set: usize) { - let Ok(source) = crate::io::load_file(Path::new(file)) else { + let Some(source) = self.sheet_set.drawings.get(file) else { self.command_line.push_error(crate::tf!("{}: cannot be read.", file).as_ref()); return; }; @@ -1719,7 +1800,7 @@ impl OpenCADStudio { self.insert_set_block(i, &file, &name, Vector3::new(x, y, 0.0), &view, &sheet, set); } Placing::View { set, sheet, drawing, view } => { - let Some(mv) = crate::io::load_file(Path::new(&drawing)).ok().and_then(|d| model_view(&d, view.as_deref())) else { + let Some(mv) = self.sheet_set.drawings.get(&drawing).and_then(|d| model_view(&d, view.as_deref())) else { return; }; let title = view.clone().unwrap_or_else(|| { @@ -1791,7 +1872,8 @@ impl OpenCADStudio { /// Select Block: read a drawing's named blocks. fn select_block_file(&mut self, path: PathBuf) { - let names: Vec<(String, String, bool)> = match crate::io::load_file(&path) { + let file = path.to_string_lossy().to_string(); + let names: Vec<(String, String, bool)> = match self.sheet_set.drawings.get(&file).ok_or_else(|| crate::t!("cannot be read").into_owned()) { Ok(doc) => { let mut n: Vec<(String, String, bool)> = doc .block_records @@ -1807,9 +1889,12 @@ impl OpenCADStudio { return; } }; + // The whole drawing is the first preview. + let preview = self.block_preview(&file, ""); if let Some(SsDialog::SelectBlock(b)) = self.sheet_set.dialog.as_mut() { - b.file = path.to_string_lossy().to_string(); + b.file = file; b.names = names; + b.preview = preview; b.error = None; } } diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index f875ffd8f..b78547705 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -3041,7 +3041,7 @@ impl OpenCADStudio { auto_collapse, ), crate::ui::dock::PanelId::SheetSetManager => { - crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse) + crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse, side == crate::app::config::DockSide::Right) } }; let divider = dock_divider(id); diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index 204e15eec..6814ed061 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -50,6 +50,8 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "New sheet location" => Some(("view", "ss-new-sheet-location")), "Remove Subset" => Some(("view", "ss-remove-subset")), "Rename Subset..." => Some(("view", "ss-rename-subset")), + "Content cannot be placed into model space. Please switch to paper space and try again." => Some(("view", "ss-content-model-space")), + "cannot be read" => Some(("view", "ss-cannot-read")), "Rename & Renumber View" => Some(("view", "ss-rename-renumber-view")), "List of Blocks" => Some(("view", "ss-list-of-blocks")), "Select Block" => Some(("view", "ss-select-block")), diff --git a/src/scene/camera_ops.rs b/src/scene/camera_ops.rs index bac7b6622..b216219be 100644 --- a/src/scene/camera_ops.rs +++ b/src/scene/camera_ops.rs @@ -227,7 +227,8 @@ impl Scene { let cam = self.camera.borrow(); let eye_dir = cam.rotation * glam::Vec3::Z; let height = cam.ortho_size() * 2.0; - let width = height; // caller can adjust; rough square + // The window's shape: the last rendered aspect ratio. + let width = height * self.active_camera_aspect(); let mut view = codec::tables::View::new(name); // The centre is in view (DCS) coordinates, relative to the target. view.center = Vector3 { x: 0.0, y: 0.0, z: 0.0 }; diff --git a/src/ui/window/block_palette.rs b/src/ui/window/block_palette.rs index 17527437a..a84fda8e9 100644 --- a/src/ui/window/block_palette.rs +++ b/src/ui/window/block_palette.rs @@ -378,6 +378,11 @@ impl<'a> Program for BlockPreviewCanvas<'a> { } } +/// A block preview (the palette's canvas) for other dialogs. +pub(crate) fn preview_canvas<'a>(wires: &'a [WireModel], height: f32) -> Element<'a, Message> { + canvas(BlockPreviewCanvas { wires }).width(Fill).height(Length::Fixed(height)).into() +} + fn pm(m: BlockPaletteMsg) -> Message { Message::BlockPalette(m) } diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index 15bdec0c3..7f49066e1 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -82,6 +82,47 @@ pub struct SheetSetManager { pub placing: Option, /// A named view to show once the sheet drawing being opened is up. pub pending_view: Option, + /// The fly-out submenu open in a row menu (its key). + pub submenu: Option, + /// Block previews (`file|name`; no name: the whole drawing). + pub previews: std::collections::HashMap>, + /// Drawings read for Model Views, placement and blocks, by path key. + pub drawings: DrawingCache, +} + +/// Drawings read from disk with their modification time; a changed file is +/// read again. +#[derive(Default)] +pub struct DrawingCache(pub std::collections::HashMap)>); + +impl std::fmt::Debug for DrawingCache { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "DrawingCache({} drawings)", self.0.len()) + } +} + +impl DrawingCache { + /// The drawing at `path`, read again when its modification time changed. + pub fn get(&mut self, path: &str) -> Option> { + let modified = std::fs::metadata(path).and_then(|m| m.modified()).ok()?; + let key = ss::path_key(path); + if let Some((t, doc)) = self.0.get(&key) { + if *t == modified { + return Some(doc.clone()); + } + } + let doc = std::sync::Arc::new(crate::io::load_file(std::path::Path::new(path)).ok()?); + self.0.insert(key, (modified, doc.clone())); + Some(doc) + } +} + +/// A row menu item: an entry, a separator, or a fly-out submenu (label, key, items). +#[derive(Debug, Clone)] +pub enum Mi { + Entry(String, Option), + Sep, + Sub(String, String, Vec<(String, Option)>), } /// What a sheet set point pick places on the current sheet. @@ -176,6 +217,8 @@ pub enum SheetSetMsg { ViewMenu(String, ViewAction), /// List of Blocks / Select Block. BlockListSelect(usize), + /// A row menu's fly-out submenu opened (hovered) or closed. + Submenu(Option), BlockListAdd, BlockListDelete, SelectBlockBrowse, @@ -407,6 +450,8 @@ pub struct BlockList { pub category: Box, pub blocks: Vec<(String, String)>, pub selected: Option, + /// Preview of the selected block (`file|name`). + pub preview: Option, } /// Select Block: a drawing, taken whole or by its named blocks. @@ -417,6 +462,8 @@ pub struct SelectBlock { pub whole: bool, /// Block name, handle, checked. pub names: Vec<(String, String, bool)>, + /// Preview shown (`file|name`). + pub preview: Option, pub error: Option, } @@ -564,7 +611,8 @@ fn plain_row<'a>( label: String, fold: Option<(bool, Message)>, double: Option, - menu: Vec<(String, Option)>, + menu: Vec, + open_sub: Option<(String, bool)>, ) -> Element<'a, Message> { let arrow: Element<'a, Message> = match fold { Some((open, m)) => button(if open { crate::ui::icons::themed_arrow_down(10.0) } else { crate::ui::icons::themed_arrow_right(10.0) }) @@ -589,27 +637,72 @@ fn plain_row<'a>( if menu.is_empty() { return area.into(); } - iced_aw::ContextMenu::new(area, move || { - let items: Vec> = menu - .iter() - .map(|(label, m)| if label.is_empty() { menu_separator() } else { menu_entry(label.clone(), m.clone()) }) - .collect(); - container(iced::widget::Column::with_children(items).spacing(1)) - .padding(4) - .width(Length::Fixed(240.0)) - .style(|theme: &Theme| container::Style { - background: Some(Background::Color(theme.palette().background.weak.color)), - border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() }, - ..Default::default() - }) - .into() - }) - .into() + iced_aw::ContextMenu::new(area, move || menu_panel(&menu, open_sub.as_ref())).into() +} + +/// Height of a menu entry and of a separator (with the column spacing). +const MENU_ENTRY_H: f32 = 27.0; +const MENU_SEP_H: f32 = 2.0; + +fn menu_box<'b>(items: Vec>) -> Element<'b, Message> { + container(iced::widget::Column::with_children(items).spacing(1)) + .padding(4) + .width(Length::Fixed(240.0)) + .style(|theme: &Theme| container::Style { + background: Some(Background::Color(theme.palette().background.weak.color)), + border: Border { color: theme.palette().background.neutral.color, width: 1.0, radius: 4.0.into() }, + ..Default::default() + }) + .into() +} + +/// A row menu; hovering a submenu entry opens its items beside it (`open`: +/// key and whether the fly-out goes on the left). +fn menu_panel(menu: &[Mi], open: Option<&(String, bool)>) -> Element<'static, Message> { + let mut items: Vec> = Vec::new(); + let mut flyout: Option<(f32, Element<'static, Message>)> = None; + let mut y = 0.0; + for item in menu { + match item { + Mi::Sep => { + items.push(menu_separator()); + y += MENU_SEP_H; + } + Mi::Entry(label, m) => { + items.push(mouse_area(menu_entry(label.clone(), m.clone())).on_enter(msg(SheetSetMsg::Submenu(None))).into()); + y += MENU_ENTRY_H; + } + Mi::Sub(label, key, sub) => { + let open_here = open.is_some_and(|(k, _)| k == key); + // Hover (or a right press) opens the fly-out; a click closes the menu. + let entry = menu_entry(format!("{label} ▸"), Some(msg(SheetSetMsg::Submenu(None)))); + let open_msg = msg(SheetSetMsg::Submenu(Some(key.clone()))); + items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into()); + if open_here { + let subs = sub.iter().map(|(l, m)| menu_entry(l.clone(), m.clone())).collect(); + flyout = Some((y, menu_box(subs))); + } + y += MENU_ENTRY_H; + } + } + } + let main = menu_box(items); + match flyout { + Some((offset, sub)) => { + let sub = column![Space::new().height(Length::Fixed(offset)), sub]; + // A menu opened from a right-docked palette sits left of the cursor + // (the overlay flips it at the window edge); its fly-out opens to + // the left so the menu itself does not move. + // ponytail: side from the dock, not the menu's actual position. + if open.is_some_and(|(_, left)| *left) { row![sub, main] } else { row![main, sub] }.spacing(2).into() + } + None => main, + } } /// A menu item: translated label and message (an empty label is a separator). -fn mi(label: &str, m: Option) -> (String, Option) { - (if label.is_empty() { String::new() } else { t!(label).into_owned() }, m) +fn mi(label: &str, m: Option) -> Mi { + if label.is_empty() { Mi::Sep } else { Mi::Entry(t!(label).into_owned(), m) } } fn tree_row<'a>( @@ -689,32 +782,32 @@ fn push_tree<'a>( /// A sheet view row and its menu: Display, Rename & Renumber, Set category /// (the categories as sub-items), Place Callout Block (the set's callout /// blocks as sub-items) and Place View Label Block. -fn view_row<'a>(db: &SheetSetDatabase, view: &SsElement, depth: u16) -> Element<'a, Message> { +fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement, depth: u16, left: bool) -> Element<'a, Message> { let id = view.id().to_string(); let act = |a: ViewAction| Some(msg(SheetSetMsg::ViewMenu(id.clone(), a))); - let mut menu = vec![mi("Display", act(ViewAction::Display)), mi("Rename & Renumber...", act(ViewAction::Rename)), mi("", None)]; - menu.push((format!("{} ▸", t!("Set category")), None)); - for c in db.view_categories() { - menu.push((format!(" {}", c.prop("Name").unwrap_or("")), act(ViewAction::Category(c.id().to_string())))); - } - menu.push((format!("{} ▸", t!("Place Callout Block")), None)); - for b in db.callout_blocks() { - menu.push((format!(" {}", b.prop("Name").unwrap_or("")), act(ViewAction::Callout(b.id().to_string())))); - } - menu.push(mi("Place View Label Block", act(ViewAction::Label))); - plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu) + let categories = db.view_categories().iter().map(|c| (c.prop("Name").unwrap_or("").to_string(), act(ViewAction::Category(c.id().to_string())))).collect(); + let callouts = db.callout_blocks().iter().map(|b| (b.prop("Name").unwrap_or("").to_string(), act(ViewAction::Callout(b.id().to_string())))).collect(); + let menu = vec![ + mi("Display", act(ViewAction::Display)), + mi("Rename & Renumber...", act(ViewAction::Rename)), + Mi::Sep, + Mi::Sub(t!("Set category").into_owned(), format!("{id}|category"), categories), + Mi::Sub(t!("Place Callout Block").into_owned(), format!("{id}|callout"), callouts), + mi("Place View Label Block", act(ViewAction::Label)), + ]; + plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu, state.submenu.clone().map(|k| (k, left))) } /// The Sheet Views page: the set, then its view categories (or sheets) and their views. -fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec> { +fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bool) -> Vec> { let new_category = || mi("New View Category...", Some(msg(SheetSetMsg::NewCategory))); - let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()])]; + let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()], None)]; let views: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect(); if state.by_category { let named = db.view_categories(); // Views of the unnamed (default) category sit directly under the set. for v in views.iter().filter(|v| !SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))) { - rows.push(view_row(db, v, 1)); + rows.push(view_row(state, db, v, 1, left)); } for c in named { let id = c.id().to_string(); @@ -729,9 +822,10 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec(state: &SheetSetManager) -> Vec> { mi("Add New Location...", Some(msg(SheetSetMsg::AddLocation))), mi("Remove Location", Some(msg(SheetSetMsg::RemoveLocation(id.clone())))), ], + None, )); for file in files { let path = std::path::Path::new(folder).join(file).to_string_lossy().to_string(); @@ -784,6 +879,7 @@ fn model_views_rows<'a>(state: &SheetSetManager) -> Vec> { mi("Place on Sheet", Some(msg(SheetSetMsg::PlaceOnSheet(path.clone(), None)))), mi("See Model Space Views", Some(msg(SheetSetMsg::SeeViews(path.clone())))), ], + None, )); if open { for v in state.model_views.get(&key).cloned().unwrap_or_default() { @@ -798,6 +894,7 @@ fn model_views_rows<'a>(state: &SheetSetManager) -> Vec> { mi("", None), mi("Place on Sheet", Some(msg(SheetSetMsg::PlaceOnSheet(path.clone(), Some(v))))), ], + None, )); } } @@ -822,7 +919,8 @@ fn heading(label: &str) -> Element<'static, Message> { .into() } -pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> Element<'a, Message> { +/// `dock_right`: the palette is docked on the right (fly-outs open leftwards). +pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool, dock_right: bool) -> Element<'a, Message> { let title_bar = crate::ui::dock::title_bar(PanelId::SheetSetManager, t!("Sheet Set Manager").into_owned(), auto_collapse); let mut picks: Vec = @@ -888,7 +986,7 @@ pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> ] .spacing(4) .align_y(iced::Center); - column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db)).spacing(1)).height(Fill)] + column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db, dock_right)).spacing(1)).height(Fill)] .spacing(6) .into() } @@ -1537,15 +1635,16 @@ fn category_view<'a>(c: &'a Category) -> Element<'a, Message> { col.push(ok_cancel("OK")).into() } -fn preview_box<'a>() -> Element<'a, Message> { - container(text(t!("preview").into_owned()).size(11).style(muted_style)) - .center_x(Fill) - .center_y(Fill) - .style(card_style) - .into() +/// A block preview (the Blocks palette's), or the empty preview box. +fn preview_box<'a>(previews: &'a std::collections::HashMap>, key: Option<&String>, height: f32) -> Element<'a, Message> { + let inner: Element<'a, Message> = match key.and_then(|k| previews.get(k)) { + Some(w) if !w.is_empty() => crate::ui::window::block_palette::preview_canvas(w, height - 8.0), + _ => text(t!("preview").into_owned()).size(11).style(muted_style).into(), + }; + container(inner).center_x(Fill).center_y(Length::Fixed(height)).padding(4).style(card_style).into() } -fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> { +fn block_list_view<'a>(b: &'a BlockList, state: &'a SheetSetManager) -> Element<'a, Message> { let rows = b.blocks.iter().enumerate().map(|(i, (_, label))| { button(text(label.clone()).size(12)) .on_press(msg(SheetSetMsg::BlockListSelect(i))) @@ -1565,7 +1664,7 @@ fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> { .style(button_style(false)) .padding([5, 12]) .width(Fill), - preview_box(), + preview_box(&state.previews, b.preview.as_ref(), 84.0), ] .spacing(6) .width(Length::Fixed(130.0)); @@ -1593,7 +1692,7 @@ fn block_list_view<'a>(b: &'a BlockList) -> Element<'a, Message> { .into() } -fn select_block_view<'a>(b: &'a SelectBlock) -> Element<'a, Message> { +fn select_block_view<'a>(b: &'a SelectBlock, state: &'a SheetSetManager) -> Element<'a, Message> { let names = b.names.iter().enumerate().map(|(i, (name, _, on))| { let c = checkbox(*on).label(name.clone()).text_size(12).size(14); if b.whole { c.into() } else { c.on_toggle(move |v| msg(SheetSetMsg::SelectBlockCheck(i, v))).into() } @@ -1615,7 +1714,7 @@ fn select_block_view<'a>(b: &'a SelectBlock) -> Element<'a, Message> { .height(Length::Fixed(110.0)) .width(Fill) .style(card_style), - container(preview_box()).width(Length::Fixed(130.0)).height(Length::Fixed(110.0)), + container(preview_box(&state.previews, b.preview.as_ref(), 110.0)).width(Length::Fixed(130.0)), ] .spacing(10), ] @@ -1658,8 +1757,8 @@ pub fn dialog_view<'a>(state: &'a SheetSetManager, sizing: crate::ui::modal::Mod Some(SsDialog::Confirm(_, _, q)) => confirm_view(q), Some(SsDialog::Template(t)) => template_view(t), Some(SsDialog::Category(c)) => category_view(c), - Some(SsDialog::BlockList(b)) => block_list_view(b), - Some(SsDialog::SelectBlock(b)) => select_block_view(b), + Some(SsDialog::BlockList(b)) => block_list_view(b, state), + Some(SsDialog::SelectBlock(b)) => select_block_view(b, state), None => Space::new().into(), }; container(body).padding([10, 12]).width(sizing.width).into() From 582e0980d9b0e79b0e0e8670c588e03f6cb202db Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:23:08 +0300 Subject: [PATCH 08/17] Sheet set row menus: own fly-out menu widget The row menu is a small overlay widget: a click on a submenu entry opens its fly-out instead of closing the menu, and the fly-out is placed beside the menu where it actually landed (right when it fits, otherwise left), so the menu never moves. Co-Authored-By: Claude Opus 5.5 --- src/app/view/mod.rs | 2 +- src/ui/window/mod.rs | 1 + src/ui/window/row_menu.rs | 286 +++++++++++++++++++++++++++++++++++++ src/ui/window/sheet_set.rs | 68 ++++----- 4 files changed, 312 insertions(+), 45 deletions(-) create mode 100644 src/ui/window/row_menu.rs diff --git a/src/app/view/mod.rs b/src/app/view/mod.rs index b78547705..f875ffd8f 100644 --- a/src/app/view/mod.rs +++ b/src/app/view/mod.rs @@ -3041,7 +3041,7 @@ impl OpenCADStudio { auto_collapse, ), crate::ui::dock::PanelId::SheetSetManager => { - crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse, side == crate::app::config::DockSide::Right) + crate::ui::window::sheet_set::view(&self.sheet_set, width, auto_collapse) } }; let divider = dock_divider(id); diff --git a/src/ui/window/mod.rs b/src/ui/window/mod.rs index 7497baed3..fcd11afca 100644 --- a/src/ui/window/mod.rs +++ b/src/ui/window/mod.rs @@ -27,6 +27,7 @@ pub mod alias_editor; pub mod find_replace; pub mod named_parameters; pub mod pc_manager; +pub mod row_menu; pub mod sheet_set; pub mod pdf_dialogs; pub mod xref_attach; diff --git a/src/ui/window/row_menu.rs b/src/ui/window/row_menu.rs new file mode 100644 index 000000000..fc8ec518a --- /dev/null +++ b/src/ui/window/row_menu.rs @@ -0,0 +1,286 @@ +//! A right-click menu for palette rows with one level of fly-out submenus. +//! +//! The menu opens at the cursor (flipped at the window edges); an open fly-out +//! is laid out beside it — on the right when it fits, otherwise on the left — +//! so the menu itself never moves. Pressing a submenu entry keeps the menu open. + +use iced::advanced::layout::{self, Layout}; +use iced::advanced::widget::{self, tree, Tree, Widget}; +use iced::advanced::{mouse, overlay, renderer, Shell}; +use iced::{keyboard, Element, Event, Length, Point, Rectangle, Renderer, Size, Theme, Vector}; + +use crate::app::Message; + +const FLYOUT_GAP: f32 = 2.0; + +pub(crate) struct RowMenu<'a> { + underlay: Element<'a, Message>, + main: Element<'a, Message>, + /// Per entry of the menu's column: a submenu entry (pressing it keeps the menu open). + keep_open: Vec, + /// The open fly-out: its entry's index in the column, and its content. + flyout: Option<(usize, Element<'a, Message>)>, + /// Published when the menu closes with a fly-out open. + on_close: Message, +} + +#[derive(Default)] +struct State { + show: bool, + position: Point, +} + +impl<'a> RowMenu<'a> { + pub(crate) fn new( + underlay: impl Into>, + main: Element<'a, Message>, + keep_open: Vec, + flyout: Option<(usize, Element<'a, Message>)>, + on_close: Message, + ) -> Self { + Self { underlay: underlay.into(), main, keep_open, flyout, on_close } + } +} + +impl<'a> Widget for RowMenu<'a> { + fn size(&self) -> Size { + self.underlay.as_widget().size() + } + + fn layout(&mut self, tree: &mut Tree, renderer: &Renderer, limits: &layout::Limits) -> layout::Node { + self.underlay.as_widget_mut().layout(&mut tree.children[0], renderer, limits) + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Theme, + style: &renderer::Style, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + ) { + self.underlay.as_widget().draw(&tree.children[0], renderer, theme, style, layout, cursor, viewport); + } + + fn tag(&self) -> tree::Tag { + tree::Tag::of::() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn diff(&mut self, tree: &mut Tree) { + let mut refs: Vec<&mut Element<'a, Message>> = vec![&mut self.underlay, &mut self.main]; + if let Some((_, f)) = &mut self.flyout { + refs.push(f); + } + tree.diff_children(&mut refs); + } + + fn update( + &mut self, + tree: &mut Tree, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + shell: &mut Shell<'_, Message>, + viewport: &Rectangle, + ) { + if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right)) = event { + if let Some(p) = cursor.position_over(layout.bounds()) { + let state: &mut State = tree.state.downcast_mut(); + state.show = true; + state.position = p; + if self.flyout.is_some() { + shell.publish(self.on_close.clone()); + } + shell.capture_event(); + shell.request_redraw(); + return; + } + } + self.underlay + .as_widget_mut() + .update(&mut tree.children[0], event, layout, cursor, renderer, shell, viewport); + } + + fn mouse_interaction( + &self, + tree: &Tree, + layout: Layout<'_>, + cursor: mouse::Cursor, + viewport: &Rectangle, + renderer: &Renderer, + ) -> mouse::Interaction { + self.underlay.as_widget().mouse_interaction(&tree.children[0], layout, cursor, viewport, renderer) + } + + fn operate( + &mut self, + tree: &mut Tree, + layout: Layout<'_>, + renderer: &Renderer, + operation: &mut dyn widget::Operation, + ) { + self.underlay.as_widget_mut().operate(&mut tree.children[0], layout, renderer, operation); + } + + fn overlay<'b>( + &'b mut self, + tree: &'b mut Tree, + layout: Layout<'b>, + renderer: &Renderer, + viewport: &Rectangle, + translation: Vector, + ) -> Option> { + let state: &mut State = tree.state.downcast_mut(); + if !state.show { + return self.underlay.as_widget_mut().overlay(&mut tree.children[0], layout, renderer, viewport, translation); + } + let position = state.position + translation; + let (_, trees) = tree.children.split_at_mut(1); + let (main_tree, fly_tree) = trees.split_at_mut(1); + Some(overlay::Element::new(Box::new(MenuOverlay { + state, + position, + main: &mut self.main, + main_tree: &mut main_tree[0], + keep_open: &self.keep_open, + flyout: self.flyout.as_mut().map(|(i, f)| (*i, f)).zip(fly_tree.first_mut()), + on_close: &self.on_close, + }))) + } +} + +impl<'a> From> for Element<'a, Message> { + fn from(w: RowMenu<'a>) -> Self { + Element::new(w) + } +} + +struct MenuOverlay<'a, 'b> { + state: &'b mut State, + position: Point, + main: &'b mut Element<'a, Message>, + main_tree: &'b mut Tree, + keep_open: &'b [bool], + flyout: Option<((usize, &'b mut Element<'a, Message>), &'b mut Tree)>, + on_close: &'b Message, +} + +/// The entries of a menu box (container › column › entries). +fn entries(main: Layout<'_>) -> impl Iterator> { + main.children().next().into_iter().flat_map(|column| column.children()) +} + +impl MenuOverlay<'_, '_> { + fn close(&mut self, shell: &mut Shell<'_, Message>) { + self.state.show = false; + if self.flyout.is_some() { + shell.publish(self.on_close.clone()); + } + shell.request_redraw(); + } +} + +impl overlay::Overlay for MenuOverlay<'_, '_> { + fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node { + let limits = layout::Limits::new(Size::ZERO, bounds); + let main = self.main.as_widget_mut().layout(self.main_tree, renderer, &limits); + let size = main.size(); + // At the cursor, flipped to the other side at the window edges. + let mut p = self.position; + if p.x + size.width > bounds.width { + p.x = (p.x - size.width).max(0.0); + } + if p.y + size.height > bounds.height { + p.y = (p.y - size.height).max(0.0); + } + let main = main.move_to(p); + let mut nodes = Vec::with_capacity(2); + if let Some(((index, flyout), tree)) = self.flyout.as_mut() { + let node = flyout.as_widget_mut().layout(tree, renderer, &limits); + let fly = node.size(); + // Its first item level with the entry (both boxes have the same inset). + let entry_y = main.children().first().and_then(|column| column.children().get(*index)).map_or(0.0, |e| e.bounds().y); + let y = (p.y + entry_y).min(bounds.height - fly.height).max(0.0); + // On the right when it fits, otherwise on the left. + let right = p.x + size.width + FLYOUT_GAP; + let x = if right + fly.width <= bounds.width { right } else { (p.x - fly.width - FLYOUT_GAP).max(0.0) }; + nodes.push(main); + nodes.push(node.move_to(Point::new(x, y))); + } else { + nodes.push(main); + } + layout::Node::with_children(bounds, nodes) + } + + fn draw(&self, renderer: &mut Renderer, theme: &Theme, style: &renderer::Style, layout: Layout<'_>, cursor: mouse::Cursor) { + let mut children = layout.children(); + if let Some(main) = children.next() { + self.main.as_widget().draw(self.main_tree, renderer, theme, style, main, cursor, &layout.bounds()); + } + if let (Some(((_, flyout), tree)), Some(fly)) = (self.flyout.as_ref(), children.next()) { + flyout.as_widget().draw(tree, renderer, theme, style, fly, cursor, &layout.bounds()); + } + } + + fn update( + &mut self, + event: &Event, + layout: Layout<'_>, + cursor: mouse::Cursor, + renderer: &Renderer, + shell: &mut Shell<'_, Message>, + ) { + let mut children = layout.children(); + let Some(main) = children.next() else { return }; + let fly = children.next(); + let over = cursor.is_over(main.bounds()) || fly.is_some_and(|f| cursor.is_over(f.bounds())); + match event { + Event::Keyboard(keyboard::Event::KeyPressed { key: keyboard::Key::Named(keyboard::key::Named::Escape), .. }) => { + self.close(shell); + shell.capture_event(); + return; + } + Event::Mouse(mouse::Event::ButtonPressed(_)) if !over => { + self.close(shell); + return; + } + _ => {} + } + let viewport = layout.bounds(); + self.main.as_widget_mut().update(self.main_tree, event, main, cursor, renderer, shell, &viewport); + if let (Some(((_, flyout), tree)), Some(fly)) = (self.flyout.as_mut(), fly) { + flyout.as_widget_mut().update(tree, event, fly, cursor, renderer, shell, &viewport); + } + if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) = event { + // A chosen entry closes the menu; a submenu entry opens its fly-out instead. + let on_submenu = entries(main) + .zip(self.keep_open.iter()) + .any(|(e, keep)| *keep && cursor.is_over(e.bounds())); + if over && !on_submenu { + self.close(shell); + } + } + if over { + shell.capture_event(); + } + } + + fn mouse_interaction(&self, layout: Layout<'_>, cursor: mouse::Cursor, renderer: &Renderer) -> mouse::Interaction { + let mut children = layout.children(); + let main = children.next().map(|m| { + self.main.as_widget().mouse_interaction(self.main_tree, m, cursor, &m.bounds(), renderer) + }); + let fly = match (self.flyout.as_ref(), children.next()) { + (Some(((_, f), tree)), Some(l)) => Some(f.as_widget().mouse_interaction(tree, l, cursor, &l.bounds(), renderer)), + _ => None, + }; + fly.filter(|i| *i != mouse::Interaction::default()).or(main).unwrap_or_default() + } +} diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index 7f49066e1..72aabdc39 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -612,7 +612,7 @@ fn plain_row<'a>( fold: Option<(bool, Message)>, double: Option, menu: Vec, - open_sub: Option<(String, bool)>, + open_sub: Option, ) -> Element<'a, Message> { let arrow: Element<'a, Message> = match fold { Some((open, m)) => button(if open { crate::ui::icons::themed_arrow_down(10.0) } else { crate::ui::icons::themed_arrow_right(10.0) }) @@ -637,13 +637,10 @@ fn plain_row<'a>( if menu.is_empty() { return area.into(); } - iced_aw::ContextMenu::new(area, move || menu_panel(&menu, open_sub.as_ref())).into() + let (main, keep_open, flyout) = menu_panel(&menu, open_sub.as_deref()); + crate::ui::window::row_menu::RowMenu::new(area, main, keep_open, flyout, msg(SheetSetMsg::Submenu(None))).into() } -/// Height of a menu entry and of a separator (with the column spacing). -const MENU_ENTRY_H: f32 = 27.0; -const MENU_SEP_H: f32 = 2.0; - fn menu_box<'b>(items: Vec>) -> Element<'b, Message> { container(iced::widget::Column::with_children(items).spacing(1)) .padding(4) @@ -656,48 +653,32 @@ fn menu_box<'b>(items: Vec>) -> Element<'b, Message> { .into() } -/// A row menu; hovering a submenu entry opens its items beside it (`open`: -/// key and whether the fly-out goes on the left). -fn menu_panel(menu: &[Mi], open: Option<&(String, bool)>) -> Element<'static, Message> { +/// A row menu: the menu box, which of its entries open a fly-out, and the +/// open fly-out (its entry index and items). Hovering, pressing or right +/// pressing a submenu entry opens its fly-out. +fn menu_panel(menu: &[Mi], open: Option<&str>) -> (Element<'static, Message>, Vec, Option<(usize, Element<'static, Message>)>) { let mut items: Vec> = Vec::new(); - let mut flyout: Option<(f32, Element<'static, Message>)> = None; - let mut y = 0.0; + let mut keep_open = Vec::new(); + let mut flyout = None; for item in menu { match item { - Mi::Sep => { - items.push(menu_separator()); - y += MENU_SEP_H; - } + Mi::Sep => items.push(menu_separator()), Mi::Entry(label, m) => { items.push(mouse_area(menu_entry(label.clone(), m.clone())).on_enter(msg(SheetSetMsg::Submenu(None))).into()); - y += MENU_ENTRY_H; } Mi::Sub(label, key, sub) => { - let open_here = open.is_some_and(|(k, _)| k == key); - // Hover (or a right press) opens the fly-out; a click closes the menu. - let entry = menu_entry(format!("{label} ▸"), Some(msg(SheetSetMsg::Submenu(None)))); let open_msg = msg(SheetSetMsg::Submenu(Some(key.clone()))); - items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into()); - if open_here { + let entry = menu_entry(format!("{label} ▸"), Some(open_msg.clone())); + if open == Some(key.as_str()) { let subs = sub.iter().map(|(l, m)| menu_entry(l.clone(), m.clone())).collect(); - flyout = Some((y, menu_box(subs))); + flyout = Some((items.len(), menu_box(subs))); } - y += MENU_ENTRY_H; + items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into()); } } + keep_open.push(matches!(item, Mi::Sub(..))); } - let main = menu_box(items); - match flyout { - Some((offset, sub)) => { - let sub = column![Space::new().height(Length::Fixed(offset)), sub]; - // A menu opened from a right-docked palette sits left of the cursor - // (the overlay flips it at the window edge); its fly-out opens to - // the left so the menu itself does not move. - // ponytail: side from the dock, not the menu's actual position. - if open.is_some_and(|(_, left)| *left) { row![sub, main] } else { row![main, sub] }.spacing(2).into() - } - None => main, - } + (menu_box(items), keep_open, flyout) } /// A menu item: translated label and message (an empty label is a separator). @@ -782,7 +763,7 @@ fn push_tree<'a>( /// A sheet view row and its menu: Display, Rename & Renumber, Set category /// (the categories as sub-items), Place Callout Block (the set's callout /// blocks as sub-items) and Place View Label Block. -fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement, depth: u16, left: bool) -> Element<'a, Message> { +fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement, depth: u16) -> Element<'a, Message> { let id = view.id().to_string(); let act = |a: ViewAction| Some(msg(SheetSetMsg::ViewMenu(id.clone(), a))); let categories = db.view_categories().iter().map(|c| (c.prop("Name").unwrap_or("").to_string(), act(ViewAction::Category(c.id().to_string())))).collect(); @@ -795,11 +776,11 @@ fn view_row<'a>(state: &SheetSetManager, db: &SheetSetDatabase, view: &SsElement Mi::Sub(t!("Place Callout Block").into_owned(), format!("{id}|callout"), callouts), mi("Place View Label Block", act(ViewAction::Label)), ]; - plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu, state.submenu.clone().map(|k| (k, left))) + plain_row(depth, crate::ui::icons::DOC, ss::number_and_title(view), None, act(ViewAction::Display), menu, state.submenu.clone()) } /// The Sheet Views page: the set, then its view categories (or sheets) and their views. -fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bool) -> Vec> { +fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase) -> Vec> { let new_category = || mi("New View Category...", Some(msg(SheetSetMsg::NewCategory))); let mut rows = vec![plain_row(0, SET_ICON, db.name().to_string(), None, None, vec![new_category()], None)]; let views: Vec<&SsElement> = db.sheets().into_iter().flat_map(|s| db.sheet_views(s)).collect(); @@ -807,7 +788,7 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bo let named = db.view_categories(); // Views of the unnamed (default) category sit directly under the set. for v in views.iter().filter(|v| !SheetSetDatabase::view_category_of(v).is_some_and(|c| named.iter().any(|n| n.id() == c))) { - rows.push(view_row(state, db, v, 1, left)); + rows.push(view_row(state, db, v, 1)); } for c in named { let id = c.id().to_string(); @@ -825,7 +806,7 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bo None, )); for v in views.iter().filter(|v| SheetSetDatabase::view_category_of(v) == Some(id.as_str())) { - rows.push(view_row(state, db, v, 2, left)); + rows.push(view_row(state, db, v, 2)); } } } else { @@ -836,7 +817,7 @@ fn sheet_views_rows<'a>(state: &SheetSetManager, db: &SheetSetDatabase, left: bo } rows.push(plain_row(1, crate::ui::icons::DOC, ss::number_and_title(sheet), None, None, vec![], None)); for v in views { - rows.push(view_row(state, db, v, 2, left)); + rows.push(view_row(state, db, v, 2)); } } } @@ -919,8 +900,7 @@ fn heading(label: &str) -> Element<'static, Message> { .into() } -/// `dock_right`: the palette is docked on the right (fly-outs open leftwards). -pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool, dock_right: bool) -> Element<'a, Message> { +pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool) -> Element<'a, Message> { let title_bar = crate::ui::dock::title_bar(PanelId::SheetSetManager, t!("Sheet Set Manager").into_owned(), auto_collapse); let mut picks: Vec = @@ -986,7 +966,7 @@ pub fn view<'a>(state: &'a SheetSetManager, width: f32, auto_collapse: bool, doc ] .spacing(4) .align_y(iced::Center); - column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db, dock_right)).spacing(1)).height(Fill)] + column![header, scrollable(iced::widget::Column::with_children(sheet_views_rows(state, db)).spacing(1)).height(Fill)] .spacing(6) .into() } From 74df837d6ca56f104088c63f515e564d3b6ac5ec Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:35:49 +0300 Subject: [PATCH 09/17] Sheet set row menus: clicks on separators, gaps and padding keep the menu open Co-Authored-By: Claude Opus 5.5 --- src/ui/window/row_menu.rs | 14 ++++++++------ src/ui/window/sheet_set.rs | 4 ++-- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/ui/window/row_menu.rs b/src/ui/window/row_menu.rs index fc8ec518a..4f3f5a121 100644 --- a/src/ui/window/row_menu.rs +++ b/src/ui/window/row_menu.rs @@ -16,7 +16,7 @@ const FLYOUT_GAP: f32 = 2.0; pub(crate) struct RowMenu<'a> { underlay: Element<'a, Message>, main: Element<'a, Message>, - /// Per entry of the menu's column: a submenu entry (pressing it keeps the menu open). + /// Per child of the menu's column: a submenu entry or a separator (a click on it keeps the menu open). keep_open: Vec, /// The open fly-out: its entry's index in the column, and its content. flyout: Option<(usize, Element<'a, Message>)>, @@ -259,11 +259,13 @@ impl overlay::Overlay for MenuOverlay<'_, '_> { flyout.as_widget_mut().update(tree, event, fly, cursor, renderer, shell, &viewport); } if let Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) = event { - // A chosen entry closes the menu; a submenu entry opens its fly-out instead. - let on_submenu = entries(main) - .zip(self.keep_open.iter()) - .any(|(e, keep)| *keep && cursor.is_over(e.bounds())); - if over && !on_submenu { + // Only a chosen entry closes the menu: a submenu entry opens its + // fly-out, and separators, gaps and padding do nothing. + let chosen = match entries(main).zip(self.keep_open.iter()).find(|(e, _)| cursor.is_over(e.bounds())) { + Some((_, keep)) => !keep, + None => fly.is_some_and(|f| entries(f).any(|e| cursor.is_over(e.bounds()))), + }; + if chosen { self.close(shell); } } diff --git a/src/ui/window/sheet_set.rs b/src/ui/window/sheet_set.rs index 72aabdc39..d136db048 100644 --- a/src/ui/window/sheet_set.rs +++ b/src/ui/window/sheet_set.rs @@ -653,7 +653,7 @@ fn menu_box<'b>(items: Vec>) -> Element<'b, Message> { .into() } -/// A row menu: the menu box, which of its entries open a fly-out, and the +/// A row menu: the menu box, which children keep it open when clicked (submenu entries, separators), and the /// open fly-out (its entry index and items). Hovering, pressing or right /// pressing a submenu entry opens its fly-out. fn menu_panel(menu: &[Mi], open: Option<&str>) -> (Element<'static, Message>, Vec, Option<(usize, Element<'static, Message>)>) { @@ -676,7 +676,7 @@ fn menu_panel(menu: &[Mi], open: Option<&str>) -> (Element<'static, Message>, Ve items.push(mouse_area(entry).on_enter(open_msg.clone()).on_right_press(open_msg).into()); } } - keep_open.push(matches!(item, Mi::Sub(..))); + keep_open.push(matches!(item, Mi::Sub(..) | Mi::Sep)); } (menu_box(items), keep_open, flyout) } From 03e3632efd2ec1ff2b8594fa84143cf5a0d3f404 Mon Sep 17 00:00:00 2001 From: ramox81 <184937705+ramox81@users.noreply.github.com> Date: Mon, 5 Oct 2026 17:13:12 +0300 Subject: [PATCH 10/17] Sheet sets: fields evaluated when a sheet opens; VIEW WINDOW - A drawing's sheet set fields are refreshed when it opens with sets loaded. - VIEW WINDOW saves the plan view of a window. - opencadcodec: block-attribute fields use their block reference's layout. Co-Authored-By: Claude Opus 5.5 --- Cargo.lock | 2 +- Cargo.toml | 2 +- locales/ar-SA/opencadstudio.ftl | 1 + locales/bg-BG/opencadstudio.ftl | 1 + locales/cs-CZ/opencadstudio.ftl | 1 + locales/de-DE/opencadstudio.ftl | 1 + locales/el-GR/opencadstudio.ftl | 1 + locales/en-US/opencadstudio.ftl | 1 + locales/es-ES/opencadstudio.ftl | 1 + locales/fi-FI/opencadstudio.ftl | 1 + locales/fr-FR/opencadstudio.ftl | 1 + locales/hi-IN/opencadstudio.ftl | 1 + locales/hu-HU/opencadstudio.ftl | 1 + locales/it-IT/opencadstudio.ftl | 1 + locales/ja-JP/opencadstudio.ftl | 1 + locales/ko-KR/opencadstudio.ftl | 1 + locales/nl-NL/opencadstudio.ftl | 1 + locales/pl-PL/opencadstudio.ftl | 1 + locales/pt-BR/opencadstudio.ftl | 1 + locales/ru-RU/opencadstudio.ftl | 1 + locales/tr-TR/opencadstudio.ftl | 1 + locales/zh-CN/opencadstudio.ftl | 1 + locales/zh-TW/opencadstudio.ftl | 1 + src/app/command_driver/utilities.rs | 2 ++ src/app/commands/layerprops.rs | 23 +++++++++++++++++++++ src/app/commands/sheet_set.rs | 31 ++++++++++++++++++----------- src/locale_catalog.rs | 1 + 27 files changed, 68 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0566733cc..5c5c71d15 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=1ace49e#1ace49ea940cd4e1337befac1004e57a7d36015b" +source = "git+https://github.com/ramox81/cadcodec.git?rev=5f4653a#5f4653ada2008eb0db5917abb0cb68f489edecc0" dependencies = [ "ahash 0.8.12", "anyhow", diff --git a/Cargo.toml b/Cargo.toml index f8681b4b7..bb1de8109 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ block2 = "0.5" objc2-foundation = { version = "0.2", features = ["NSArray", "NSURL", "NSString", "NSObject", "NSNotification", "NSThread", "NSEnumerator"] } [patch."https://github.com/HakanSeven12/opencadcodec.git"] -opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "1ace49e" } +opencadcodec = { git = "https://github.com/ramox81/cadcodec.git", rev = "5f4653a" } [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 0ae5d73de..7147b91e3 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3273,6 +3273,7 @@ layers = .usage-view-list-view-save-name-view-restore-na = الاستخدام: VIEW LIST | VIEW SAVE | VIEW RESTORE | VIEW DELETE .usage-view-restore-name = الاستخدام: VIEW RESTORE .usage-view-save-name = الاستخدام: VIEW SAVE + .usage-view-window = Usage: VIEW WINDOW .usage-prefix-font-style-font-file = الاستخدام: __ocs_fmt_0__ FONT