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()