diff --git a/locales/ar-SA/opencadstudio.ftl b/locales/ar-SA/opencadstudio.ftl index f210ce5eb..a4c3d8d3d 100644 --- a/locales/ar-SA/opencadstudio.ftl +++ b/locales/ar-SA/opencadstudio.ftl @@ -3133,6 +3133,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT حدد كتلة ذات سمات: .annotation-object-scale = مقياس كائن التعليق التوضيحي .bedit-select-block-reference-to-edit = BEDIT حدد مرجع الكتلة لتحريره: + .bvstate-option = BVSTATE أدخل خيارًا [جديد/تعيين/إعادة تسمية/حذف] <تعيين>: + .bvstate-new-name = BVSTATE أدخل اسم حالة الرؤية الجديدة: + .bvstate-set = BVSTATE أدخل حالة الرؤية المراد جعلها الحالية <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE أدخل حالة الرؤية المراد إعادة تسميتها <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE أدخل الاسم الجديد: + .bvstate-delete = BVSTATE أدخل حالة الرؤية المراد حذفها: + .bvstate-not-found = لم يتم العثور على حالة الرؤية "__ocs_fmt_0__". + .bvstate-exists = حالة الرؤية "__ocs_fmt_0__" موجودة بالفعل. + .bvstate-last = لا يمكن حذف حالة الرؤية الوحيدة. + .bvstate-needs-bedit = __ocs_fmt_0__: افتح الكتلة أولاً في محرر الكتل (BEDIT). + .bvstate-current = حالة الرؤية الحالية: __ocs_fmt_0__ + .bvhide-select = حدد الكائنات المراد إخفاؤها: + .bvshow-select = حدد الكائنات المراد إظهارها: .arrayedit-select-array = ARRAYEDIT حدد المصفوفة: .arrayedit-options = ARRAYEDIT أدخل خيارًا [المصدر/eXit] : .arrayedit-not-an-array = ARRAYEDIT: الكائن المحدد ليس مصفوفة ترابطية. diff --git a/locales/bg-BG/opencadstudio.ftl b/locales/bg-BG/opencadstudio.ftl index 694d37d17..068d07c73 100644 --- a/locales/bg-BG/opencadstudio.ftl +++ b/locales/bg-BG/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Избери блок с атрибути: .annotation-object-scale = Мащаб на анотационен обект .bedit-select-block-reference-to-edit = BEDIT Избери препратка към блок за редактиране: + .bvstate-option = BVSTATE Въведете опция [Ново/Задай/Преименувай/Изтрий] <Задай>: + .bvstate-new-name = BVSTATE Въведете име на новото състояние на видимост: + .bvstate-set = BVSTATE Въведете състоянието на видимост, което да стане текущо <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Въведете състоянието на видимост за преименуване <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Въведете новото име: + .bvstate-delete = BVSTATE Въведете състоянието на видимост за изтриване: + .bvstate-not-found = Състоянието на видимост "__ocs_fmt_0__" не е намерено. + .bvstate-exists = Състоянието на видимост "__ocs_fmt_0__" вече съществува. + .bvstate-last = Единственото състояние на видимост не може да бъде изтрито. + .bvstate-needs-bedit = __ocs_fmt_0__: първо отворете блока в редактора на блокове (BEDIT). + .bvstate-current = Текущо състояние на видимост: __ocs_fmt_0__ + .bvhide-select = Изберете обекти за скриване: + .bvshow-select = Изберете обекти за показване: .arrayedit-select-array = ARRAYEDIT Изберете масив: .arrayedit-options = ARRAYEDIT Въведете опция [Източник/eXit] : .arrayedit-not-an-array = ARRAYEDIT: избраният обект не е асоциативен масив. diff --git a/locales/cs-CZ/opencadstudio.ftl b/locales/cs-CZ/opencadstudio.ftl index 55dc8ab97..11d89aeb8 100644 --- a/locales/cs-CZ/opencadstudio.ftl +++ b/locales/cs-CZ/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Vyberte blok s atributy: .annotation-object-scale = Měřítko objektu anotace .bedit-select-block-reference-to-edit = BEDIT Vyberte referenci bloku, kterou chcete upravit: + .bvstate-option = BVSTATE Zadejte možnost [Nový/Nastavit/Přejmenovat/Odstranit] : + .bvstate-new-name = BVSTATE Zadejte název nového stavu viditelnosti: + .bvstate-set = BVSTATE Zadejte stav viditelnosti, který se stane aktuálním <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Zadejte stav viditelnosti k přejmenování <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Zadejte nový název: + .bvstate-delete = BVSTATE Zadejte stav viditelnosti k odstranění: + .bvstate-not-found = Stav viditelnosti "__ocs_fmt_0__" nebyl nalezen. + .bvstate-exists = Stav viditelnosti "__ocs_fmt_0__" již existuje. + .bvstate-last = Jediný stav viditelnosti nelze odstranit. + .bvstate-needs-bedit = __ocs_fmt_0__: nejprve otevřete blok v editoru bloků (BEDIT). + .bvstate-current = Aktuální stav viditelnosti: __ocs_fmt_0__ + .bvhide-select = Vyberte objekty ke skrytí: + .bvshow-select = Vyberte objekty k zobrazení: .arrayedit-select-array = ARRAYEDIT Vyberte pole: .arrayedit-options = ARRAYEDIT Zadejte možnost [Zdroj/eXit] : .arrayedit-not-an-array = ARRAYEDIT: vybraný objekt není asociativní pole. diff --git a/locales/de-DE/opencadstudio.ftl b/locales/de-DE/opencadstudio.ftl index ef91c0ea0..d977c6fb8 100644 --- a/locales/de-DE/opencadstudio.ftl +++ b/locales/de-DE/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Block mit Attributen auswählen: .annotation-object-scale = Beschriftungsobjektmaßstab .bedit-select-block-reference-to-edit = BEDIT Blockreferenz zum Bearbeiten auswählen: + .bvstate-option = BVSTATE Option eingeben [Neu/Setzen/Umbenennen/Löschen] : + .bvstate-new-name = BVSTATE Namen des neuen Sichtbarkeitsstatus eingeben: + .bvstate-set = BVSTATE Aktuell zu setzenden Sichtbarkeitsstatus eingeben <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Umzubenennenden Sichtbarkeitsstatus eingeben <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Neuen Namen eingeben: + .bvstate-delete = BVSTATE Zu löschenden Sichtbarkeitsstatus eingeben: + .bvstate-not-found = Sichtbarkeitsstatus "__ocs_fmt_0__" nicht gefunden. + .bvstate-exists = Sichtbarkeitsstatus "__ocs_fmt_0__" existiert bereits. + .bvstate-last = Der einzige Sichtbarkeitsstatus kann nicht gelöscht werden. + .bvstate-needs-bedit = __ocs_fmt_0__: Öffnen Sie den Block zuerst im Blockeditor (BEDIT). + .bvstate-current = Aktueller Sichtbarkeitsstatus: __ocs_fmt_0__ + .bvhide-select = Auszublendende Objekte wählen: + .bvshow-select = Einzublendende Objekte wählen: .arrayedit-select-array = ARRAYEDIT Reihe wählen: .arrayedit-options = ARRAYEDIT Option eingeben [Quelle/eXit] : .arrayedit-not-an-array = ARRAYEDIT: das gewählte Objekt ist keine assoziative Reihe. diff --git a/locales/el-GR/opencadstudio.ftl b/locales/el-GR/opencadstudio.ftl index afa07b2fb..6355853a7 100644 --- a/locales/el-GR/opencadstudio.ftl +++ b/locales/el-GR/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Επιλέξτε μπλοκ με χαρακτηριστικά: .annotation-object-scale = Κλίμακα αντικειμένου επισημείωσης .bedit-select-block-reference-to-edit = BEDIT Επιλέξτε αναφορά μπλοκ προς επεξεργασία: + .bvstate-option = BVSTATE Δώστε επιλογή [Νέα/Ορισμός/Μετονομασία/Διαγραφή] <Ορισμός>: + .bvstate-new-name = BVSTATE Δώστε όνομα νέας κατάστασης ορατότητας: + .bvstate-set = BVSTATE Δώστε την κατάσταση ορατότητας που θα γίνει τρέχουσα <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Δώστε την κατάσταση ορατότητας προς μετονομασία <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Δώστε νέο όνομα: + .bvstate-delete = BVSTATE Δώστε την κατάσταση ορατότητας προς διαγραφή: + .bvstate-not-found = Η κατάσταση ορατότητας "__ocs_fmt_0__" δεν βρέθηκε. + .bvstate-exists = Η κατάσταση ορατότητας "__ocs_fmt_0__" υπάρχει ήδη. + .bvstate-last = Η μοναδική κατάσταση ορατότητας δεν μπορεί να διαγραφεί. + .bvstate-needs-bedit = __ocs_fmt_0__: ανοίξτε πρώτα το μπλοκ στον επεξεργαστή μπλοκ (BEDIT). + .bvstate-current = Τρέχουσα κατάσταση ορατότητας: __ocs_fmt_0__ + .bvhide-select = Επιλέξτε αντικείμενα για απόκρυψη: + .bvshow-select = Επιλέξτε αντικείμενα για εμφάνιση: .arrayedit-select-array = ARRAYEDIT Επιλέξτε διάταξη: .arrayedit-options = ARRAYEDIT Δώστε επιλογή [Πηγή/eXit] : .arrayedit-not-an-array = ARRAYEDIT: το επιλεγμένο αντικείμενο δεν είναι συσχετιστική διάταξη. diff --git a/locales/en-US/opencadstudio.ftl b/locales/en-US/opencadstudio.ftl index c0a15929d..cc3a6dd29 100644 --- a/locales/en-US/opencadstudio.ftl +++ b/locales/en-US/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Select block with attributes: .annotation-object-scale = Annotation Object Scale .bedit-select-block-reference-to-edit = BEDIT Select block reference to edit: + .bvstate-option = BVSTATE Enter an option [New/Set/Rename/Delete] : + .bvstate-new-name = BVSTATE Enter name of new visibility state: + .bvstate-set = BVSTATE Enter visibility state to make current <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Enter visibility state to rename <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Enter new name: + .bvstate-delete = BVSTATE Enter visibility state to delete: + .bvstate-not-found = Visibility state "__ocs_fmt_0__" not found. + .bvstate-exists = Visibility state "__ocs_fmt_0__" already exists. + .bvstate-last = The only visibility state cannot be deleted. + .bvstate-needs-bedit = __ocs_fmt_0__: open the block in the Block Editor first (BEDIT). + .bvstate-current = Current visibility state: __ocs_fmt_0__ + .bvhide-select = Select objects to hide: + .bvshow-select = Select objects to make visible: .arrayedit-select-array = ARRAYEDIT Select array: .arrayedit-options = ARRAYEDIT Enter an option [Source/eXit] : .arrayedit-not-an-array = ARRAYEDIT: the selected object is not an associative array. diff --git a/locales/es-ES/opencadstudio.ftl b/locales/es-ES/opencadstudio.ftl index 786b46eaf..24ab151af 100644 --- a/locales/es-ES/opencadstudio.ftl +++ b/locales/es-ES/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Seleccione un bloque con atributos: .annotation-object-scale = Escala del objeto anotativo .bedit-select-block-reference-to-edit = BEDIT Seleccione la referencia de bloque que desea editar: + .bvstate-option = BVSTATE Indique una opción [Nuevo/Establecer/Renombrar/Suprimir] : + .bvstate-new-name = BVSTATE Indique el nombre del nuevo estado de visibilidad: + .bvstate-set = BVSTATE Indique el estado de visibilidad que se hará actual <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Indique el estado de visibilidad que se renombrará <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Indique el nuevo nombre: + .bvstate-delete = BVSTATE Indique el estado de visibilidad que se suprimirá: + .bvstate-not-found = No se encuentra el estado de visibilidad "__ocs_fmt_0__". + .bvstate-exists = El estado de visibilidad "__ocs_fmt_0__" ya existe. + .bvstate-last = No se puede suprimir el único estado de visibilidad. + .bvstate-needs-bedit = __ocs_fmt_0__: abra primero el bloque en el Editor de bloques (BEDIT). + .bvstate-current = Estado de visibilidad actual: __ocs_fmt_0__ + .bvhide-select = Seleccione los objetos que se ocultarán: + .bvshow-select = Seleccione los objetos que se harán visibles: .arrayedit-select-array = ARRAYEDIT Seleccione la matriz: .arrayedit-options = ARRAYEDIT Indique una opción [Origen/eXit] : .arrayedit-not-an-array = ARRAYEDIT: el objeto seleccionado no es una matriz asociativa. diff --git a/locales/fi-FI/opencadstudio.ftl b/locales/fi-FI/opencadstudio.ftl index 5381c7037..ba3fd03be 100644 --- a/locales/fi-FI/opencadstudio.ftl +++ b/locales/fi-FI/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Valitse lohko määritteillä: .annotation-object-scale = Merkintäobjektin mittakaava .bedit-select-block-reference-to-edit = BEDIT Valitse muokattava lohkoviite: + .bvstate-option = BVSTATE Anna vaihtoehto [Uusi/Aseta/Nimeä uudelleen/Poista] : + .bvstate-new-name = BVSTATE Anna uuden näkyvyystilan nimi: + .bvstate-set = BVSTATE Anna nykyiseksi asetettava näkyvyystila <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Anna uudelleennimettävä näkyvyystila <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Anna uusi nimi: + .bvstate-delete = BVSTATE Anna poistettava näkyvyystila: + .bvstate-not-found = Näkyvyystilaa "__ocs_fmt_0__" ei löydy. + .bvstate-exists = Näkyvyystila "__ocs_fmt_0__" on jo olemassa. + .bvstate-last = Ainoaa näkyvyystilaa ei voi poistaa. + .bvstate-needs-bedit = __ocs_fmt_0__: avaa lohko ensin lohkoeditorissa (BEDIT). + .bvstate-current = Nykyinen näkyvyystila: __ocs_fmt_0__ + .bvhide-select = Valitse piilotettavat objektit: + .bvshow-select = Valitse näytettävät objektit: .arrayedit-select-array = ARRAYEDIT Valitse sarja: .arrayedit-options = ARRAYEDIT Anna vaihtoehto [Lähde/eXit] : .arrayedit-not-an-array = ARRAYEDIT: valittu objekti ei ole assosiatiivinen sarja. diff --git a/locales/fr-FR/opencadstudio.ftl b/locales/fr-FR/opencadstudio.ftl index b9f67f2da..3403ade30 100644 --- a/locales/fr-FR/opencadstudio.ftl +++ b/locales/fr-FR/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Sélectionner un bloc avec des attributs : .annotation-object-scale = Échelle d'objets d'annotation .bedit-select-block-reference-to-edit = BEDIT Sélectionnez la référence de bloc à modifier : + .bvstate-option = BVSTATE Entrez une option [Nouveau/Définir/Renommer/Supprimer] : + .bvstate-new-name = BVSTATE Entrez le nom du nouvel état de visibilité : + .bvstate-set = BVSTATE Entrez l'état de visibilité à rendre courant <__ocs_fmt_0__> : + .bvstate-rename-from = BVSTATE Entrez l'état de visibilité à renommer <__ocs_fmt_0__> : + .bvstate-rename-to = BVSTATE Entrez le nouveau nom : + .bvstate-delete = BVSTATE Entrez l'état de visibilité à supprimer : + .bvstate-not-found = État de visibilité "__ocs_fmt_0__" introuvable. + .bvstate-exists = L'état de visibilité "__ocs_fmt_0__" existe déjà. + .bvstate-last = Le seul état de visibilité ne peut pas être supprimé. + .bvstate-needs-bedit = __ocs_fmt_0__ : ouvrez d'abord le bloc dans l'éditeur de blocs (BEDIT). + .bvstate-current = État de visibilité courant : __ocs_fmt_0__ + .bvhide-select = Sélectionnez les objets à masquer : + .bvshow-select = Sélectionnez les objets à rendre visibles : .arrayedit-select-array = ARRAYEDIT Sélectionnez le réseau : .arrayedit-options = ARRAYEDIT Entrez une option [Source/eXit] : .arrayedit-not-an-array = ARRAYEDIT : l'objet sélectionné n'est pas un réseau associatif. diff --git a/locales/hi-IN/opencadstudio.ftl b/locales/hi-IN/opencadstudio.ftl index 1626cd702..4d7a49907 100644 --- a/locales/hi-IN/opencadstudio.ftl +++ b/locales/hi-IN/opencadstudio.ftl @@ -3109,6 +3109,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT विशेषताओं के साथ ब्लॉक का चयन करें: .annotation-object-scale = Annotation ऑब्जेक्ट स्केल .bedit-select-block-reference-to-edit = BEDIT संपादित करने के लिए ब्लॉक संदर्भ का चयन करें: + .bvstate-option = BVSTATE विकल्प दर्ज करें [नया/सेट/नाम बदलें/हटाएँ] <सेट>: + .bvstate-new-name = BVSTATE नई दृश्यता स्थिति का नाम दर्ज करें: + .bvstate-set = BVSTATE वर्तमान बनाने के लिए दृश्यता स्थिति दर्ज करें <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE नाम बदलने के लिए दृश्यता स्थिति दर्ज करें <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE नया नाम दर्ज करें: + .bvstate-delete = BVSTATE हटाने के लिए दृश्यता स्थिति दर्ज करें: + .bvstate-not-found = दृश्यता स्थिति "__ocs_fmt_0__" नहीं मिली। + .bvstate-exists = दृश्यता स्थिति "__ocs_fmt_0__" पहले से मौजूद है। + .bvstate-last = एकमात्र दृश्यता स्थिति हटाई नहीं जा सकती। + .bvstate-needs-bedit = __ocs_fmt_0__: पहले ब्लॉक को ब्लॉक एडिटर में खोलें (BEDIT)। + .bvstate-current = वर्तमान दृश्यता स्थिति: __ocs_fmt_0__ + .bvhide-select = छिपाने के लिए ऑब्जेक्ट चुनें: + .bvshow-select = दिखाने के लिए ऑब्जेक्ट चुनें: .arrayedit-select-array = ARRAYEDIT ऐरे चुनें: .arrayedit-options = ARRAYEDIT विकल्प दर्ज करें [स्रोत/eXit] : .arrayedit-not-an-array = ARRAYEDIT: चुना गया ऑब्जेक्ट सहयोगी ऐरे नहीं है। diff --git a/locales/hu-HU/opencadstudio.ftl b/locales/hu-HU/opencadstudio.ftl index fb17ef462..5e313d26b 100644 --- a/locales/hu-HU/opencadstudio.ftl +++ b/locales/hu-HU/opencadstudio.ftl @@ -3123,6 +3123,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Blokk kiválasztása attribútumokkal: .annotation-object-scale = Feliratozási objektum léptéke .bedit-select-block-reference-to-edit = BEDIT Válassza ki a szerkeszteni kívánt blokkreferenciát: + .bvstate-option = BVSTATE Adjon meg egy lehetőséget [Új/Beállítás/Átnevezés/Törlés] : + .bvstate-new-name = BVSTATE Adja meg az új láthatósági állapot nevét: + .bvstate-set = BVSTATE Adja meg az aktuálissá teendő láthatósági állapotot <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Adja meg az átnevezendő láthatósági állapotot <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Adja meg az új nevet: + .bvstate-delete = BVSTATE Adja meg a törlendő láthatósági állapotot: + .bvstate-not-found = "__ocs_fmt_0__" láthatósági állapot nem található. + .bvstate-exists = "__ocs_fmt_0__" láthatósági állapot már létezik. + .bvstate-last = Az egyetlen láthatósági állapot nem törölhető. + .bvstate-needs-bedit = __ocs_fmt_0__: előbb nyissa meg a blokkot a blokkszerkesztőben (BEDIT). + .bvstate-current = Aktuális láthatósági állapot: __ocs_fmt_0__ + .bvhide-select = Jelölje ki az elrejtendő objektumokat: + .bvshow-select = Jelölje ki a megjelenítendő objektumokat: .arrayedit-select-array = ARRAYEDIT Válassza ki a tömböt: .arrayedit-options = ARRAYEDIT Adjon meg egy lehetőséget [Forrás/eXit] : .arrayedit-not-an-array = ARRAYEDIT: a kijelölt objektum nem asszociatív tömb. diff --git a/locales/it-IT/opencadstudio.ftl b/locales/it-IT/opencadstudio.ftl index 85e49acad..e4840f666 100644 --- a/locales/it-IT/opencadstudio.ftl +++ b/locales/it-IT/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Seleziona blocco con attributi: .annotation-object-scale = Scala oggetto annotazione .bedit-select-block-reference-to-edit = BEDIT Selezionare il riferimento di blocco da modificare: + .bvstate-option = BVSTATE Immettere un'opzione [Nuovo/Imposta/Rinomina/Elimina] : + .bvstate-new-name = BVSTATE Immettere il nome del nuovo stato di visibilità: + .bvstate-set = BVSTATE Immettere lo stato di visibilità da rendere corrente <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Immettere lo stato di visibilità da rinominare <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Immettere il nuovo nome: + .bvstate-delete = BVSTATE Immettere lo stato di visibilità da eliminare: + .bvstate-not-found = Stato di visibilità "__ocs_fmt_0__" non trovato. + .bvstate-exists = Lo stato di visibilità "__ocs_fmt_0__" esiste già. + .bvstate-last = L'unico stato di visibilità non può essere eliminato. + .bvstate-needs-bedit = __ocs_fmt_0__: aprire prima il blocco nell'Editor blocchi (BEDIT). + .bvstate-current = Stato di visibilità corrente: __ocs_fmt_0__ + .bvhide-select = Selezionare gli oggetti da nascondere: + .bvshow-select = Selezionare gli oggetti da rendere visibili: .arrayedit-select-array = ARRAYEDIT Selezionare la serie: .arrayedit-options = ARRAYEDIT Immettere un'opzione [Origine/eXit] : .arrayedit-not-an-array = ARRAYEDIT: l'oggetto selezionato non è una serie associativa. diff --git a/locales/ja-JP/opencadstudio.ftl b/locales/ja-JP/opencadstudio.ftl index 72df97414..978be1188 100644 --- a/locales/ja-JP/opencadstudio.ftl +++ b/locales/ja-JP/opencadstudio.ftl @@ -3111,6 +3111,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT 属性付きブロックを選択: .annotation-object-scale = 注釈オブジェクト尺度 .bedit-select-block-reference-to-edit = BEDIT 編集するブロック参照を選択: + .bvstate-option = BVSTATE オプションを入力 [新規/設定/名前変更/削除] <設定>: + .bvstate-new-name = BVSTATE 新しい可視性状態の名前を入力: + .bvstate-set = BVSTATE 現在にする可視性状態を入力 <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE 名前を変更する可視性状態を入力 <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE 新しい名前を入力: + .bvstate-delete = BVSTATE 削除する可視性状態を入力: + .bvstate-not-found = 可視性状態「__ocs_fmt_0__」が見つかりません。 + .bvstate-exists = 可視性状態「__ocs_fmt_0__」は既に存在します。 + .bvstate-last = 唯一の可視性状態は削除できません。 + .bvstate-needs-bedit = __ocs_fmt_0__: 先にブロック エディタでブロックを開いてください (BEDIT)。 + .bvstate-current = 現在の可視性状態: __ocs_fmt_0__ + .bvhide-select = 非表示にするオブジェクトを選択: + .bvshow-select = 表示するオブジェクトを選択: .arrayedit-select-array = ARRAYEDIT 配列複写を選択: .arrayedit-options = ARRAYEDIT オプションを入力 [元/eXit] : .arrayedit-not-an-array = ARRAYEDIT: 選択したオブジェクトは自動調整配列複写ではありません。 diff --git a/locales/ko-KR/opencadstudio.ftl b/locales/ko-KR/opencadstudio.ftl index 9899dbd75..a02a5f742 100644 --- a/locales/ko-KR/opencadstudio.ftl +++ b/locales/ko-KR/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT 속성이 있는 블록 선택: .annotation-object-scale = 주석 객체 축척 .bedit-select-block-reference-to-edit = BEDIT 편집할 블록 참조 선택: + .bvstate-option = BVSTATE 옵션 입력 [새로 만들기/설정/이름 바꾸기/삭제] <설정>: + .bvstate-new-name = BVSTATE 새 가시성 상태의 이름 입력: + .bvstate-set = BVSTATE 현재로 설정할 가시성 상태 입력 <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE 이름을 바꿀 가시성 상태 입력 <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE 새 이름 입력: + .bvstate-delete = BVSTATE 삭제할 가시성 상태 입력: + .bvstate-not-found = 가시성 상태 "__ocs_fmt_0__"을(를) 찾을 수 없습니다. + .bvstate-exists = 가시성 상태 "__ocs_fmt_0__"이(가) 이미 있습니다. + .bvstate-last = 유일한 가시성 상태는 삭제할 수 없습니다. + .bvstate-needs-bedit = __ocs_fmt_0__: 먼저 블록 편집기에서 블록을 여십시오(BEDIT). + .bvstate-current = 현재 가시성 상태: __ocs_fmt_0__ + .bvhide-select = 숨길 객체 선택: + .bvshow-select = 표시할 객체 선택: .arrayedit-select-array = ARRAYEDIT 배열 선택: .arrayedit-options = ARRAYEDIT 옵션 입력 [원본/eXit] : .arrayedit-not-an-array = ARRAYEDIT: 선택한 객체는 연관 배열이 아닙니다. diff --git a/locales/lo-LA/opencadstudio.ftl b/locales/lo-LA/opencadstudio.ftl index 3b9358f63..5e1b081d3 100644 --- a/locales/lo-LA/opencadstudio.ftl +++ b/locales/lo-LA/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT ເລືອກບລັອກທີ່ມີຄຸນລັກສະນະ: .annotation-object-scale = ມາດຕາສ່ວນວັດຖຸຄຳອະທິບາຍ .bedit-select-block-reference-to-edit = BEDIT ເລືອກການອ້າງອີງບລັອກທີ່ຈະແກ້ໄຂ: + .bvstate-option = BVSTATE ປ້ອນທາງເລືອກ [ໃໝ່/ຕັ້ງ/ປ່ຽນຊື່/ລຶບ] <ຕັ້ງ>: + .bvstate-new-name = BVSTATE ປ້ອນຊື່ສະຖານະການເບິ່ງເຫັນໃໝ່: + .bvstate-set = BVSTATE ປ້ອນສະຖານະການເບິ່ງເຫັນທີ່ຈະເປັນປັດຈຸບັນ <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE ປ້ອນສະຖານະການເບິ່ງເຫັນທີ່ຈະປ່ຽນຊື່ <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE ປ້ອນຊື່ໃໝ່: + .bvstate-delete = BVSTATE ປ້ອນສະຖານະການເບິ່ງເຫັນທີ່ຈະລຶບ: + .bvstate-not-found = ບໍ່ພົບສະຖານະການເບິ່ງເຫັນ "__ocs_fmt_0__". + .bvstate-exists = ສະຖານະການເບິ່ງເຫັນ "__ocs_fmt_0__" ມີຢູ່ແລ້ວ. + .bvstate-last = ບໍ່ສາມາດລຶບສະຖານະການເບິ່ງເຫັນດຽວໄດ້. + .bvstate-needs-bedit = __ocs_fmt_0__: ເປີດບລັອກໃນຕົວແກ້ໄຂບລັອກກ່ອນ (BEDIT). + .bvstate-current = ສະຖານະການເບິ່ງເຫັນປັດຈຸບັນ: __ocs_fmt_0__ + .bvhide-select = ເລືອກວັດຖຸທີ່ຈະເຊື່ອງ: + .bvshow-select = ເລືອກວັດຖຸທີ່ຈະສະແດງ: .arrayedit-select-array = ARRAYEDIT ເລືອກອາເຣ: .arrayedit-options = ARRAYEDIT ປ້ອນທາງເລືອກ [ແຫຼ່ງ/eXit] : .arrayedit-not-an-array = ARRAYEDIT: ວັດຖຸທີ່ເລືອກບໍ່ແມ່ນອາເຣທີ່ເຊື່ອມໂຍງ. diff --git a/locales/nl-NL/opencadstudio.ftl b/locales/nl-NL/opencadstudio.ftl index 25ce0ed57..c5766c4b3 100644 --- a/locales/nl-NL/opencadstudio.ftl +++ b/locales/nl-NL/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Selecteer symbool met attributen: .annotation-object-scale = Annotatie objectschaal .bedit-select-block-reference-to-edit = BEDIT Selecteer symboolverwijzing naar bewerken: + .bvstate-option = BVSTATE Geef een optie [Nieuw/Instellen/Hernoemen/Verwijderen] : + .bvstate-new-name = BVSTATE Geef de naam van de nieuwe zichtbaarheidsstatus: + .bvstate-set = BVSTATE Geef de zichtbaarheidsstatus die huidig wordt <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Geef de te hernoemen zichtbaarheidsstatus <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Geef de nieuwe naam: + .bvstate-delete = BVSTATE Geef de te verwijderen zichtbaarheidsstatus: + .bvstate-not-found = Zichtbaarheidsstatus "__ocs_fmt_0__" niet gevonden. + .bvstate-exists = Zichtbaarheidsstatus "__ocs_fmt_0__" bestaat al. + .bvstate-last = De enige zichtbaarheidsstatus kan niet worden verwijderd. + .bvstate-needs-bedit = __ocs_fmt_0__: open het blok eerst in de blokeditor (BEDIT). + .bvstate-current = Huidige zichtbaarheidsstatus: __ocs_fmt_0__ + .bvhide-select = Selecteer te verbergen objecten: + .bvshow-select = Selecteer zichtbaar te maken objecten: .arrayedit-select-array = ARRAYEDIT Selecteer de reeks: .arrayedit-options = ARRAYEDIT Geef een optie [Bron/eXit] : .arrayedit-not-an-array = ARRAYEDIT: het geselecteerde object is geen associatieve reeks. diff --git a/locales/pl-PL/opencadstudio.ftl b/locales/pl-PL/opencadstudio.ftl index eb2c2c96c..e09fba7d0 100644 --- a/locales/pl-PL/opencadstudio.ftl +++ b/locales/pl-PL/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Wybierz blok z atrybutami: .annotation-object-scale = Skala obiektu adnotacji .bedit-select-block-reference-to-edit = BEDIT Wybierz odniesienie do bloku do edycji: + .bvstate-option = BVSTATE Podaj opcję [Nowy/Ustaw/Zmień nazwę/Usuń] : + .bvstate-new-name = BVSTATE Podaj nazwę nowego stanu widoczności: + .bvstate-set = BVSTATE Podaj stan widoczności, który ma być bieżący <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Podaj stan widoczności do zmiany nazwy <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Podaj nową nazwę: + .bvstate-delete = BVSTATE Podaj stan widoczności do usunięcia: + .bvstate-not-found = Nie znaleziono stanu widoczności "__ocs_fmt_0__". + .bvstate-exists = Stan widoczności "__ocs_fmt_0__" już istnieje. + .bvstate-last = Nie można usunąć jedynego stanu widoczności. + .bvstate-needs-bedit = __ocs_fmt_0__: najpierw otwórz blok w edytorze bloków (BEDIT). + .bvstate-current = Bieżący stan widoczności: __ocs_fmt_0__ + .bvhide-select = Wybierz obiekty do ukrycia: + .bvshow-select = Wybierz obiekty do pokazania: .arrayedit-select-array = ARRAYEDIT Wybierz szyk: .arrayedit-options = ARRAYEDIT Podaj opcję [Źródło/eXit] : .arrayedit-not-an-array = ARRAYEDIT: wybrany obiekt nie jest szykiem skojarzonym. diff --git a/locales/pt-BR/opencadstudio.ftl b/locales/pt-BR/opencadstudio.ftl index 120d5b580..8af49667b 100644 --- a/locales/pt-BR/opencadstudio.ftl +++ b/locales/pt-BR/opencadstudio.ftl @@ -3121,6 +3121,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Selecione um bloco com atributos: .annotation-object-scale = Escala de objeto de anotação .bedit-select-block-reference-to-edit = BEDIT Selecione a referência de bloco a editar: + .bvstate-option = BVSTATE Digite uma opção [Novo/Definir/Renomear/Excluir] : + .bvstate-new-name = BVSTATE Digite o nome do novo estado de visibilidade: + .bvstate-set = BVSTATE Digite o estado de visibilidade a tornar atual <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Digite o estado de visibilidade a renomear <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Digite o novo nome: + .bvstate-delete = BVSTATE Digite o estado de visibilidade a excluir: + .bvstate-not-found = Estado de visibilidade "__ocs_fmt_0__" não encontrado. + .bvstate-exists = O estado de visibilidade "__ocs_fmt_0__" já existe. + .bvstate-last = O único estado de visibilidade não pode ser excluído. + .bvstate-needs-bedit = __ocs_fmt_0__: abra primeiro o bloco no Editor de blocos (BEDIT). + .bvstate-current = Estado de visibilidade atual: __ocs_fmt_0__ + .bvhide-select = Selecione os objetos a ocultar: + .bvshow-select = Selecione os objetos a tornar visíveis: .arrayedit-select-array = ARRAYEDIT Selecione o arranjo: .arrayedit-options = ARRAYEDIT Digite uma opção [Origem/eXit] : .arrayedit-not-an-array = ARRAYEDIT: o objeto selecionado não é um arranjo associativo. diff --git a/locales/ru-RU/opencadstudio.ftl b/locales/ru-RU/opencadstudio.ftl index e0838f93c..4bac0c524 100644 --- a/locales/ru-RU/opencadstudio.ftl +++ b/locales/ru-RU/opencadstudio.ftl @@ -3124,6 +3124,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Выберите блок с атрибутами: .annotation-object-scale = Масштаб аннотативного объекта .bedit-select-block-reference-to-edit = BEDIT Выберите ссылку на блок для редактирования: + .bvstate-option = BVSTATE Введите параметр [Новое/Установить/Переименовать/Удалить] <Установить>: + .bvstate-new-name = BVSTATE Введите имя нового состояния видимости: + .bvstate-set = BVSTATE Введите состояние видимости, которое станет текущим <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Введите состояние видимости для переименования <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Введите новое имя: + .bvstate-delete = BVSTATE Введите состояние видимости для удаления: + .bvstate-not-found = Состояние видимости "__ocs_fmt_0__" не найдено. + .bvstate-exists = Состояние видимости "__ocs_fmt_0__" уже существует. + .bvstate-last = Единственное состояние видимости нельзя удалить. + .bvstate-needs-bedit = __ocs_fmt_0__: сначала откройте блок в редакторе блоков (BEDIT). + .bvstate-current = Текущее состояние видимости: __ocs_fmt_0__ + .bvhide-select = Выберите объекты, которые нужно скрыть: + .bvshow-select = Выберите объекты, которые нужно показать: .arrayedit-select-array = ARRAYEDIT Выберите массив: .arrayedit-options = ARRAYEDIT Введите параметр [Исходные/eXit] : .arrayedit-not-an-array = ARRAYEDIT: выбранный объект не является ассоциативным массивом. diff --git a/locales/tr-TR/opencadstudio.ftl b/locales/tr-TR/opencadstudio.ftl index c16d192e1..812d81026 100644 --- a/locales/tr-TR/opencadstudio.ftl +++ b/locales/tr-TR/opencadstudio.ftl @@ -3102,6 +3102,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT Öznitelikleri olan bir blok seçin: .annotation-object-scale = Açıklama Nesnesi Ölçeği .bedit-select-block-reference-to-edit = BEDIT Düzenlenecek blok referansını seçin: + .bvstate-option = BVSTATE Bir seçenek girin [Yeni/Ayarla/Yeniden adlandır/Sil] : + .bvstate-new-name = BVSTATE Yeni görünürlük durumunun adını girin: + .bvstate-set = BVSTATE Geçerli yapılacak görünürlük durumunu girin <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE Yeniden adlandırılacak görünürlük durumunu girin <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE Yeni adı girin: + .bvstate-delete = BVSTATE Silinecek görünürlük durumunu girin: + .bvstate-not-found = "__ocs_fmt_0__" görünürlük durumu bulunamadı. + .bvstate-exists = "__ocs_fmt_0__" görünürlük durumu zaten var. + .bvstate-last = Tek görünürlük durumu silinemez. + .bvstate-needs-bedit = __ocs_fmt_0__: önce bloğu Blok Düzenleyici'de açın (BEDIT). + .bvstate-current = Geçerli görünürlük durumu: __ocs_fmt_0__ + .bvhide-select = Gizlenecek nesneleri seçin: + .bvshow-select = Görünür yapılacak nesneleri seçin: .arrayedit-select-array = ARRAYEDIT Diziyi seçin: .arrayedit-options = ARRAYEDIT Bir seçenek girin [Kaynak/çıKış] <çıKış>: .arrayedit-not-an-array = ARRAYEDIT: seçilen nesne ilişkili bir dizi değil. diff --git a/locales/zh-CN/opencadstudio.ftl b/locales/zh-CN/opencadstudio.ftl index 8a81b1bc1..c91e42953 100644 --- a/locales/zh-CN/opencadstudio.ftl +++ b/locales/zh-CN/opencadstudio.ftl @@ -3108,6 +3108,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT 选择带属性的块: .annotation-object-scale = 注释对象比例 .bedit-select-block-reference-to-edit = BEDIT 选择要编辑的块引用 : + .bvstate-option = BVSTATE 输入选项 [新建/设置/重命名/删除] <设置>: + .bvstate-new-name = BVSTATE 输入新可见性状态的名称: + .bvstate-set = BVSTATE 输入要置为当前的可见性状态 <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE 输入要重命名的可见性状态 <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE 输入新名称: + .bvstate-delete = BVSTATE 输入要删除的可见性状态: + .bvstate-not-found = 找不到可见性状态“__ocs_fmt_0__”。 + .bvstate-exists = 可见性状态“__ocs_fmt_0__”已存在。 + .bvstate-last = 不能删除唯一的可见性状态。 + .bvstate-needs-bedit = __ocs_fmt_0__: 请先在块编辑器中打开该块 (BEDIT)。 + .bvstate-current = 当前可见性状态: __ocs_fmt_0__ + .bvhide-select = 选择要隐藏的对象: + .bvshow-select = 选择要显示的对象: .arrayedit-select-array = ARRAYEDIT 选择阵列: .arrayedit-options = ARRAYEDIT 输入选项 [源/eXit] : .arrayedit-not-an-array = ARRAYEDIT: 所选对象不是关联阵列。 diff --git a/locales/zh-TW/opencadstudio.ftl b/locales/zh-TW/opencadstudio.ftl index 28ac1a835..53b433d98 100644 --- a/locales/zh-TW/opencadstudio.ftl +++ b/locales/zh-TW/opencadstudio.ftl @@ -3120,6 +3120,19 @@ modify = .attedit-select-block-with-attributes = ATTEDIT 選擇帶屬性的圖塊: .annotation-object-scale = 註解物件比例 .bedit-select-block-reference-to-edit = BEDIT 選擇要編輯的圖塊引用 : + .bvstate-option = BVSTATE 輸入選項 [新建/設定/更名/刪除] <設定>: + .bvstate-new-name = BVSTATE 輸入新可見性狀態的名稱: + .bvstate-set = BVSTATE 輸入要設為目前的可見性狀態 <__ocs_fmt_0__>: + .bvstate-rename-from = BVSTATE 輸入要更名的可見性狀態 <__ocs_fmt_0__>: + .bvstate-rename-to = BVSTATE 輸入新名稱: + .bvstate-delete = BVSTATE 輸入要刪除的可見性狀態: + .bvstate-not-found = 找不到可見性狀態「__ocs_fmt_0__」。 + .bvstate-exists = 可見性狀態「__ocs_fmt_0__」已存在。 + .bvstate-last = 無法刪除唯一的可見性狀態。 + .bvstate-needs-bedit = __ocs_fmt_0__: 請先在圖塊編輯器中開啟圖塊 (BEDIT)。 + .bvstate-current = 目前可見性狀態: __ocs_fmt_0__ + .bvhide-select = 選取要隱藏的物件: + .bvshow-select = 選取要顯示的物件: .arrayedit-select-array = ARRAYEDIT 選取陣列: .arrayedit-options = ARRAYEDIT 輸入選項 [來源/eXit] : .arrayedit-not-an-array = ARRAYEDIT: 選取的物件不是關聯式陣列。 diff --git a/src/app/block_editor.rs b/src/app/block_editor.rs index e797ed515..cbc22d2d7 100644 --- a/src/app/block_editor.rs +++ b/src/app/block_editor.rs @@ -260,6 +260,14 @@ impl OpenCADStudio { return false; } } + // A dynamic block leaves the editor showing its first visibility + // state, with erased objects dropped from its states. + let definition = session.br_handle; + crate::scene::block_visibility::finish_editing( + &mut self.tabs[i].scene.document, + definition, + ); + self.tabs[i].scene.visibility_states.remove(&definition); if let Some(delta) = self.block_edit_delta(i) { let mut history = self.tabs[i].block_edit_history.take().unwrap(); std::mem::swap(&mut self.tabs[i].history, &mut history.main); diff --git a/src/app/commands/bvisibility.rs b/src/app/commands/bvisibility.rs new file mode 100644 index 000000000..c70a66626 --- /dev/null +++ b/src/app/commands/bvisibility.rs @@ -0,0 +1,188 @@ +//! BVSTATE, BVHIDE and BVSHOW: the visibility states of the block open in +//! the Block Editor (see `scene::block_visibility`). + +use crate::app::{Message, OpenCADStudio}; +use crate::scene::block_visibility as visibility; +use codec::{CadDocument, EntityType, Handle}; +use iced::Task; + +impl OpenCADStudio { + /// The block open in the Block Editor, or an error naming `command`. + fn visibility_block(&mut self, i: usize, command: &str) -> Option { + let block = self.tabs[i] + .active_block_edit_session() + .map(|session| session.br_handle); + if block.is_none() { + self.command_line.push_error( + crate::tf!("{}: open the block in the Block Editor first (BEDIT).", command) + .as_ref(), + ); + } + block + } + + /// The block's visibility parameter, made when it has none: its grip + /// sits on the block's base point. + fn visibility_parameter(&mut self, i: usize, block: Handle) -> Option { + let document = &mut self.tabs[i].scene.document; + let base = document + .block_records + .iter() + .find(|record| record.handle == block) + .and_then(|record| match document.get_entity(record.block_entity_handle) { + Some(EntityType::Block(begin)) => Some(begin.base_point), + _ => None, + }) + .unwrap_or(codec::types::Vector3::ZERO); + visibility::create(document, block, base) + } + + /// The state the block shows in the editor. + fn visibility_current(&self, i: usize, block: Handle) -> usize { + let scene = &self.tabs[i].scene; + scene.visibility_states.get(&block).copied().unwrap_or_else(|| { + visibility::parameter(&scene.document, block) + .map_or(0, |parameter| visibility::shown_state(&scene.document, parameter.handle)) + }) + } + + fn show_visibility_state(&mut self, i: usize, block: Handle, parameter: Handle, state: usize) { + visibility::apply_state(&mut self.tabs[i].scene.document, parameter, state); + self.tabs[i].scene.visibility_states.insert(block, state); + self.tabs[i].scene.bump_geometry(); + self.tabs[i].dirty = true; + if let Some(name) = visibility::state_names(&self.tabs[i].scene.document, parameter) + .get(state) + { + self.command_line + .push_output(crate::tf!("Current visibility state: {}", name).as_ref()); + } + } + + pub(super) fn dispatch_bvisibility(&mut self, cmd: &str, i: usize) -> Option> { + use crate::command::CadCommand; + use crate::modules::draw::modify::bvstate::{BvStateCommand, SEP}; + match cmd { + "BVSTATE" => { + let block = self.visibility_block(i, "BVSTATE")?; + let document = &self.tabs[i].scene.document; + let names = visibility::parameter(document, block) + .map(|parameter| visibility::state_names(document, parameter.handle)) + .unwrap_or_else(|| vec!["VisibilityState0".to_string()]); + let current = self.visibility_current(i, block); + if let Some(name) = names.get(current) { + self.command_line + .push_output(crate::tf!("Current visibility state: {}", name).as_ref()); + } + let command = BvStateCommand::new(names, current); + self.command_line.push_info(&command.prompt()); + self.command_line.set_step_options(command.options()); + self.tabs[i].active_cmd = Some(Box::new(command)); + Some(Task::none()) + } + cmd if cmd.starts_with("_BVSTATE") => { + let block = self.visibility_block(i, "BVSTATE")?; + let fields: Vec<&str> = cmd.split(SEP).skip(1).collect(); + self.push_undo_snapshot(i, "BVSTATE"); + let parameter = self.visibility_parameter(i, block)?; + let current = self.visibility_current(i, block); + let document = &mut self.tabs[i].scene.document; + let index = |document: &CadDocument, name: &str| { + visibility::state_names(document, parameter) + .iter() + .position(|candidate| candidate.eq_ignore_ascii_case(name)) + }; + let shown = match fields.as_slice() { + ["NEW", name] => visibility::add_state(document, parameter, name, current), + ["SET", name] => index(document, name), + ["RENAME", from, to] => { + if let Some(state) = index(document, from) { + visibility::rename_state(document, parameter, state, to); + } + Some(current) + } + ["DELETE", name] => index(document, name).map(|state| { + visibility::delete_state(document, parameter, state); + let count = visibility::state_names(document, parameter).len(); + if current == state || current >= count { + 0 + } else if current > state { + current - 1 + } else { + current + } + }), + _ => None, + }; + self.show_visibility_state(i, block, parameter, shown.unwrap_or(current)); + self.tabs[i].active_cmd = None; + Some(Task::none()) + } + "BVHIDE" | "BVSHOW" => { + let block = self.visibility_block(i, cmd)?; + let hide = cmd == "BVHIDE"; + if !hide { + // Every object of the block shows while they are picked; + // the state is put back once the pick ends. + let document = &mut self.tabs[i].scene.document; + let members: Vec = document + .block_records + .iter() + .find(|record| record.handle == block) + .map(|record| record.entity_handles.clone()) + .unwrap_or_default(); + for handle in members { + if let Some(entity) = document.get_entity_mut(handle) { + entity.common_mut().invisible = false; + } + } + self.tabs[i].scene.bump_geometry(); + } + let pending = if hide { "_BVHIDE" } else { "_BVSHOW" }; + if !self.tabs[i].scene.selected.is_empty() { + return Some(self.dispatch_command(pending)); + } + let prompt = if hide { + "Select objects to hide:" + } else { + "Select objects to make visible:" + }; + use crate::modules::draw::select::SelectObjectsCommand; + let command = SelectObjectsCommand::with_prompt(cmd, pending, prompt); + self.command_line.push_info(&command.prompt()); + self.tabs[i].active_cmd = Some(Box::new(command)); + Some(Task::none()) + } + "_BVHIDE" | "_BVSHOW" => { + let block = self.visibility_block(i, &cmd[1..])?; + let handles: Vec = self.tabs[i] + .scene + .selected_handles_in_order() + .into_iter() + .filter(|handle| { + self.tabs[i] + .scene + .document + .get_entity(*handle) + .is_some_and(|entity| entity.common().owner_handle == block) + }) + .collect(); + self.push_undo_snapshot(i, &cmd[1..]); + let parameter = self.visibility_parameter(i, block)?; + let current = self.visibility_current(i, block); + visibility::set_visible( + &mut self.tabs[i].scene.document, + parameter, + current, + &handles, + cmd == "_BVSHOW", + ); + self.tabs[i].scene.deselect_all(); + self.show_visibility_state(i, block, parameter, current); + self.tabs[i].active_cmd = None; + Some(Task::none()) + } + _ => None, + } + } +} diff --git a/src/app/commands/mod.rs b/src/app/commands/mod.rs index abac53573..1ea774d8c 100644 --- a/src/app/commands/mod.rs +++ b/src/app/commands/mod.rs @@ -7,6 +7,7 @@ use std::path::PathBuf; mod attdef; mod blocks; mod blockvars; +mod bvisibility; mod count; mod dim; pub(crate) mod display; @@ -302,6 +303,9 @@ impl OpenCADStudio { if let Some(t) = self.dispatch_xref_attach(cmd, i) { return Some(t); } + if let Some(t) = self.dispatch_bvisibility(cmd, i) { + return Some(t); + } if let Some(t) = self.dispatch_blocks(cmd, i) { return Some(t); } diff --git a/src/app/visibility.rs b/src/app/visibility.rs index f3d8504ca..049dd55ea 100644 --- a/src/app/visibility.rs +++ b/src/app/visibility.rs @@ -186,6 +186,20 @@ impl OpenCADStudio { return; } + // A reference to the definition itself gets an evaluated copy first, + // so the state picked for it leaves the definition alone. + let direct = { + let doc = &self.tabs[i].scene.document; + matches!(doc.get_entity(insert_handle), Some(EntityType::Insert(ins)) + if doc.block_records.get(&ins.block_name).is_some_and(|record| { + crate::scene::block_visibility::parameter(doc, record.handle).is_some() + })) + }; + if direct { + self.push_undo_snapshot(i, "Visibility State"); + self.tabs[i].scene.visibility_representation(insert_handle); + } + // Resolve everything against an immutable borrow first. let mapping = { let doc = &self.tabs[i].scene.document; @@ -202,7 +216,9 @@ impl OpenCADStudio { return; }; - self.push_undo_snapshot(i, "Visibility State"); + if !direct { + self.push_undo_snapshot(i, "Visibility State"); + } // Apply invisibility flags to the anonymous block's members. { diff --git a/src/locale_catalog.rs b/src/locale_catalog.rs index d903c1804..452314b9c 100644 --- a/src/locale_catalog.rs +++ b/src/locale_catalog.rs @@ -832,6 +832,19 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static "BACKGROUND colour [Default/Black/DarkGray/Gray/LightGray/White] or R G B (0–255):" => Some(("file", "background-colour-default-black-darkgray-gray")), "BASE Specify base point:" => Some(("insert", "base-specify-base-point")), "BEDIT Select block reference to edit:" => Some(("modify", "bedit-select-block-reference-to-edit")), + "BVSTATE Enter an option [New/Set/Rename/Delete] :" => Some(("modify", "bvstate-option")), + "BVSTATE Enter name of new visibility state:" => Some(("modify", "bvstate-new-name")), + "BVSTATE Enter visibility state to make current <{}>:" => Some(("modify", "bvstate-set")), + "BVSTATE Enter visibility state to rename <{}>:" => Some(("modify", "bvstate-rename-from")), + "BVSTATE Enter new name:" => Some(("modify", "bvstate-rename-to")), + "BVSTATE Enter visibility state to delete:" => Some(("modify", "bvstate-delete")), + "Visibility state \"{}\" not found." => Some(("modify", "bvstate-not-found")), + "Visibility state \"{}\" already exists." => Some(("modify", "bvstate-exists")), + "The only visibility state cannot be deleted." => Some(("modify", "bvstate-last")), + "{}: open the block in the Block Editor first (BEDIT)." => Some(("modify", "bvstate-needs-bedit")), + "Current visibility state: {}" => Some(("modify", "bvstate-current")), + "Select objects to hide:" => Some(("modify", "bvhide-select")), + "Select objects to make visible:" => Some(("modify", "bvshow-select")), "ARRAYEDIT Select array:" => Some(("modify", "arrayedit-select-array")), "ARRAYEDIT Enter an option [Source/eXit] :" => Some(("modify", "arrayedit-options")), "ARRAYEDIT: the selected object is not an associative array." => Some(("modify", "arrayedit-not-an-array")), diff --git a/src/modules/draw/modify/bvstate.rs b/src/modules/draw/modify/bvstate.rs new file mode 100644 index 000000000..3dfe7cb93 --- /dev/null +++ b/src/modules/draw/modify/bvstate.rs @@ -0,0 +1,200 @@ +// BVSTATE — the visibility states of the block open in the Block Editor: +// make a new one, make one current, rename or delete one. The first use in a +// block makes it dynamic with a visibility parameter. + +use crate::command::{CadCommand, CmdOption, CmdResult, InputKind}; +use codec::Handle; +use glam::DVec3; + +/// Separates the fields of the `_BVSTATE` step the command hands over. +pub const SEP: char = '\u{1f}'; + +enum Step { + Option, + NewName, + SetName, + RenameFrom, + RenameTo(String), + DeleteName, +} + +pub struct BvStateCommand { + names: Vec, + current: usize, + step: Step, +} + +impl BvStateCommand { + pub fn new(names: Vec, current: usize) -> Self { + Self { + names, + current, + step: Step::Option, + } + } + + fn current_name(&self) -> String { + self.names.get(self.current).cloned().unwrap_or_default() + } + + fn known(&self, name: &str) -> Option { + self.names + .iter() + .find(|candidate| candidate.eq_ignore_ascii_case(name)) + .cloned() + } + + fn done(parts: &[&str]) -> CmdResult { + let mut command = String::from("_BVSTATE"); + for part in parts { + command.push(SEP); + command.push_str(part); + } + CmdResult::Dispatch(command) + } + + fn unknown(name: &str) -> CmdResult { + CmdResult::ReportError(crate::tf!("Visibility state \"{}\" not found.", name).into_owned()) + } +} + +impl CadCommand for BvStateCommand { + fn name(&self) -> &'static str { + "BVSTATE" + } + + fn prompt(&self) -> String { + match &self.step { + Step::Option => { + crate::t!("BVSTATE Enter an option [New/Set/Rename/Delete] :").into_owned() + } + Step::NewName => crate::t!("BVSTATE Enter name of new visibility state:").into_owned(), + Step::SetName => crate::tf!( + "BVSTATE Enter visibility state to make current <{}>:", + self.current_name() + ) + .into_owned(), + Step::RenameFrom => crate::tf!( + "BVSTATE Enter visibility state to rename <{}>:", + self.current_name() + ) + .into_owned(), + Step::RenameTo(_) => crate::t!("BVSTATE Enter new name:").into_owned(), + Step::DeleteName => { + crate::t!("BVSTATE Enter visibility state to delete:").into_owned() + } + } + } + + fn options(&self) -> Vec { + match self.step { + Step::Option => vec![ + CmdOption::new(crate::t!("New").as_ref(), "N"), + CmdOption::new(crate::t!("Set").as_ref(), "S"), + CmdOption::new(crate::t!("Rename").as_ref(), "R"), + CmdOption::new(crate::t!("Delete").as_ref(), "D"), + ], + _ => Vec::new(), + } + } + + fn wants_text_input(&self) -> bool { + true + } + + fn input_kind(&self) -> InputKind { + match self.step { + Step::Option => InputKind::SingleToken, + _ => InputKind::FreeText, + } + } + + fn on_text_input(&mut self, text: &str) -> Option { + let text = text.trim(); + match &self.step { + Step::Option => { + self.step = match text.trim_start_matches('_').to_ascii_uppercase().as_str() { + "N" | "NEW" => Step::NewName, + "" | "S" | "SET" => Step::SetName, + "R" | "RENAME" => Step::RenameFrom, + "D" | "DELETE" => Step::DeleteName, + _ => { + return Some(CmdResult::ReportError( + crate::t!("Invalid option keyword.").into_owned(), + )) + } + }; + Some(CmdResult::NeedPoint) + } + Step::NewName if text.is_empty() => Some(CmdResult::NeedPoint), + Step::NewName => match self.known(text) { + Some(_) => Some(CmdResult::ReportError( + crate::tf!("Visibility state \"{}\" already exists.", text).into_owned(), + )), + None => Some(Self::done(&["NEW", text])), + }, + Step::SetName => { + let name = if text.is_empty() { + self.current_name() + } else { + text.to_string() + }; + Some(match self.known(&name) { + Some(name) => Self::done(&["SET", &name]), + None => Self::unknown(&name), + }) + } + Step::RenameFrom => { + let name = if text.is_empty() { + self.current_name() + } else { + text.to_string() + }; + match self.known(&name) { + Some(name) => { + self.step = Step::RenameTo(name); + Some(CmdResult::NeedPoint) + } + None => Some(Self::unknown(&name)), + } + } + Step::RenameTo(_) if text.is_empty() => Some(CmdResult::NeedPoint), + Step::RenameTo(from) => { + let taken = self + .known(text) + .is_some_and(|existing| !existing.eq_ignore_ascii_case(from)); + Some(if taken { + CmdResult::ReportError( + crate::tf!("Visibility state \"{}\" already exists.", text).into_owned(), + ) + } else { + Self::done(&["RENAME", from, text]) + }) + } + Step::DeleteName if text.is_empty() => Some(CmdResult::NeedPoint), + Step::DeleteName => Some(match self.known(text) { + Some(_) if self.names.len() <= 1 => CmdResult::ReportError( + crate::t!("The only visibility state cannot be deleted.").into_owned(), + ), + Some(name) => Self::done(&["DELETE", &name]), + None => Self::unknown(text), + }), + } + } + + fn on_point(&mut self, _pt: DVec3) -> CmdResult { + CmdResult::NeedPoint + } + + fn on_entity_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult { + CmdResult::NeedPoint + } + + fn on_enter(&mut self) -> CmdResult { + self.on_text_input("").unwrap_or(CmdResult::Cancel) + } +} + +inventory::submit!(crate::command::CommandRegistration { + names: &["BVSTATE", "BVHIDE", "BVSHOW"] +}); diff --git a/src/modules/draw/modify/mod.rs b/src/modules/draw/modify/mod.rs index ce4da1d84..8087c4efa 100644 --- a/src/modules/draw/modify/mod.rs +++ b/src/modules/draw/modify/mod.rs @@ -5,6 +5,7 @@ pub mod attedit; pub mod blend; pub mod block_edit; pub mod break_cmd; +pub mod bvstate; pub mod copy; pub mod delete; mod entity_index; diff --git a/src/scene/block_visibility.rs b/src/scene/block_visibility.rs new file mode 100644 index 000000000..01830d9b3 --- /dev/null +++ b/src/scene/block_visibility.rs @@ -0,0 +1,742 @@ +//! Visibility states of a dynamic block, authored in the Block Editor and +//! stored the way the format keeps them: a BLOCKVISIBILITYPARAMETER in the +//! block's `ACAD_ENHANCEDBLOCK` evaluation graph (with its grip and the +//! grip's two location expressions), `AcDbBlockRepETag` tags on the +//! definition and on each of its objects, and, behind every reference that +//! shows a state other than the definition's, an evaluated `*U` copy tagged +//! `AcDbBlockRepBTag` and linked back by `AcDbBlockRepresentationData`. +//! +//! The definition itself shows the first state, so a reference to it needs +//! no copy until another state is picked for it. + +use codec::objects::{ + BlockElement, BlockEvalExpression, BlockEvalValue, BlockEvaluationEdge, BlockEvaluationGraph, + BlockEvaluationNode, BlockGrip, BlockGripExpression, BlockParameterPropertyInfo, + BlockRepresentationData, BlockVisibilityParameter, BlockVisibilityState, DynamicBlockData, + DynamicBlockObject, ObjectType, +}; +use codec::types::Vector3; +use codec::xdata::{ExtendedDataRecord, XDataValue}; +use codec::{CadDocument, EntityType, Handle}; + +use super::Scene; + +const ELEMENT_TAG: &str = "AcDbBlockRepETag"; +const REPRESENTATION_TAG: &str = "AcDbBlockRepBTag"; +const TRUE_NAME: &str = "AcDbDynamicBlockTrueName"; +const GUID: &str = "AcDbDynamicBlockGUID"; + +/// Object version the parameter, grip and expressions carry. +const MAJOR: i32 = 33; +const MINOR: i32 = 329; +/// Value code of an expression that holds no value. +const NO_VALUE: i16 = -9999; + +/// (DXF name, class, DWG maintenance version) of the classes used here. +const CLASSES: &[(&str, &str, i32)] = &[ + ("ACAD_EVALUATION_GRAPH", "AcDbEvalGraph", 55), + ("BLOCKVISIBILITYPARAMETER", "AcDbBlockVisibilityParameter", 55), + ("BLOCKVISIBILITYGRIP", "AcDbBlockVisibilityGrip", 55), + ("BLOCKGRIPLOCATIONCOMPONENT", "AcDbBlockGripExpr", 20), + ("ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION", "AcDbDynamicBlockPurgePreventer", 61), + ("ACDB_BLOCKREPRESENTATION_DATA", "AcDbBlockRepresentationData", 58), +]; + +/// Register the classes with the flags and versions the reference writes +/// for them; a reader takes a class registered otherwise for a proxy. +fn register_classes(document: &mut CadDocument) { + for &(dxf_name, cpp_class_name, maintenance_version) in CLASSES { + if let Some(existing) = document.classes.get_mut_by_name(dxf_name) { + existing.proxy_flags = codec::classes::ProxyFlags(1153); + existing.dwg_version = 25; + existing.maintenance_version = maintenance_version; + continue; + } + document.classes.add_or_update(codec::classes::DxfClass { + dxf_name: dxf_name.to_string(), + cpp_class_name: cpp_class_name.to_string(), + application_name: "ObjectDBX Classes".to_string(), + proxy_flags: codec::classes::ProxyFlags(1153), + instance_count: 0, + was_zombie: false, + is_an_entity: false, + class_number: 0, + item_class_id: 0x1F3, + dwg_version: 25, + maintenance_version, + unknown1: 0, + unknown2: 0, + }); + } +} + +fn ensure_app_id(document: &mut CadDocument, name: &str) { + if !document.app_ids.contains(name) { + let mut app = codec::tables::AppId::new(name); + app.handle = document.allocate_handle(); + let _ = document.app_ids.add(app); + } +} + +fn expression(node_id: i32, value_code: i16, value: BlockEvalValue) -> BlockEvalExpression { + BlockEvalExpression { + parent_id: BlockEvalExpression::NO_PARENT, + major: MAJOR, + minor: MINOR, + value_code, + value, + node_id, + } +} + +fn dynamic( + handle: Handle, + owner: Handle, + dxf_name: &str, + cpp_class_name: &str, + data: DynamicBlockData, +) -> DynamicBlockObject { + let mut object = DynamicBlockObject::new(dxf_name, cpp_class_name); + object.handle = handle; + object.owner = owner; + object.data = data; + object +} + +/// The visibility parameter of block definition `definition`. +pub fn parameter(document: &CadDocument, definition: Handle) -> Option<&BlockVisibilityParameter> { + document.block_visibility_param_for_def(definition) +} + +/// Store an edited parameter in the document and its lookup view. +fn store(document: &mut CadDocument, parameter: BlockVisibilityParameter) { + document + .block_visibility_params + .insert(parameter.handle, parameter.clone()); + document.objects.insert( + parameter.handle, + ObjectType::BlockVisibilityParameter(parameter), + ); +} + +/// The objects of a definition, block begin and end left out. +fn members(document: &CadDocument, definition: Handle) -> Vec { + document + .block_records + .iter() + .find(|record| record.handle == definition) + .map(|record| { + record + .entity_handles + .iter() + .copied() + .filter(|handle| { + !matches!( + document.get_entity(*handle), + None | Some(EntityType::Block(_) | EntityType::BlockEnd(_)) + ) + }) + .collect() + }) + .unwrap_or_default() +} + +fn element_tag(id: i32, handle: Handle) -> ExtendedDataRecord { + let mut tag = ExtendedDataRecord::new(ELEMENT_TAG); + tag.add_value(XDataValue::Integer16(1)); + tag.add_value(XDataValue::Integer32(id)); + tag.add_value(XDataValue::Handle(handle)); + tag +} + +fn element_id(entity: &EntityType) -> Option { + entity + .common() + .extended_data + .get_record(ELEMENT_TAG)? + .values + .iter() + .find_map(|value| match value { + XDataValue::Integer32(id) => Some(*id), + _ => None, + }) +} + +fn set_entity_tag(document: &mut CadDocument, handle: Handle, record: ExtendedDataRecord) { + let app = document + .app_ids + .get(&record.application_name) + .map(|app| app.handle.value()); + if let Some(entity) = document.get_entity_mut(handle) { + let xdata = &mut entity.common_mut().extended_data; + // The fresh record, not bytes kept from a read, is what gets written. + if let Some(app) = app { + xdata.raw_dwg_eed.retain(|(owner, _)| *owner != app); + } + xdata.upsert_record(record); + } +} + +/// Give every object of the definition its element tag, numbering the new +/// ones after the highest id in use, and record the next id on the block. +fn tag_members(document: &mut CadDocument, definition: Handle) { + let members = members(document, definition); + let mut next = members + .iter() + .filter_map(|handle| document.get_entity(*handle).and_then(element_id)) + .max() + .map_or(0, |id| id + 1); + for handle in members { + let id = match document.get_entity(handle).and_then(element_id) { + Some(id) => id, + None => { + next += 1; + next - 1 + } + }; + set_entity_tag(document, handle, element_tag(id, handle)); + } + let mut tag = ExtendedDataRecord::new(ELEMENT_TAG); + tag.add_value(XDataValue::Integer16(1)); + tag.add_value(XDataValue::Integer32(next)); + document.set_object_xdata_record(definition, tag); +} + +/// A registry-format GUID for a new dynamic block. +fn new_guid() -> String { + let mut bytes = [0u8; 16]; + if getrandom::fill(&mut bytes).is_err() { + let seed = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .map_or(0, |elapsed| elapsed.as_nanos()); + bytes = seed.to_le_bytes(); + } + let hex: String = bytes.iter().map(|byte| format!("{byte:02X}")).collect(); + format!( + "{{{}-{}-{}-{}-{}}}", + &hex[0..8], + &hex[8..12], + &hex[12..16], + &hex[16..20], + &hex[20..32] + ) +} + +/// Make block definition `definition` dynamic with a visibility parameter +/// whose grip sits at `location`, one state showing every object. Returns +/// the parameter, or the one the block has already. +pub fn create(document: &mut CadDocument, definition: Handle, location: Vector3) -> Option { + if let Some(existing) = parameter(document, definition) { + return Some(existing.handle); + } + let name = document + .block_records + .iter() + .find(|record| record.handle == definition)? + .name + .clone(); + register_classes(document); + for app in [ELEMENT_TAG, REPRESENTATION_TAG, TRUE_NAME, GUID] { + ensure_app_id(document, app); + } + let dictionary = document.ensure_extension_dictionary(definition); + let [graph, parameter, grip, x, y, preventer] = + [(); 6].map(|_| document.allocate_handle()); + + store( + document, + BlockVisibilityParameter { + handle: parameter, + owner: graph, + eval_parent_id: BlockEvalExpression::NO_PARENT, + eval_major: MAJOR, + eval_minor: MINOR, + eval_value_code: NO_VALUE, + eval_value: BlockEvalValue::None, + eval_node_id: 1, + element_name: "Visibility State".to_string(), + element_major: MAJOR, + element_minor: MINOR, + element_eed_1071: 0, + show_properties: true, + chain_actions: false, + name: "Visibility1".to_string(), + description: String::new(), + def_point: location, + property_info: [ + BlockParameterPropertyInfo::default(), + BlockParameterPropertyInfo::default(), + ], + property_info_count: 2, + is_initialized: true, + unknown_bool: false, + all_blocks: Vec::new(), + states: vec![BlockVisibilityState { + name: "VisibilityState0".to_string(), + ..Default::default() + }], + }, + ); + let grip_object = dynamic( + grip, + graph, + "BLOCKVISIBILITYGRIP", + "AcDbBlockVisibilityGrip", + DynamicBlockData::VisibilityGrip(BlockGrip { + element: BlockElement { + eval: expression(2, NO_VALUE, BlockEvalValue::None), + name: "Grip".to_string(), + major: MAJOR, + minor: MINOR, + eed_1071: 0, + }, + flags_91: 3, + flags_92: 4, + location, + insert_cycling: false, + insert_cycling_weight: -1, + }), + ); + document + .objects + .insert(grip, ObjectType::DynamicBlock(grip_object)); + for (handle, node, axis) in [(x, 3, "UpdatedX"), (y, 4, "UpdatedY")] { + let component = dynamic( + handle, + graph, + "BLOCKGRIPLOCATIONCOMPONENT", + "AcDbBlockGripExpr", + DynamicBlockData::GripLocationComponent(BlockGripExpression { + eval: expression(node, 40, BlockEvalValue::Real(0.0)), + grip_type: 1, + expression: axis.to_string(), + }), + ); + document + .objects + .insert(handle, ObjectType::DynamicBlock(component)); + } + // The grip drives the parameter, whose position the two expressions + // read: node 0 the parameter, 1 the grip, 2 and 3 the expressions. + let node = |id: i32, expression: Handle, node_data: [i32; 4]| BlockEvaluationNode { + id, + edge_flags: 32, + next_id: id + 1, + expression, + node_data, + active_cycles: None, + }; + let edge = |id: i32, source_node: i32, destination_node: i32, outgoing_edges| { + BlockEvaluationEdge { + id, + next_id: 0, + incoming_edge: 1, + source_node, + destination_node, + outgoing_edges, + } + }; + let mut graph_object = dynamic( + graph, + dictionary, + "ACAD_EVALUATION_GRAPH", + "AcDbEvalGraph", + DynamicBlockData::EvaluationGraph(BlockEvaluationGraph { + first_node_id: 4, + first_node_id_copy: 4, + nodes: vec![ + node(0, parameter, [0, 0, 1, 2]), + node(1, grip, [-1, -1, 0, 0]), + node(2, x, [1, 1, -1, -1]), + node(3, y, [2, 2, -1, -1]), + ], + edges: vec![ + edge(0, 1, 0, [-1; 5]), + edge(1, 0, 2, [-1, -1, -1, 2, -1]), + edge(2, 0, 3, [-1, -1, 1, -1, -1]), + ], + }), + ); + graph_object.reactors.push(dictionary); + document + .objects + .insert(graph, ObjectType::DynamicBlock(graph_object)); + let mut preventer_object = dynamic( + preventer, + dictionary, + "ACDB_DYNAMICBLOCKPURGEPREVENTER_VERSION", + "AcDbDynamicBlockPurgePreventer", + DynamicBlockData::Representation(BlockRepresentationData { + flags: 1, + block: definition, + }), + ); + preventer_object.reactors.push(dictionary); + document + .objects + .insert(preventer, ObjectType::DynamicBlock(preventer_object)); + if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&dictionary) { + dictionary.add_entry("ACAD_ENHANCEDBLOCK", graph); + dictionary.add_entry("AcDbDynamicBlockRoundTripPurgePreventer", preventer); + } + + let mut true_name = ExtendedDataRecord::new(TRUE_NAME); + true_name.add_value(XDataValue::String(name)); + document.set_object_xdata_record(definition, true_name); + let mut guid = ExtendedDataRecord::new(GUID); + guid.add_value(XDataValue::String(new_guid())); + document.set_object_xdata_record(definition, guid); + tag_members(document, definition); + Some(parameter) +} + +/// The names of the states of `parameter`, in list order. +pub fn state_names(document: &CadDocument, parameter: Handle) -> Vec { + match document.objects.get(¶meter) { + Some(ObjectType::BlockVisibilityParameter(parameter)) => { + parameter.states.iter().map(|state| state.name.clone()).collect() + } + _ => Vec::new(), + } +} + +fn edit( + document: &mut CadDocument, + parameter: Handle, + change: impl FnOnce(&mut BlockVisibilityParameter) -> R, +) -> Option { + let Some(ObjectType::BlockVisibilityParameter(current)) = document.objects.get(¶meter) + else { + return None; + }; + let mut edited = current.clone(); + let result = change(&mut edited); + store(document, edited); + Some(result) +} + +/// Add state `name`, showing what state `like` shows. Returns its index, or +/// `None` when the name is taken. +pub fn add_state( + document: &mut CadDocument, + parameter: Handle, + name: &str, + like: usize, +) -> Option { + edit(document, parameter, |parameter| { + if parameter + .states + .iter() + .any(|state| state.name.eq_ignore_ascii_case(name)) + { + return None; + } + let visible_blocks = parameter + .states + .get(like) + .map(|state| state.visible_blocks.clone()) + .unwrap_or_default(); + parameter.states.push(BlockVisibilityState { + name: name.to_string(), + visible_blocks, + visible_params: Vec::new(), + }); + Some(parameter.states.len() - 1) + }) + .flatten() +} + +/// Rename state `index`; false when the new name is taken. +pub fn rename_state( + document: &mut CadDocument, + parameter: Handle, + index: usize, + name: &str, +) -> bool { + edit(document, parameter, |parameter| { + let taken = parameter + .states + .iter() + .enumerate() + .any(|(other, state)| other != index && state.name.eq_ignore_ascii_case(name)); + match parameter.states.get_mut(index) { + Some(state) if !taken => { + state.name = name.to_string(); + true + } + _ => false, + } + }) + .unwrap_or(false) +} + +/// Delete state `index`; the last state stays. +pub fn delete_state(document: &mut CadDocument, parameter: Handle, index: usize) -> bool { + edit(document, parameter, |parameter| { + if parameter.states.len() <= 1 || index >= parameter.states.len() { + return false; + } + parameter.states.remove(index); + true + }) + .unwrap_or(false) +} + +/// Show (`visible`) or hide `handles` in state `index`. An object the +/// parameter did not govern yet is shown in every other state, as it was. +pub fn set_visible( + document: &mut CadDocument, + parameter: Handle, + index: usize, + handles: &[Handle], + visible: bool, +) { + let definition = owner_definition(document, parameter); + edit(document, parameter, |parameter| { + for &handle in handles { + if !parameter.all_blocks.contains(&handle) { + parameter.all_blocks.push(handle); + for state in &mut parameter.states { + state.visible_blocks.push(handle); + } + } + let Some(state) = parameter.states.get_mut(index) else { + continue; + }; + state.visible_blocks.retain(|other| *other != handle); + if visible { + state.visible_blocks.push(handle); + } + } + }); + if let Some(definition) = definition { + tag_members(document, definition); + } +} + +/// The block definition `parameter` belongs to (graph, dictionary, record). +fn owner_definition(document: &CadDocument, parameter: Handle) -> Option { + let graph = match document.objects.get(¶meter)? { + ObjectType::BlockVisibilityParameter(parameter) => parameter.owner, + _ => return None, + }; + let dictionary = match document.objects.get(&graph)? { + ObjectType::DynamicBlock(graph) => graph.owner, + _ => return None, + }; + match document.objects.get(&dictionary)? { + ObjectType::Dictionary(dictionary) => Some(dictionary.owner), + _ => None, + } +} + +/// Show the definition's objects as state `index` does. +pub fn apply_state(document: &mut CadDocument, parameter: Handle, index: usize) { + let Some(ObjectType::BlockVisibilityParameter(current)) = document.objects.get(¶meter) + else { + return; + }; + let Some(state) = current.states.get(index) else { + return; + }; + let shown: Vec<(Handle, bool)> = current + .all_blocks + .iter() + .map(|handle| (*handle, state.visible_blocks.contains(handle))) + .collect(); + for (handle, visible) in shown { + if let Some(entity) = document.get_entity_mut(handle) { + entity.common_mut().invisible = !visible; + } + } +} + +/// The state whose objects are the ones the definition shows now. +pub fn shown_state(document: &CadDocument, parameter: Handle) -> usize { + let Some(ObjectType::BlockVisibilityParameter(parameter)) = document.objects.get(¶meter) + else { + return 0; + }; + parameter + .states + .iter() + .position(|state| { + parameter.all_blocks.iter().all(|handle| { + let shown = document + .get_entity(*handle) + .is_some_and(|entity| !entity.common().invisible); + shown == state.visible_blocks.contains(handle) + }) + }) + .unwrap_or(0) +} + +/// After the Block Editor: drop objects that were erased, tag new ones and +/// leave the definition showing its first state. +pub fn finish_editing(document: &mut CadDocument, definition: Handle) { + let Some(handle) = parameter(document, definition).map(|parameter| parameter.handle) else { + return; + }; + let live: Vec = members(document, definition); + edit(document, handle, |parameter| { + parameter.all_blocks.retain(|handle| live.contains(handle)); + for state in &mut parameter.states { + state.visible_blocks.retain(|handle| live.contains(handle)); + } + }); + tag_members(document, definition); + apply_state(document, handle, 0); +} + +impl Scene { + /// Give a reference to a dynamic definition its own evaluated copy, so a + /// state picked for it leaves the definition and the other references + /// alone. Returns the copy's name; a reference already showing a copy, + /// or not dynamic, keeps its block and gets `None`. + pub fn visibility_representation(&mut self, insert: Handle) -> Option { + let Some(EntityType::Insert(reference)) = self.document.get_entity(insert) else { + return None; + }; + let definition = self.document.block_records.get(&reference.block_name)?.handle; + parameter(&self.document, definition)?; + let name = { + let next = self + .document + .block_records + .iter() + .filter_map(|record| record.name.strip_prefix("*U")?.parse::().ok()) + .max() + .map_or(1, |n| n + 1); + format!("*U{next}") + }; + let base = self + .document + .block_records + .iter() + .find(|record| record.handle == definition) + .and_then(|record| match self.document.get_entity(record.block_entity_handle) { + Some(EntityType::Block(block)) => Some(block.base_point), + _ => None, + }) + .unwrap_or(Vector3::ZERO); + let originals = members(&self.document, definition); + let copies: Vec = originals + .iter() + .filter_map(|handle| self.document.get_entity(*handle).cloned()) + .collect(); + self.define_block_raw(&name, base, copies); + let record = self.document.block_records.get_mut(&name)?; + record.flags.anonymous = true; + let copy = record.handle; + // The copies keep their element ids, now naming themselves. + for handle in members(&self.document, copy) { + if let Some(id) = self.document.get_entity(handle).and_then(element_id) { + set_entity_tag(&mut self.document, handle, element_tag(id, handle)); + } + } + let mut tag = ExtendedDataRecord::new(REPRESENTATION_TAG); + tag.add_value(XDataValue::Integer16(1)); + tag.add_value(XDataValue::Handle(definition)); + self.document.set_object_xdata_record(copy, tag); + self.document.ensure_extension_dictionary(copy); + + // The reference: AcDbBlockRepresentation / AcDbRepData names the + // definition it was evaluated from. + let holder = self.document.ensure_extension_dictionary(insert); + let representation = self.document.allocate_handle(); + let data = self.document.allocate_handle(); + let mut dictionary = codec::objects::Dictionary::new(); + dictionary.handle = representation; + dictionary.owner = holder; + dictionary.reactors.push(holder); + dictionary.add_entry("AcDbRepData", data); + self.document + .objects + .insert(representation, ObjectType::Dictionary(dictionary)); + if let Some(ObjectType::Dictionary(holder)) = self.document.objects.get_mut(&holder) { + holder.add_entry("AcDbBlockRepresentation", representation); + } + let mut link = dynamic( + data, + representation, + "ACDB_BLOCKREPRESENTATION_DATA", + "AcDbBlockRepresentationData", + DynamicBlockData::Representation(BlockRepresentationData { + flags: 1, + block: definition, + }), + ); + link.reactors.push(representation); + self.document + .objects + .insert(data, ObjectType::DynamicBlock(link)); + self.document.block_representations.insert(data, definition); + if let Some(EntityType::Insert(reference)) = self.document.get_entity_mut(insert) { + reference.block_name = name.clone(); + } + for record in self.document.block_records.iter_mut() { + if record.handle == definition { + record.insert_handles.retain(|handle| *handle != insert); + } else if record.handle == copy { + record.insert_handles.push(insert); + } + } + self.bump_geometry(); + Some(name) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use codec::entities::{Circle, Insert, Line}; + + /// A block given visibility states round-trips them, and a reference + /// given its own copy still finds the definition and its states. + #[test] + fn states_and_a_reference_copy_survive_dwg_and_dxf() { + let mut scene = Scene::new(); + scene.define_block_raw( + "door", + Vector3::ZERO, + vec![ + EntityType::Line(Line::from_coords(0.0, 0.0, 0.0, 1.0, 0.0, 0.0)), + EntityType::Circle(Circle::from_center_radius(Vector3::new(0.5, 0.5, 0.0), 0.25)), + ], + ); + let definition = scene.document.block_records.get("door").unwrap().handle; + let parameter = create(&mut scene.document, definition, Vector3::ZERO).unwrap(); + let open = add_state(&mut scene.document, parameter, "Open", 0).unwrap(); + let circle = members(&scene.document, definition)[1]; + set_visible(&mut scene.document, parameter, open, &[circle], false); + finish_editing(&mut scene.document, definition); + + let insert = scene.add_entity(EntityType::Insert(Insert::new("door", Vector3::ZERO))); + let copy = scene.visibility_representation(insert).expect("a copy"); + assert!(copy.starts_with("*U")); + + for ext in ["dwg", "dxf"] { + let bytes = crate::io::save_to_bytes(&scene.document, ext, scene.document.version) + .unwrap_or_else(|error| panic!("save {ext}: {error}")); + let read = crate::io::load_bytes(&format!("door.{ext}"), bytes) + .unwrap_or_else(|error| panic!("load {ext}: {error}")); + let insert = read + .entities() + .find_map(|entity| match entity { + EntityType::Insert(insert) if insert.block_name.starts_with("*U") => { + Some(insert.common.handle) + } + _ => None, + }) + .unwrap_or_else(|| panic!("{ext}: reference to the copy")); + let (found, parameter) = read + .dynamic_visibility_for_insert(insert) + .unwrap_or_else(|| panic!("{ext}: no visibility parameter")); + let record = read.block_records.iter().find(|r| r.handle == found).unwrap(); + assert_eq!(record.name, "door", "{ext}"); + let names: Vec<&str> = parameter.states.iter().map(|s| s.name.as_str()).collect(); + assert_eq!(names, ["VisibilityState0", "Open"], "{ext}"); + assert_eq!(parameter.states[1].visible_blocks.len(), 0, "{ext}"); + } + } +} diff --git a/src/scene/mod.rs b/src/scene/mod.rs index 8dbfd5428..208296bb7 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -7,6 +7,7 @@ // cache — block-definition and property caches pub mod annotative; pub mod assoc_array; +pub mod block_visibility; pub mod compare; pub mod section_cull; pub mod sub_select; @@ -2123,6 +2124,9 @@ pub struct Scene { /// frozen-layer signature. Invalidated only when a light entity changes; /// ordinary geometry edits never rescan the drawing's lights. lighting_cache: RefCell>>, + /// The visibility state each dynamic block shows in the Block Editor, + /// by block record. + pub visibility_states: HashMap, /// Currently selected entity handles. pub selected: HashSet, /// Polylines only part of which is selected (Ctrl+click): the picked @@ -2772,6 +2776,7 @@ impl Scene { last_created_dimension: None, object_data_cache: crate::entities::object_data::ObjectDataCache::default(), lighting_cache: RefCell::new(HashMap::default()), + visibility_states: HashMap::default(), selected: HashSet::default(), sub_segments: HashMap::default(), selected_order: Vec::new(),