feat(blocks): implement WBLOCK dialog and unify dialog buttons and radio builders

This commit is contained in:
Karim Jerbi committed 2026-09-24 16:44:24 +01:00
1 parent dddf60d79e
commit 1a6279e841
33 files changed
+1308 -45

No files matched your search

+10
View File
@@ -2316,6 +2316,15 @@ common =
.specify-on-screen = تحديد على الشاشة
.to-layout = مع التخطيط
.us-survey-feet = أقدام مسح أمريكية
.entire-drawing = الرسم بالكامل
.delete-from-drawing = حذف من الرسم
.destination = الوجهة
.file-name-and-path = اسم الملف والمسار:
.insert-units-label = وحدات الإدراج:
.wblock-err-no-block-name = يرجى تحديد أو إدخال اسم الكتلة.
.wblock-err-block-not-found = الكتلة "__ocs_fmt_0__" غير موجودة في الرسم.
.wblock-err-no-path = يرجى تحديد اسم الملف والمسار.
.wblock-help-info = يقوم WBLOCK بكتابة كائنات أو كتلة أو الرسم بالكامل إلى ملف رسم جديد.
errors =
.save-warning = تحذير الحفظ
.unable-to-save-drawing = تعذر حفظ الرسم
@@ -3824,6 +3833,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK حدد الكائنات:
.block-specify-insertion-base-point = BLOCK حدد نقطة قاعدة الإدراج:
.wblock-specify-insertion-base-point = WBLOCK حدد نقطة أساس الإدراج:
model =
.create = إنشاء
.boolean = عمليات منطقية
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Указване на екрана
.to-layout = с оформлението
.us-survey-feet = Геодезически фута (САЩ)
.entire-drawing = Цял чертеж
.delete-from-drawing = Изтриване от чертежа
.destination = Местоназначение
.file-name-and-path = Име и път на файла:
.insert-units-label = Мерни единици за вмъкване:
.wblock-err-no-block-name = Моля, изберете или въведете име на блок.
.wblock-err-block-not-found = Блокът "__ocs_fmt_0__" не съществува в чертежа.
.wblock-err-no-path = Моля, задайте име и път на файла.
.wblock-help-info = WBLOCK записва обекти, блок или целия чертеж в нов чертожен файл.
errors =
.save-warning = Предупреждение при запазване
.unable-to-save-drawing = Чертежът не може да бъде запазен
@@ -3811,6 +3820,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Изберете обекти:
.block-specify-insertion-base-point = BLOCK Укажете базова точка на вмъкване:
.wblock-specify-insertion-base-point = WBLOCK Задайте базова точка на вмъкване:
model =
.create = Създаване
.boolean = Булеви операции
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Zadat na obrazovce
.to-layout = rozvržení
.us-survey-feet = Zeměměřické stopy (USA)
.entire-drawing = Celý výkres
.delete-from-drawing = Vymazat z výkresu
.destination = Cíl
.file-name-and-path = Název souboru a cesta:
.insert-units-label = Jednotky vložení:
.wblock-err-no-block-name = Vyberte nebo zadejte název bloku.
.wblock-err-block-not-found = Blok "__ocs_fmt_0__" ve výkresu neexistuje.
.wblock-err-no-path = Zadejte název souboru a cestu.
.wblock-help-info = WBLOCK zapíše objekty, blok nebo celý výkres do nového výkresového souboru.
errors =
.save-warning = Uložit varování
.unable-to-save-drawing = Nákres nelze uložit
@@ -3809,6 +3818,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Vyberte objekty:
.block-specify-insertion-base-point = BLOCK Zadejte základní bod vložení:
.wblock-specify-insertion-base-point = WBLOCK Určete základní bod vložení:
model =
.create = Vytvořit
.boolean = Booleovské operace
+10
View File
@@ -2304,6 +2304,15 @@ common =
.specify-on-screen = Am Bildschirm bestimmen
.to-layout = an Layout
.us-survey-feet = US-Vermessungsfuß
.entire-drawing = Gesamte Zeichnung
.delete-from-drawing = Aus Zeichnung löschen
.destination = Ziel
.file-name-and-path = Dateiname und Pfad:
.insert-units-label = Einfüge-Einheiten:
.wblock-err-no-block-name = Bitte wählen Sie einen Blocknamen aus oder geben Sie ihn ein.
.wblock-err-block-not-found = Block "__ocs_fmt_0__" ist in der Zeichnung nicht vorhanden.
.wblock-err-no-path = Bitte geben Sie einen Dateinamen und Pfad an.
.wblock-help-info = WBLOCK schreibt Objekte, einen Block oder die gesamte Zeichnung in eine neue Zeichnungsdatei.
errors =
.save-warning = Warnhinweise speichern
.unable-to-save-drawing = Zeichnung konnte nicht gespeichert werden
@@ -3810,6 +3819,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Objekte auswählen:
.block-specify-insertion-base-point = BLOCK Einfügebasispunkt angeben:
.wblock-specify-insertion-base-point = WBLOCK Basispunkt für Einfügung angeben:
model =
.create = Erstellen
.boolean = Boolesche Operationen
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Καθορισμός στην οθόνη
.to-layout = με τη διάταξη
.us-survey-feet = Τοπογραφικά πόδια ΗΠΑ
.entire-drawing = Ολόκληρο σχέδιο
.delete-from-drawing = Διαγραφή από το σχέδιο
.destination = Προορισμός
.file-name-and-path = Όνομα και διαδρομή αρχείου:
.insert-units-label = Μονάδες εισαγωγής:
.wblock-err-no-block-name = Επιλέξτε ή πληκτρολογήστε ένα όνομα μπλοκ.
.wblock-err-block-not-found = Το μπλοκ "__ocs_fmt_0__" δεν υπάρχει στο σχέδιο.
.wblock-err-no-path = Καθορίστε ένα όνομα και διαδρομή αρχείου.
.wblock-help-info = Η εντολή WBLOCK εγγράφει αντικείμενα, ένα μπλοκ ή ολόκληρο το σχέδιο σε ένα νέο αρχείο σχεδίου.
errors =
.save-warning = Προειδοποίηση αποθήκευσης
.unable-to-save-drawing = Αδυναμία αποθήκευσης σχεδίου
@@ -3812,6 +3821,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Επιλέξτε αντικείμενα:
.block-specify-insertion-base-point = BLOCK Καθορίστε σημείο βάσης εισαγωγής:
.wblock-specify-insertion-base-point = WBLOCK Καθορίστε σημείο βάσης εισαγωγής:
model =
.create = Δημιουργία
.boolean = Δυαδική πράξη
+11
View File
@@ -2304,6 +2304,16 @@ common =
.specify-on-screen = Specify On-screen
.to-layout = to layout
.us-survey-feet = US Survey Feet
.entire-drawing = Entire drawing
.delete-from-drawing = Delete from drawing
.destination = Destination
.file-name-and-path = File name and path:
.insert-units-label = Insert units:
.wblock-err-no-block-name = Please select or enter a block name.
.wblock-err-block-not-found = Block "__ocs_fmt_0__" does not exist in drawing.
.wblock-err-no-path = Please specify a file name and path.
.wblock-err-current-drawing = Cannot write to the current drawing file.
.wblock-help-info = WBLOCK writes objects, a block, or the entire drawing to a new drawing file.
errors =
.save-warning = Save Warning
.unable-to-save-drawing = Unable to Save Drawing
@@ -3813,6 +3823,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Select objects:
.block-specify-insertion-base-point = BLOCK Specify insertion base point:
.wblock-specify-insertion-base-point = WBLOCK Specify insertion base point:
model =
.create = Create
.boolean = Boolean
+10
View File
@@ -2304,6 +2304,15 @@ common =
.specify-on-screen = Precisar en pantalla
.to-layout = a la presentación
.us-survey-feet = Pies de agrimensura de EE. UU.
.entire-drawing = Dibujo completo
.delete-from-drawing = Suprimir del dibujo
.destination = Destino
.file-name-and-path = Nombre de archivo y ruta:
.insert-units-label = Unidades de inserción:
.wblock-err-no-block-name = Seleccione o escriba un nombre de bloque.
.wblock-err-block-not-found = El bloque "__ocs_fmt_0__" no existe en el dibujo.
.wblock-err-no-path = Especifique un nombre de archivo y una ruta.
.wblock-help-info = WBLOCK escribe objetos, un bloque o el dibujo completo en un nuevo archivo de dibujo.
errors =
.save-warning = Advertencia de guardado
.unable-to-save-drawing = No se puede guardar el dibujo
@@ -3812,6 +3821,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Designar objetos:
.block-specify-insertion-base-point = BLOCK Precise punto base de inserción:
.wblock-specify-insertion-base-point = WBLOCK Precise punto base de inserción:
model =
.create = Crear
.boolean = Booleanas
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Määritä näytöltä
.to-layout = asetteluun
.us-survey-feet = US Survey -jalat
.entire-drawing = Koko piirustus
.delete-from-drawing = Poista piirustuksesta
.destination = Kohde
.file-name-and-path = Tiedoston nimi ja polku:
.insert-units-label = Lisäysyksiköt:
.wblock-err-no-block-name = Valitse tai anna lohkon nimi.
.wblock-err-block-not-found = Lohkoa "__ocs_fmt_0__" ei ole piirustuksessa.
.wblock-err-no-path = Määritä tiedoston nimi ja polku.
.wblock-help-info = WBLOCK kirjoittaa kohteet, lohkon tai koko piirustuksen uuteen piirustustiedostoon.
errors =
.save-warning = Tallenna varoitus
.unable-to-save-drawing = Piirustusta ei voi tallentaa
@@ -3811,6 +3820,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Valitse kohteet:
.block-specify-insertion-base-point = BLOCK Määritä sijoituksen peruspiste:
.wblock-specify-insertion-base-point = WBLOCK Määritä lisäyksen peruspiste:
model =
.create = Luo
.boolean = Boolen operaatiot
+10
View File
@@ -2304,6 +2304,15 @@ common =
.specify-on-screen = Spécifier à l'écran
.to-layout = sur la présentation
.us-survey-feet = Pieds d'arpentage US
.entire-drawing = Dessin entier
.delete-from-drawing = Supprimer du dessin
.destination = Destination
.file-name-and-path = Nom de fichier et chemin d'accès :
.insert-units-label = Unités d'insertion :
.wblock-err-no-block-name = Veuillez sélectionner ou entrer un nom de bloc.
.wblock-err-block-not-found = Le bloc « __ocs_fmt_0__ » n'existe pas dans le dessin.
.wblock-err-no-path = Veuillez spécifier un nom de fichier et un chemin d'accès.
.wblock-help-info = WBLOCK écrit des objets, un bloc ou l'ensemble du dessin dans un nouveau fichier de dessin.
errors =
.save-warning = Enregistrer l' avertissement
.unable-to-save-drawing = Impossible de sauvegarder le dessin
@@ -3812,6 +3821,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Sélectionnez les objets :
.block-specify-insertion-base-point = BLOCK Spécifiez le point de base d'insertion :
.wblock-specify-insertion-base-point = WBLOCK Spécifiez le point de base d'insertion :
model =
.create = Créer
.boolean = Opérations booléennes
+10
View File
@@ -2292,6 +2292,15 @@ common =
.specify-on-screen = स्क्रीन पर निर्दिष्ट करें
.to-layout = लेआउट के अनुसार
.us-survey-feet = यूएस सर्वेक्षण फीट
.entire-drawing = संपूर्ण रेखाचित्र
.delete-from-drawing = रेखाचित्र से हटाएं
.destination = गंतव्य
.file-name-and-path = फ़ाइल का नाम और पथ:
.insert-units-label = सम्मिलन इकाइयाँ:
.wblock-err-no-block-name = कृपया एक ब्लॉक नाम चुनें या दर्ज करें।
.wblock-err-block-not-found = ब्लॉक "__ocs_fmt_0__" रेखाचित्र में मौजूद नहीं है।
.wblock-err-no-path = कृपया फ़ाइल का नाम और पथ निर्दिष्ट करें।
.wblock-help-info = WBLOCK ऑब्जेक्ट, एक ब्लॉक, या संपूर्ण रेखाचित्र को एक नई रेखाचित्र फ़ाइल में लिखता है।
errors =
.save-warning = चेतावनी सहेजें
.unable-to-save-drawing = ड्रॉइंग सहेजी नहीं जा सकी
@@ -3800,6 +3809,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK ऑब्जेक्ट चुनें:
.block-specify-insertion-base-point = BLOCK सम्मिलन आधार बिंदु निर्दिष्ट करें:
.wblock-specify-insertion-base-point = WBLOCK सम्मिलन आधार बिंदु निर्दिष्ट करें:
model =
.create = बनाएँ
.boolean = बूलियन संक्रियाएँ
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Megadás a képernyőn
.to-layout = az elrendezéshez
.us-survey-feet = USA geodéziai láb
.entire-drawing = Teljes rajz
.delete-from-drawing = Törlés a rajzból
.destination = Cél
.file-name-and-path = Fájlnév és elérési út:
.insert-units-label = Beillesztési mértékegységek:
.wblock-err-no-block-name = Válasszon vagy adjon meg egy blokknevet.
.wblock-err-block-not-found = A(z) "__ocs_fmt_0__" blokk nem létezik a rajzban.
.wblock-err-no-path = Adja meg a fájlnevet és az elérési utat.
.wblock-help-info = A WBLOCK objektumokat, egy blokkot vagy a teljes rajzot új rajzfájlba menti.
errors =
.save-warning = Figyelmeztetés mentése
.unable-to-save-drawing = Nem sikerült menteni a rajzot
@@ -3811,6 +3820,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Válasszon objektumokat:
.block-specify-insertion-base-point = BLOCK Adja meg a beillesztési bázispontot:
.wblock-specify-insertion-base-point = WBLOCK Adja meg a beillesztési bázispontot:
model =
.create = Létrehozás
.boolean = Logikai műveletek
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Specifica sullo schermo
.to-layout = al layout
.us-survey-feet = Piedi topografici USA
.entire-drawing = Intero disegno
.delete-from-drawing = Elimina dal disegno
.destination = Destinazione
.file-name-and-path = Nome file e percorso:
.insert-units-label = Unità di inserimento:
.wblock-err-no-block-name = Selezionare o immettere il nome di un blocco.
.wblock-err-block-not-found = Il blocco "__ocs_fmt_0__" non esiste nel disegno.
.wblock-err-no-path = Specificare un nome file e un percorso.
.wblock-help-info = WBLOCK scrive gli oggetti, un blocco o l'intero disegno in un nuovo file di disegno.
errors =
.save-warning = Salva avviso
.unable-to-save-drawing = Impossibile salvare il disegno
@@ -3811,6 +3820,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Selezionare oggetti:
.block-specify-insertion-base-point = BLOCK Specificare punto base di inserimento:
.wblock-specify-insertion-base-point = WBLOCK Specificare il punto base di inserimento:
model =
.create = Crea
.boolean = Operazioni booleane
+10
View File
@@ -2294,6 +2294,15 @@ common =
.specify-on-screen = 画面上で指定
.to-layout = レイアウトに合わせる
.us-survey-feet = 米国測量フィート
.entire-drawing = 図面全体
.delete-from-drawing = 図面から削除
.destination = 保存先
.file-name-and-path = ファイル名とパス:
.insert-units-label = 挿入単位:
.wblock-err-no-block-name = ブロック名を選択または入力してください。
.wblock-err-block-not-found = ブロック "__ocs_fmt_0__" は図面に存在しません。
.wblock-err-no-path = ファイル名とパスを指定してください。
.wblock-help-info = WBLOCK はオブジェクト、ブロック、または図面全体を新しい図面ファイルに書き出します。
errors =
.save-warning = 保存の警告
.unable-to-save-drawing = 図面を保存できません
@@ -3800,6 +3809,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK オブジェクトを選択:
.block-specify-insertion-base-point = BLOCK 挿入基点を指定:
.wblock-specify-insertion-base-point = WBLOCK 挿入基点を指定:
model =
.create = 作成
.boolean = ブール演算
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = 화면상에 지정
.to-layout = 배치에 맞춤
.us-survey-feet = 미국 측량 피트
.entire-drawing = 도면 전체
.delete-from-drawing = 도면에서 삭제
.destination = 대상
.file-name-and-path = 파일 이름 및 경로:
.insert-units-label = 삽입 단위:
.wblock-err-no-block-name = 블록 이름을 선택하거나 입력하십시오.
.wblock-err-block-not-found = 블록 "__ocs_fmt_0__"이(가) 도면에 존재하지 않습니다.
.wblock-err-no-path = 파일 이름과 경로를 지정하십시오.
.wblock-help-info = WBLOCK은 객체, 블록 또는 도면 전체를 새 도면 파일로 내보냅니다.
errors =
.save-warning = 경고 저장
.unable-to-save-drawing = 도면을 저장할 수 없습니다
@@ -3811,6 +3820,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK 객체 선택:
.block-specify-insertion-base-point = BLOCK 삽입 기준점 지정:
.wblock-specify-insertion-base-point = WBLOCK 삽입 기준점 지정:
model =
.create = 생성
.boolean = 불리언 연산
+10
View File
@@ -2304,6 +2304,15 @@ common =
.specify-on-screen = Op scherm opgeven
.to-layout = aan lay-out
.us-survey-feet = US Survey Feet
.entire-drawing = Volledige tekening
.delete-from-drawing = Verwijderen uit tekening
.destination = Bestemming
.file-name-and-path = Bestandsnaam en pad:
.insert-units-label = Invoegeenheden:
.wblock-err-no-block-name = Selecteer of voer een bloknaam in.
.wblock-err-block-not-found = Blok "__ocs_fmt_0__" bestaat niet in de tekening.
.wblock-err-no-path = Geef een bestandsnaam en pad op.
.wblock-help-info = WBLOCK schrijft objecten, een blok of de volledige tekening naar een nieuw tekeningbestand.
errors =
.save-warning = Waarschuwing opslaan
.unable-to-save-drawing = Kan tekening niet opslaan
@@ -3810,6 +3819,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Objecten selecteren:
.block-specify-insertion-base-point = BLOCK Invoegbasispunt opgeven:
.wblock-specify-insertion-base-point = WBLOCK Geef basisinvoegpunt op:
model =
.create = Maken
.boolean = Booleaanse bewerkingen
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = Określ na ekranie
.to-layout = do układu
.us-survey-feet = Stopy geodezyjne USA
.entire-drawing = Cały rysunek
.delete-from-drawing = Usuń z rysunku
.destination = Cel
.file-name-and-path = Nazwa pliku i ścieżka:
.insert-units-label = Jednostki wstawiania:
.wblock-err-no-block-name = Wybierz lub wprowadź nazwę bloku.
.wblock-err-block-not-found = Blok „__ocs_fmt_0__” nie istnieje w rysunku.
.wblock-err-no-path = Podaj nazwę pliku i ścieżkę.
.wblock-help-info = WBLOCK zapisuje obiekty, blok lub cały rysunek do nowego pliku rysunku.
errors =
.save-warning = Zapisz ostrzeżenie
.unable-to-save-drawing = Nie można zapisać rysunku
@@ -3811,6 +3820,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Wybierz obiekty:
.block-specify-insertion-base-point = BLOCK Określ bazowy punkt wstawienia:
.wblock-specify-insertion-base-point = WBLOCK Określ punkt bazowy wstawienia:
model =
.create = Utwórz
.boolean = Operacje boolowskie
+10
View File
@@ -2304,6 +2304,15 @@ common =
.specify-on-screen = Especificar na tela
.to-layout = ao layout
.us-survey-feet = Pés topográficos dos EUA
.entire-drawing = Desenho inteiro
.delete-from-drawing = Excluir do desenho
.destination = Destino
.file-name-and-path = Nome do arquivo e caminho:
.insert-units-label = Unidades de inserção:
.wblock-err-no-block-name = Selecione ou insira o nome de um bloco.
.wblock-err-block-not-found = O bloco "__ocs_fmt_0__" não existe no desenho.
.wblock-err-no-path = Especifique um nome de arquivo e caminho.
.wblock-help-info = O WBLOCK grava objetos, um bloco ou o desenho inteiro em um novo arquivo de desenho.
errors =
.save-warning = Aviso de salvamento
.unable-to-save-drawing = Não foi possível salvar o desenho
@@ -3812,6 +3821,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Selecionar objetos:
.block-specify-insertion-base-point = BLOCK Especificar ponto base de inserção:
.wblock-specify-insertion-base-point = WBLOCK Especifique o ponto base de inserção:
model =
.create = Criar
.boolean = Operações booleanas
+10
View File
@@ -2307,6 +2307,15 @@ common =
.specify-on-screen = Указать на экране
.to-layout = с листом
.us-survey-feet = Геодезические футы США
.entire-drawing = Весь чертеж
.delete-from-drawing = Удалить из чертежа
.destination = Место назначения
.file-name-and-path = Имя файла и путь:
.insert-units-label = Единицы вставки:
.wblock-err-no-block-name = Выберите или введите имя блока.
.wblock-err-block-not-found = Блок "__ocs_fmt_0__" отсутствует в чертеже.
.wblock-err-no-path = Укажите имя файла и путь к нему.
.wblock-help-info = WBLOCK записывает объекты, блок или весь чертеж в новый файл чертежа.
errors =
.save-warning = Спасти предостережение
.unable-to-save-drawing = Не удалось сохранить чертёж
@@ -3813,6 +3822,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Выберите объекты:
.block-specify-insertion-base-point = BLOCK Укажите базовую точку вставки:
.wblock-specify-insertion-base-point = WBLOCK Укажите базовую точку вставки:
model =
.create = Создание
.boolean = Булевы операции
+10
View File
@@ -2288,6 +2288,15 @@ common =
.specify-on-screen = Ekranda Belirt
.to-layout = düzene
.us-survey-feet = ABD Ölçüm Fiti
.entire-drawing = Tüm çizim
.delete-from-drawing = Çizimden sil
.destination = Hedef
.file-name-and-path = Dosya adı ve yolu:
.insert-units-label = Ekleme birimleri:
.wblock-err-no-block-name = Lütfen bir blok adı seçin veya girin.
.wblock-err-block-not-found = "__ocs_fmt_0__" bloğu çizimde mevcut değil.
.wblock-err-no-path = Lütfen bir dosya adı ve yolu belirtin.
.wblock-help-info = WBLOCK nesneleri, bir bloğu veya tüm çizimi yeni bir çizim dosyasına yazar.
errors =
.save-warning = Kaydetme Uyarısı
.unable-to-save-drawing = Çizim Kaydedilemiyor
@@ -3772,6 +3781,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK Nesneleri seçin:
.block-specify-insertion-base-point = BLOCK Ekleme taban noktasını belirtin:
.wblock-specify-insertion-base-point = WBLOCK Ekleme taban noktasını belirleyin:
model =
.create = Oluştur
.boolean = Boole işlemleri
+10
View File
@@ -2291,6 +2291,15 @@ common =
.specify-on-screen = 在屏幕上指定
.to-layout = 匹配到布局
.us-survey-feet = 美国测量英尺
.entire-drawing = 整个图形
.delete-from-drawing = 从图形中删除
.destination = 目标
.file-name-and-path = 文件名和路径:
.insert-units-label = 插入单位:
.wblock-err-no-block-name = 请选择或输入块名称。
.wblock-err-block-not-found = 图形中不存在块“__ocs_fmt_0__”。
.wblock-err-no-path = 请指定文件名和路径。
.wblock-help-info = WBLOCK 将对象、块或整个图形写入新的图形文件。
errors =
.save-warning = 保存警告
.unable-to-save-drawing = 无法保存图形
@@ -3795,6 +3804,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK 选择对象:
.block-specify-insertion-base-point = BLOCK 指定插入基点:
.wblock-specify-insertion-base-point = WBLOCK 指定插入基点:
model =
.create = 创建
.boolean = 布尔运算
+10
View File
@@ -2303,6 +2303,15 @@ common =
.specify-on-screen = 在螢幕上指定
.to-layout = 與配置相符
.us-survey-feet = 美國測量英呎
.entire-drawing = 整個圖面
.delete-from-drawing = 從圖面中刪除
.destination = 目標
.file-name-and-path = 檔名和路徑:
.insert-units-label = 插入單位:
.wblock-err-no-block-name = 請選取或輸入圖塊名稱。
.wblock-err-block-not-found = 圖面中不存在圖塊「__ocs_fmt_0__」。
.wblock-err-no-path = 請指定檔名和路徑。
.wblock-help-info = WBLOCK 將物件、圖塊或整個圖面寫入新的圖面檔案。
errors =
.save-warning = 儲存警告
.unable-to-save-drawing = 無法儲存圖形
@@ -3807,6 +3816,7 @@ insert =
.xref = XREF: __ocs_fmt_0__
.block-select-objects = BLOCK 選取物件:
.block-specify-insertion-base-point = BLOCK 指定插入基準點:
.wblock-specify-insertion-base-point = WBLOCK 指定插入基準點:
model =
.create = 建立
.boolean = 布林運算
+90 -4
View File
@@ -460,6 +460,41 @@ impl OpenCADStudio {
self.active_modal = Some(crate::app::ModalKind::BlockDefinition);
}
cmd if cmd.starts_with("WBLOCK_POINT_PICKED ") => {
let parts: Vec<f64> = cmd["WBLOCK_POINT_PICKED ".len()..]
.split_whitespace()
.filter_map(|s| s.parse::<f64>().ok())
.collect();
if parts.len() == 3 {
if let Some(state) = self.wblock.as_mut() {
state.base_point_x = format!("{:.4}", parts[0]);
state.base_point_y = format!("{:.4}", parts[1]);
state.base_point_z = format!("{:.4}", parts[2]);
}
}
self.tabs[i].active_cmd = None;
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
}
"WBLOCK_POINT_CANCELLED" => {
self.tabs[i].active_cmd = None;
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
}
"WBLOCK_OBJECTS_GATHERED" => {
let handles: Vec<_> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
.map(|(h, _)| h)
.collect();
if let Some(state) = self.wblock.as_mut() {
state.selected_handles = handles;
}
self.tabs[i].active_cmd = None;
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
}
"INSERT" => {
let blocks = self.tabs[i].scene.custom_block_names();
if blocks.is_empty() {
@@ -663,9 +698,60 @@ impl OpenCADStudio {
"PDFATTACH" => {
return Some(Task::done(Message::PdfAttachPick));
}
cmd if cmd == "WBLOCK" || cmd == "WB" || cmd.starts_with("WBLOCK ") => {
let arg = cmd.splitn(2, ' ').nth(1).unwrap_or("").trim();
if arg.is_empty() {
"XATTACH" => {
// Launch the file picker; XAttachPickResult will start the command.
return Some(Task::done(Message::XAttachPick));
}
"WBLOCK" | "WB" => {
let handles: Vec<_> = self.tabs[i]
.scene
.selected_entities()
.into_iter()
.map(|(h, _)| h)
.collect();
let existing_blocks = self.tabs[i].scene.custom_block_names();
let default_unit = self.tabs[i].scene.document.header.insertion_units;
let default_folder = self.tabs[i]
.current_path
.as_ref()
.and_then(|p| p.parent())
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_default();
let default_path = if default_folder.is_empty() {
"new_block.dwg".to_string()
} else {
format!("{}/new_block.dwg", default_folder.replace('\\', "/"))
};
self.wblock = Some(
crate::ui::window::wblock::WblockState::new(
existing_blocks,
handles,
default_unit,
default_path,
),
);
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
}
cmd if cmd == "-WBLOCK"
|| cmd == "-WB"
|| cmd.starts_with("-WBLOCK ")
|| cmd.starts_with("-WB ")
|| cmd.starts_with("WBLOCK ")
|| cmd.starts_with("WB ") =>
{
let rest = if let Some(r) = cmd.strip_prefix("-WBLOCK") {
r.trim()
} else if let Some(r) = cmd.strip_prefix("-WB") {
r.trim()
} else if let Some(r) = cmd.strip_prefix("WBLOCK") {
r.trim()
} else if let Some(r) = cmd.strip_prefix("WB") {
r.trim()
} else {
""
};
if rest.is_empty() {
// No argument: use selected entities (*) if any, else ask.
let sel: Vec<_> = self.tabs[i].scene.selected.iter().copied().collect();
if sel.is_empty() {
@@ -676,7 +762,7 @@ impl OpenCADStudio {
return Some(Task::done(Message::WblockSave("*".to_string())));
}
} else {
return Some(Task::done(Message::WblockSave(arg.to_string())));
return Some(Task::done(Message::WblockSave(rest.to_string())));
}
}
+22
View File
@@ -495,6 +495,8 @@ pub(super) struct OpenCADStudio {
block_definition: Option<crate::ui::window::block_definition::BlockDefinitionState>,
/// Working copy of the Attach External Reference dialog; None while closed.
xref_attach: Option<crate::ui::window::xref_attach::XrefAttachState>,
/// Working copy of the Write Block (WBLOCK) dialog; `None` while it is closed.
wblock: Option<crate::ui::window::wblock::WblockState>,
/// Working copy of the structured feature-control-frame editor.
geometric_tolerance: Option<crate::ui::window::geometric_tolerance::State>,
/// PICKDRAG (#226): false (default) = press-drag lassoes; true =
@@ -1883,6 +1885,7 @@ pub enum ModalKind {
DrawingUnits,
BlockDefinition,
XrefAttach,
WriteBlock,
GeometricTolerance,
DraftingSettings,
AutoConstrainSettings,
@@ -2880,6 +2883,24 @@ pub enum Message {
BlockDefConfirmRedefine(bool),
BlockDefDismissError,
BlockDefHelp,
/// Write Block (WBLOCK) dialog messages
WblockSourceMode(crate::ui::window::wblock::WblockSourceMode),
WblockBlockName(String),
WblockBlockSelect(String),
WblockPickPoint,
WblockBaseX(String),
WblockBaseY(String),
WblockBaseZ(String),
WblockSelectObjects,
WblockQuickSelect,
WblockObjectMode(crate::ui::window::wblock::WblockObjectMode),
WblockFilePath(String),
WblockBrowsePath,
WblockBrowsePathResult(Option<std::path::PathBuf>),
WblockUnit(i16),
WblockApply,
WblockDismissError,
WblockHelp,
/// One structured feature-control-frame field changed.
ToleranceDialogField(crate::ui::window::geometric_tolerance::Field),
/// One structured feature-control-frame option changed.
@@ -3962,6 +3983,7 @@ impl OpenCADStudio {
drawing_units: None,
block_definition: None,
xref_attach: None,
wblock: None,
geometric_tolerance: None,
pick_drag_rect: false,
perf_hud: false,
+2
View File
@@ -1163,6 +1163,8 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
self.reset_modal_geometry();
if self.block_definition.is_some() {
self.active_modal = Some(crate::app::ModalKind::BlockDefinition);
} else if self.wblock.is_some() {
self.active_modal = Some(crate::app::ModalKind::WriteBlock);
}
return Task::none();
}
+315
View File
@@ -209,6 +209,7 @@ impl OpenCADStudio {
Some(GeometricTolerance) => self.geometric_tolerance = None,
Some(BlockDefinition) => self.block_definition = None,
Some(XrefAttach) => self.xref_attach = None,
Some(WriteBlock) => self.wblock = None,
Some(Hyperlink) => {
self.hyperlink_editor_handles.clear();
self.hyperlink_editor_url.clear();
@@ -346,6 +347,14 @@ impl OpenCADStudio {
}
}
}
if self.active_modal == Some(super::ModalKind::WriteBlock) {
if let Some(ref mut state) = self.wblock {
if state.error_message.is_some() {
state.error_message = None;
return Task::none();
}
}
}
return self.update(Message::CloseModal);
}
if self.active_modal == Some(super::ModalKind::BlockDefinition) {
@@ -362,6 +371,13 @@ impl OpenCADStudio {
}
}
}
if self.active_modal == Some(super::ModalKind::WriteBlock) {
if matches!(msg, Message::CommandFinalize)
|| matches!(&msg, Message::ShortcutPressed(key) if key.rsplit('+').next() == Some("ENTER") || key.rsplit('+').next() == Some("RETURN"))
{
return self.update(Message::WblockApply);
}
}
if is_modal_blocked_key_msg(&msg) {
return Task::none();
}
@@ -4950,6 +4966,294 @@ impl OpenCADStudio {
}
self.commit_block_definition(false)
}
Message::WblockSourceMode(mode) => {
if let Some(state) = self.wblock.as_mut() {
state.source_mode = mode;
}
Task::none()
}
Message::WblockBlockName(name) => {
if let Some(state) = self.wblock.as_mut() {
state.block_name = name;
}
Task::none()
}
Message::WblockBlockSelect(name) => {
if let Some(state) = self.wblock.as_mut() {
state.block_name = name.clone();
let trimmed = name.trim();
if !trimmed.is_empty() {
let current_path = std::path::Path::new(&state.file_path);
let file_stem = current_path.file_stem().and_then(|s| s.to_str());
let is_default_or_block = file_stem == Some("new_block")
|| state.existing_blocks.iter().any(|b| Some(b.as_str()) == file_stem);
if is_default_or_block {
let new_file_name = format!("{}.dwg", trimmed);
if let Some(parent) = current_path.parent() {
if !parent.as_os_str().is_empty() {
state.file_path = parent.join(new_file_name).to_string_lossy().to_string();
} else {
state.file_path = new_file_name;
}
} else {
state.file_path = new_file_name;
}
}
}
}
Task::none()
}
Message::WblockPickPoint => {
self.active_modal = None;
let cmd = crate::modules::insert::wblock::WblockPickBasePointCommand;
self.command_line.push_info(&cmd.prompt());
self.tabs[self.active_tab].active_cmd = Some(Box::new(cmd));
Task::none()
}
Message::WblockBaseX(val) => {
if let Some(state) = self.wblock.as_mut() {
state.base_point_x = val;
}
Task::none()
}
Message::WblockBaseY(val) => {
if let Some(state) = self.wblock.as_mut() {
state.base_point_y = val;
}
Task::none()
}
Message::WblockBaseZ(val) => {
if let Some(state) = self.wblock.as_mut() {
state.base_point_z = val;
}
Task::none()
}
Message::WblockSelectObjects => {
self.active_modal = None;
use crate::modules::draw::select::SelectObjectsCommand;
let cmd = SelectObjectsCommand::plain("WBLOCK", "WBLOCK_OBJECTS_GATHERED");
self.command_line.push_info(&cmd.prompt());
self.tabs[self.active_tab].active_cmd = Some(Box::new(cmd));
Task::none()
}
Message::WblockQuickSelect => {
self.active_modal = None;
self.on_qselect_open()
}
Message::WblockObjectMode(mode) => {
if let Some(state) = self.wblock.as_mut() {
state.object_mode = mode;
}
Task::none()
}
Message::WblockFilePath(path) => {
if let Some(state) = self.wblock.as_mut() {
state.file_path = path;
}
Task::none()
}
Message::WblockBrowsePath => {
let default_name = if let Some(state) = self.wblock.as_ref() {
let p = std::path::Path::new(&state.file_path);
p.file_name()
.and_then(|f| f.to_str())
.unwrap_or("new_block.dwg")
.to_string()
} else {
"new_block.dwg".to_string()
};
Task::perform(
async move {
let path = crate::sys::file_dialog()
.set_title(crate::t!("Save Block As").as_ref())
.set_file_name(&default_name)
.add_filter(crate::t!("DWG Files").as_ref(), &["dwg"])
.add_filter(crate::t!("DXF Files").as_ref(), &["dxf"])
.save_file()
.await
.map(|h| crate::sys::handle_path(&h));
path
},
|path| Message::WblockBrowsePathResult(path),
)
}
Message::WblockBrowsePathResult(opt_path) => {
if let Some(path) = opt_path {
if let Some(state) = self.wblock.as_mut() {
state.file_path = path.to_string_lossy().to_string();
}
}
Task::none()
}
Message::WblockUnit(unit) => {
if let Some(state) = self.wblock.as_mut() {
state.unit = unit;
}
Task::none()
}
Message::WblockDismissError => {
if let Some(state) = self.wblock.as_mut() {
state.error_message = None;
}
Task::none()
}
Message::WblockHelp => {
self.command_line.push_info(
crate::t!("WBLOCK writes objects, a block, or the entire drawing to a new drawing file.").as_ref(),
);
Task::none()
}
Message::WblockApply => {
let Some(state) = self.wblock.as_mut() else {
return Task::none();
};
use crate::ui::window::wblock::{WblockObjectMode, WblockSourceMode};
match state.source_mode {
WblockSourceMode::Block => {
let name = state.block_name.trim();
if name.is_empty() {
state.error_message =
Some(crate::t!("Please select or enter a block name.").into_owned());
return Task::none();
}
let i = self.active_tab;
if self.tabs[i].scene.document.block_records.get(name).is_none() {
state.error_message = Some(
crate::tf!("Block \"{}\" does not exist in drawing.", name)
.into_owned(),
);
return Task::none();
}
}
WblockSourceMode::Objects => {
if state.selected_handles.is_empty() {
state.error_message = Some(
crate::t!("No objects selected. You must select objects to define a block.")
.into_owned(),
);
return Task::none();
}
}
WblockSourceMode::EntireDrawing => {}
}
let path_str = state.file_path.trim().to_string();
if path_str.is_empty() {
state.error_message =
Some(crate::t!("Please specify a file name and path.").into_owned());
return Task::none();
}
let mut path = std::path::PathBuf::from(path_str);
if path.extension().is_none() {
path.set_extension("dwg");
}
let i = self.active_tab;
if let Some(current_path) = self.tabs[i].current_path.as_ref() {
if current_path == &path {
state.error_message = Some(
crate::t!("Cannot write to the current drawing file.").into_owned(),
);
return Task::none();
}
}
let state = self.wblock.take().unwrap();
self.active_modal = None;
let document = self.tabs[i].scene.document_for_save();
let source_mode = state.source_mode;
let block_name = state.block_name.trim().to_string();
let handles = state.selected_handles.clone();
let base_point = state.parse_base_point();
let unit = state.unit;
let object_mode = state.object_mode;
if source_mode == WblockSourceMode::Objects {
match object_mode {
WblockObjectMode::Retain => {}
WblockObjectMode::Delete => {
self.push_undo_snapshot(i, "WBLOCK");
self.tabs[i].scene.erase_entities(&handles);
self.tabs[i].dirty = true;
self.tabs[i].scene.bump_geometry();
self.refresh_properties();
}
WblockObjectMode::Convert => {
let block_name_for_conv = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("WBLOCK")
.to_string();
self.push_undo_snapshot(i, "WBLOCK");
let ucs = self.tabs[i].ucs_xform();
let world_to_block = ucs.to_ucs_transform_at(base_point);
let block_to_world = ucs.to_wcs_transform_at(base_point);
let options = crate::scene::CreateBlockOptions {
name: block_name_for_conv,
handles: handles.clone(),
base_point,
world_to_block,
block_to_world,
mode: crate::ui::window::block_definition::BlockObjectMode::Convert,
annotative: false,
match_orientation: false,
scale_uniformly: false,
allow_exploding: true,
unit,
description: String::new(),
hyperlink_url: String::new(),
hyperlink_desc: String::new(),
redefine: true,
};
let _ = self.tabs[i].scene.create_block_with_options(options);
self.tabs[i].dirty = true;
self.tabs[i].scene.bump_geometry();
self.refresh_properties();
}
}
}
let worker_path = path.clone();
let display_name = match source_mode {
WblockSourceMode::Block => block_name.clone(),
WblockSourceMode::EntireDrawing => "*".to_string(),
WblockSourceMode::Objects => "*".to_string(),
};
let worker_display = display_name.clone();
file::background_task(
move || {
let out_doc = match source_mode {
WblockSourceMode::Block => {
let mut d = crate::modules::insert::wblock::extract_block_to_doc(
&document,
&block_name,
)
.map_err(|e| e.to_string())?;
d.header.insertion_units = unit;
d
}
WblockSourceMode::EntireDrawing => {
let mut d = document.clone();
d.header.insertion_units = unit;
d
}
WblockSourceMode::Objects => {
crate::modules::insert::wblock::extract_entities_to_doc_with_base(
&document,
&handles,
base_point,
unit,
)
.map_err(|e| e.to_string())?
}
};
crate::io::save(&out_doc, &worker_path).map_err(|e| e.to_string())
},
move |result| Message::WblockWriteFinished(worker_display, path, result),
)
}
Message::ToleranceDialogField(field) => {
if let Some(state) = self.geometric_tolerance.as_mut() {
state.apply_field(field);
@@ -5904,6 +6208,8 @@ impl OpenCADStudio {
self.reset_modal_geometry();
if self.block_definition.is_some() {
self.active_modal = Some(super::ModalKind::BlockDefinition);
} else if self.wblock.is_some() {
self.active_modal = Some(super::ModalKind::WriteBlock);
}
Task::none()
}
@@ -6110,6 +6416,15 @@ impl OpenCADStudio {
.collect();
block_def.error_message = None;
self.active_modal = Some(super::ModalKind::BlockDefinition);
} else if let Some(ref mut wblock) = self.wblock {
wblock.selected_handles = self.tabs[i]
.scene
.selected_entities()
.into_iter()
.map(|(h, _)| h)
.collect();
wblock.error_message = None;
self.active_modal = Some(super::ModalKind::WriteBlock);
}
Task::none()
}
+7
View File
@@ -25,6 +25,7 @@ impl OpenCADStudio {
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
Some(K::BlockDefinition) => crate::t!("Block Definition").into_owned(),
Some(K::XrefAttach) => crate::t!("Attach External Reference").into_owned(),
Some(K::WriteBlock) => crate::t!("Write Block").into_owned(),
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
Some(K::AutoConstrainSettings) => crate::t!("Constraint Settings").into_owned(),
@@ -514,6 +515,12 @@ impl OpenCADStudio {
crate::ui::window::xref_attach::view_window(state, flow)
})
}
super::super::ModalKind::WriteBlock => {
let state = self.wblock.as_ref()?;
sized_flow(ex, 440, 395, |flow| {
crate::ui::window::wblock::view_window(state, flow)
})
}
super::super::ModalKind::GeometricTolerance => {
let state = self.geometric_tolerance.as_ref()?;
sized_flow(ex, 670, 530, |flow| {
+11
View File
@@ -3,6 +3,17 @@
pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static str)> {
match source {
"Entire drawing" => Some(("common", "entire-drawing")),
"Delete from drawing" => Some(("common", "delete-from-drawing")),
"Destination" => Some(("common", "destination")),
"File name and path:" => Some(("common", "file-name-and-path")),
"Insert units:" => Some(("common", "insert-units-label")),
"Please select or enter a block name." => Some(("common", "wblock-err-no-block-name")),
"Block \"{}\" does not exist in drawing." => Some(("common", "wblock-err-block-not-found")),
"Please specify a file name and path." => Some(("common", "wblock-err-no-path")),
"Cannot write to the current drawing file." => Some(("common", "wblock-err-current-drawing")),
"WBLOCK writes objects, a block, or the entire drawing to a new drawing file." => Some(("common", "wblock-help-info")),
"WBLOCK Specify insertion base point:" => Some(("insert", "wblock-specify-insertion-base-point")),
"Auto Constrain" => Some(("common", "auto-constrain")),
"Delete Constraints" => Some(("common", "delete-constraints")),
"Dimensional" => Some(("common", "dimensional")),
+106
View File
@@ -157,3 +157,109 @@ pub fn normalize_to_origin(out: &mut CadDocument) {
crate::scene::view::dispatch::apply_transform(entity, &shift);
}
}
/// One-shot point picker for the Write Block dialog's "Pick point" button.
pub struct WblockPickBasePointCommand;
impl crate::command::CadCommand for WblockPickBasePointCommand {
fn name(&self) -> &'static str {
"WBLOCK"
}
fn prompt(&self) -> String {
crate::t!("WBLOCK Specify insertion base point:").into_owned()
}
fn on_point(&mut self, pt: glam::DVec3) -> crate::command::CmdResult {
crate::command::CmdResult::Dispatch(format!("WBLOCK_POINT_PICKED {} {} {}", pt.x, pt.y, pt.z))
}
fn on_enter(&mut self) -> crate::command::CmdResult {
crate::command::CmdResult::Dispatch("WBLOCK_POINT_CANCELLED".to_string())
}
fn on_escape(&mut self) -> crate::command::CmdResult {
crate::command::CmdResult::Dispatch("WBLOCK_POINT_CANCELLED".to_string())
}
}
/// Build a standalone `CadDocument` from an explicit list of entity handles,
/// applying `base_point` translation and `unit` insertion units.
pub fn extract_entities_to_doc_with_base(
src: &CadDocument,
handles: &[acadrust::Handle],
base_point: glam::DVec3,
unit: i16,
) -> Result<CadDocument, String> {
let mut out = CadDocument::new();
out.header.insertion_units = unit;
extract_entities_into(src, handles, &mut out)?;
if base_point != glam::DVec3::ZERO {
let shift = crate::command::EntityTransform::Affine(
acadrust::types::Transform::from_translation(acadrust::types::Vector3::new(
-base_point.x,
-base_point.y,
-base_point.z,
)),
);
for entity in out.entities_mut() {
crate::scene::view::dispatch::apply_transform(entity, &shift);
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use acadrust::entities::Line;
use acadrust::types::Vector3;
#[test]
fn test_extract_entities_with_base_point_and_units() {
let mut doc = CadDocument::new();
let mut line = Line::new();
line.start = Vector3::new(10.0, 20.0, 0.0);
line.end = Vector3::new(30.0, 40.0, 0.0);
let h = doc.add_entity(EntityType::Line(line)).unwrap();
let out = extract_entities_to_doc_with_base(
&doc,
&[h],
glam::DVec3::new(10.0, 20.0, 0.0),
4, // Millimeters
)
.expect("extract should succeed");
assert_eq!(out.header.insertion_units, 4);
assert_eq!(out.entities().count(), 1);
let e = out.entities().next().unwrap();
if let EntityType::Line(l) = e {
assert!((l.start.x - 0.0).abs() < 1e-6);
assert!((l.start.y - 0.0).abs() < 1e-6);
assert!((l.end.x - 20.0).abs() < 1e-6);
assert!((l.end.y - 20.0).abs() < 1e-6);
} else {
panic!("Expected Line entity");
}
}
#[test]
fn test_extract_block_to_doc() {
let mut doc = CadDocument::new();
let mut line = Line::new();
line.start = Vector3::new(1.0, 2.0, 0.0);
line.end = Vector3::new(3.0, 4.0, 0.0);
let h = doc.add_entity(EntityType::Line(line)).unwrap();
let mut block_record = acadrust::tables::BlockRecord::new("MY_BLOCK".to_string());
block_record.entity_handles.push(h);
let _ = doc.block_records.add(block_record);
let out = extract_block_to_doc(&doc, "MY_BLOCK").expect("block extract should succeed");
assert_eq!(out.entities().count(), 1);
assert!(extract_block_to_doc(&doc, "NON_EXISTENT").is_err());
}
}
+35 -1
View File
@@ -7,10 +7,44 @@
use crate::app::Message;
use crate::ui::style::common::muted_style;
use iced::widget::{button, container, pick_list, row, text, text_input, Space};
use iced::widget::{button, container, pick_list, radio, row, text, text_input, Space};
use iced::{Background, Border, Element, Length, Theme};
use std::borrow::Cow;
/// Standard dialog footer action button (OK, Cancel, Apply, Close, Help, etc.),
/// matching the Drafting Settings / Options appearance: 12px text, [6, 18] padding,
/// themed primary or secondary style.
pub fn dialog_button<'a>(
label: impl text::IntoFragment<'a>,
on_press: Message,
primary: bool,
) -> button::Button<'a, Message> {
button(text(label).size(12))
.on_press(on_press)
.padding([6, 18])
.style(if primary {
button::primary
} else {
button::secondary
})
}
/// Unified radio button helper ensuring consistent 16px size for exact pixel-grid concentric alignment.
pub fn form_radio<'a, T>(
label: impl Into<String>,
value: T,
selected: Option<T>,
on_click: impl Fn(T) -> Message + 'a,
) -> radio::Radio<'a, Message>
where
T: Copy + Eq + 'a,
{
radio(label, value, selected, on_click)
.size(16)
.spacing(6)
.text_size(11)
}
/// Width of the label column in a `label : control` row.
pub const LABEL_WIDTH: f32 = 92.0;
+2 -2
View File
@@ -168,7 +168,7 @@ pub fn view_window<'a>(
Some(relative),
Message::PointStyleSizeRelative,
)
.size(15)
.size(16)
.text_size(13),
radio(
t!("Set Size in Absolute Units"),
@@ -176,7 +176,7 @@ pub fn view_window<'a>(
Some(relative),
Message::PointStyleSizeRelative,
)
.size(15)
.size(16)
.text_size(13),
]
.spacing(6);
+14 -38
View File
@@ -5,7 +5,7 @@ use std::fmt;
use acadrust::Handle;
use iced::widget::{
button, checkbox, column, combo_box, container, mouse_area, pick_list, radio, row, stack, text,
button, checkbox, column, combo_box, container, mouse_area, pick_list, row, stack, text,
text_editor, text_input, Space,
};
use iced::{Background, Border, Element, Fill, Length, Theme};
@@ -14,7 +14,7 @@ use crate::app::Message;
use crate::modules::draw::units;
use crate::t;
use crate::ui::style::common::muted_style;
use crate::ui::style::form::{button_style, field_style};
use crate::ui::style::form::{button_style, dialog_button, field_style, form_radio};
static ICON_PICK_POINT: &[u8] = include_bytes!("../../../assets/icons/blocks/pick_point.svg");
static ICON_SELECT_OBJECTS: &[u8] =
@@ -353,33 +353,24 @@ pub fn view_window<'a>(
let btns_row = row![sel_obj_btn, qsel_btn].spacing(5).align_y(iced::Center);
let radios = column![
radio(
form_radio(
t!("Retain"),
BlockObjectMode::Retain,
Some(state.object_mode),
Message::BlockDefObjectMode,
)
.size(15)
.spacing(5)
.text_size(11),
radio(
),
form_radio(
t!("Convert to block"),
BlockObjectMode::Convert,
Some(state.object_mode),
Message::BlockDefObjectMode,
)
.size(15)
.spacing(5)
.text_size(11),
radio(
),
form_radio(
t!("Delete"),
BlockObjectMode::Delete,
Some(state.object_mode),
Message::BlockDefObjectMode,
)
.size(15)
.spacing(5)
.text_size(11),
),
]
.spacing(2);
@@ -580,20 +571,11 @@ pub fn view_window<'a>(
// ── Footer Bar ────────────────────────────────────────────────────────────
let actions = row![
Space::new().width(Fill),
button(text(t!("OK")).size(11))
.on_press(Message::BlockDefApply)
.style(button_style(true))
.padding([4, 16]),
button(text(t!("Cancel")).size(11))
.on_press(Message::CloseModal)
.style(button_style(false))
.padding([4, 12]),
button(text(t!("Help")).size(11))
.on_press(Message::BlockDefHelp)
.style(button_style(false))
.padding([4, 12]),
dialog_button(t!("OK"), Message::BlockDefApply, true),
dialog_button(t!("Cancel"), Message::CloseModal, false),
dialog_button(t!("Help"), Message::BlockDefHelp, false),
]
.spacing(6)
.spacing(8)
.align_y(iced::Center);
// ── Error Banner ──────────────────────────────────────────────────────────
@@ -664,15 +646,9 @@ pub fn view_window<'a>(
.align_x(iced::alignment::Horizontal::Center),
Space::new().height(16),
row![
button(text(t!("Yes")).size(12))
.on_press(Message::BlockDefConfirmRedefine(true))
.padding([6, 20])
.style(button_style(false)),
dialog_button(t!("Yes"), Message::BlockDefConfirmRedefine(true), false),
// Default / safe focus on "No"
button(text(t!("No")).size(12))
.on_press(Message::BlockDefConfirmRedefine(false))
.padding([6, 22])
.style(button_style(true)),
dialog_button(t!("No"), Message::BlockDefConfirmRedefine(false), true),
]
.spacing(12)
.align_y(iced::Center),
+1
View File
@@ -27,3 +27,4 @@ pub mod named_parameters;
pub mod xref_attach;
pub mod xref_help;
pub mod xref_manager;
pub mod wblock;
+492
View File
@@ -0,0 +1,492 @@
//! Write Block (WBLOCK) dialog window — write a block, selected entities, or entire drawing to an external DWG/DXF file.
use std::fmt;
use acadrust::Handle;
use iced::widget::{
button, column, combo_box, container, pick_list, row, text, text_input, Space,
};
use iced::{Background, Border, Element, Fill, Length, Theme};
use crate::app::Message;
use crate::modules::draw::units;
use crate::t;
use crate::ui::style::form::{button_style, dialog_button, field_style, form_radio};
static ICON_PICK_POINT: &[u8] = include_bytes!("../../../assets/icons/blocks/pick_point.svg");
static ICON_SELECT_OBJECTS: &[u8] =
include_bytes!("../../../assets/icons/blocks/select_objects.svg");
static ICON_QUICK_SELECT: &[u8] = include_bytes!("../../../assets/icons/blocks/quick_select.svg");
static ICON_WARN: &[u8] = include_bytes!("../../../assets/icons/ui/warning_triangle.svg");
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum WblockSourceMode {
Block,
EntireDrawing,
Objects,
}
impl Default for WblockSourceMode {
fn default() -> Self {
WblockSourceMode::Objects
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum WblockObjectMode {
Retain,
Convert,
Delete,
}
impl Default for WblockObjectMode {
fn default() -> Self {
WblockObjectMode::Retain
}
}
/// Dropdown entry for the Unit picker.
#[derive(Clone, PartialEq, Eq)]
pub struct UnitChoice {
pub code: i16,
pub label: &'static str,
}
impl fmt::Display for UnitChoice {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let name = crate::i18n::translate(self.label);
f.write_str(name.as_ref())
}
}
/// The Write Block dialog's state.
pub struct WblockState {
pub source_mode: WblockSourceMode,
pub block_name: String,
pub existing_blocks: Vec<String>,
pub block_combo: combo_box::State<String>,
pub base_point_x: String,
pub base_point_y: String,
pub base_point_z: String,
pub selected_handles: Vec<Handle>,
pub object_mode: WblockObjectMode,
pub file_path: String,
pub unit: i16,
pub error_message: Option<String>,
}
impl Clone for WblockState {
fn clone(&self) -> Self {
Self {
source_mode: self.source_mode,
block_name: self.block_name.clone(),
existing_blocks: self.existing_blocks.clone(),
block_combo: self.block_combo.clone(),
base_point_x: self.base_point_x.clone(),
base_point_y: self.base_point_y.clone(),
base_point_z: self.base_point_z.clone(),
selected_handles: self.selected_handles.clone(),
object_mode: self.object_mode,
file_path: self.file_path.clone(),
unit: self.unit,
error_message: self.error_message.clone(),
}
}
}
impl WblockState {
pub fn new(
existing_blocks: Vec<String>,
pre_selected: Vec<Handle>,
default_unit: i16,
default_path: String,
) -> Self {
let default_block = existing_blocks.first().cloned().unwrap_or_default();
Self {
source_mode: WblockSourceMode::Objects,
block_name: default_block,
block_combo: combo_box::State::new(existing_blocks.clone()),
existing_blocks,
base_point_x: "0.00".to_string(),
base_point_y: "0.00".to_string(),
base_point_z: "0.00".to_string(),
selected_handles: pre_selected,
object_mode: WblockObjectMode::Retain,
file_path: default_path,
unit: default_unit,
error_message: None,
}
}
pub fn parse_base_point(&self) -> glam::DVec3 {
let x = self.base_point_x.trim().parse::<f64>().unwrap_or(0.0);
let y = self.base_point_y.trim().parse::<f64>().unwrap_or(0.0);
let z = self.base_point_z.trim().parse::<f64>().unwrap_or(0.0);
glam::DVec3::new(x, y, z)
}
}
fn group<'a>(
title: impl Into<String>,
body: impl Into<Element<'a, Message>>,
height: impl Into<Length>,
) -> Element<'a, Message> {
container(
column![
text(title.into())
.size(11)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().background.strongest.text.scale_alpha(0.85)),
}),
body.into(),
]
.spacing(4),
)
.padding([5, 8])
.width(Fill)
.height(height)
.style(|theme: &Theme| container::Style {
border: Border {
width: 1.0,
radius: 4.0.into(),
color: theme.palette().background.strong.color,
},
..Default::default()
})
.into()
}
pub fn view_window<'a>(
state: &'a WblockState,
sizing: crate::ui::modal::ModalSizing,
) -> Element<'a, Message> {
let is_objects = state.source_mode == WblockSourceMode::Objects;
let is_block = state.source_mode == WblockSourceMode::Block;
// ── Source: Block Radio & Dropdown ─────────────────────────────────────────
let block_radio = form_radio(
t!("Block:"),
WblockSourceMode::Block,
Some(state.source_mode),
Message::WblockSourceMode,
);
let block_dropdown: Element<'a, Message> = if is_block {
combo_box(
&state.block_combo,
"",
if state.block_name.is_empty() {
None
} else {
Some(&state.block_name)
},
|chosen: String| Message::WblockBlockSelect(chosen),
)
.on_input(Message::WblockBlockName)
.size(11)
.padding([2, 5])
.width(Fill)
.into()
} else {
crate::ui::read_only::field(&state.block_name, 11.0, Fill)
};
let block_row = row![block_radio, block_dropdown]
.spacing(8)
.width(Fill)
.align_y(iced::Center);
let entire_drawing_radio = form_radio(
t!("Entire drawing"),
WblockSourceMode::EntireDrawing,
Some(state.source_mode),
Message::WblockSourceMode,
);
let objects_radio = form_radio(
t!("Objects"),
WblockSourceMode::Objects,
Some(state.source_mode),
Message::WblockSourceMode,
);
// ── Source: Base Point Sub-group ──────────────────────────────────────────
let pick_pt_icon = crate::ui::icons::semantic(ICON_PICK_POINT, 14.0);
let mut pick_pt_btn = button(
row![pick_pt_icon, text(t!("Pick point")).size(11)]
.spacing(6)
.align_y(iced::Center),
)
.padding([3, 10]);
if is_objects {
pick_pt_btn = pick_pt_btn
.on_press(Message::WblockPickPoint)
.style(button_style(false));
} else {
pick_pt_btn = pick_pt_btn.style(button_style(false));
}
let coord_row = |axis: &'static str, val: &'a str, on_input: fn(String) -> Message| {
let input: Element<'a, Message> = if is_objects {
text_input("", val)
.on_input(on_input)
.on_submit(Message::WblockApply)
.style(field_style)
.size(11)
.padding([2, 5])
.width(Fill)
.into()
} else {
crate::ui::read_only::field(val, 11.0, Fill)
};
row![
text(axis)
.size(11)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().background.strongest.text.scale_alpha(0.85)),
})
.width(Length::Fixed(16.0)),
input,
]
.spacing(4)
.align_y(iced::Center)
};
let base_point_group = group(
t!("Base point").into_owned(),
column![
pick_pt_btn,
Space::new().height(3),
coord_row("X:", &state.base_point_x, Message::WblockBaseX),
coord_row("Y:", &state.base_point_y, Message::WblockBaseY),
coord_row("Z:", &state.base_point_z, Message::WblockBaseZ),
]
.spacing(3),
Length::Fixed(142.0),
);
// ── Source: Objects Sub-group ─────────────────────────────────────────────
let sel_obj_icon = crate::ui::icons::semantic(ICON_SELECT_OBJECTS, 14.0);
let mut sel_obj_btn = button(
row![sel_obj_icon, text(t!("Select objects")).size(11)]
.spacing(6)
.align_y(iced::Center),
)
.padding([3, 8]);
let qsel_icon = crate::ui::icons::semantic(ICON_QUICK_SELECT, 14.0);
let mut qsel_btn = button(qsel_icon).padding([3, 6]);
if is_objects {
sel_obj_btn = sel_obj_btn
.on_press(Message::WblockSelectObjects)
.style(button_style(false));
qsel_btn = qsel_btn
.on_press(Message::WblockQuickSelect)
.style(button_style(false));
} else {
sel_obj_btn = sel_obj_btn.style(button_style(false));
qsel_btn = qsel_btn.style(button_style(false));
}
let obj_btns_row = row![sel_obj_btn, qsel_btn]
.spacing(5)
.align_y(iced::Center);
let object_radios = column![
form_radio(
t!("Retain"),
WblockObjectMode::Retain,
if is_objects { Some(state.object_mode) } else { None },
Message::WblockObjectMode,
),
form_radio(
t!("Convert to block"),
WblockObjectMode::Convert,
if is_objects { Some(state.object_mode) } else { None },
Message::WblockObjectMode,
),
form_radio(
t!("Delete from drawing"),
WblockObjectMode::Delete,
if is_objects { Some(state.object_mode) } else { None },
Message::WblockObjectMode,
),
]
.spacing(3);
let count = state.selected_handles.len();
let status_row: Element<'a, Message> = if !is_objects {
Space::new().height(14).into()
} else if count == 0 {
row![
crate::ui::icons::semantic(ICON_WARN, 13.0),
text(t!("No objects selected"))
.size(10)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().warning.base.color),
}),
]
.spacing(5)
.align_y(iced::Center)
.into()
} else {
row![text(crate::tf!("{count} objects selected"))
.size(10)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().primary.base.color),
}),]
.spacing(5)
.align_y(iced::Center)
.into()
};
let objects_subgroup = group(
t!("Objects").into_owned(),
column![obj_btns_row, Space::new().height(2), object_radios, Space::new().height(2), status_row,]
.spacing(2),
Length::Fixed(142.0),
);
let sub_panels = row![base_point_group, objects_subgroup]
.spacing(8)
.width(Fill);
let source_group = group(
t!("Source").into_owned(),
column![
block_row,
entire_drawing_radio,
objects_radio,
Space::new().height(4),
sub_panels,
]
.spacing(4),
Length::Shrink,
);
// ── Destination Group ─────────────────────────────────────────────────────
let file_input = text_input("", &state.file_path)
.on_input(Message::WblockFilePath)
.on_submit(Message::WblockApply)
.style(field_style)
.size(11)
.padding([3, 6])
.width(Fill);
let browse_btn = button(
container(text("...").size(11))
.width(Fill)
.align_x(iced::alignment::Horizontal::Center),
)
.width(Length::Fixed(28.0))
.on_press(Message::WblockBrowsePath)
.style(button::secondary)
.padding([3, 0]);
let path_row = row![file_input, browse_btn].spacing(5).align_y(iced::Center);
let unit_options: Vec<UnitChoice> = units::all()
.map(|(code, label)| UnitChoice { code, label })
.collect();
let selected_unit = unit_options
.iter()
.find(|choice| choice.code == state.unit)
.cloned();
let unit_picker = pick_list(selected_unit, unit_options, |choice| choice.to_string())
.on_select(|choice| Message::WblockUnit(choice.code))
.text_size(11)
.padding([2, 5])
.width(Fill);
let units_row = row![
text(t!("Insert units:"))
.size(11)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().background.strongest.text.scale_alpha(0.85)),
}),
Space::new().width(8),
unit_picker,
Space::new().width(Length::Fixed(33.0)),
]
.spacing(4)
.align_y(iced::Center);
let destination_group = group(
t!("Destination").into_owned(),
column![
text(t!("File name and path:"))
.size(11)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().background.strongest.text.scale_alpha(0.85)),
}),
path_row,
Space::new().height(4),
units_row,
]
.spacing(3),
Length::Shrink,
);
// ── Footer Bar ────────────────────────────────────────────────────────────
let actions = row![
Space::new().width(Fill),
dialog_button(t!("OK"), Message::WblockApply, true),
dialog_button(t!("Cancel"), Message::CloseModal, false),
dialog_button(t!("Help"), Message::WblockHelp, false),
]
.spacing(8)
.align_y(iced::Center);
// ── Error Banner ──────────────────────────────────────────────────────────
let mut main_column = column![source_group, destination_group, actions,]
.spacing(8)
.padding([8, 10])
.width(sizing.width);
if let Some(err) = &state.error_message {
let err_banner = container(
row![
crate::ui::icons::semantic(ICON_WARN, 16.0),
text(err.clone())
.size(11)
.style(|theme: &Theme| text::Style {
color: Some(theme.palette().background.strongest.text),
}),
Space::new().width(Fill),
button(text("×").size(12))
.on_press(Message::WblockDismissError)
.style(button_style(false))
.padding([1, 6]),
]
.spacing(8)
.align_y(iced::Center),
)
.padding([6, 10])
.style(|theme: &Theme| container::Style {
background: Some(Background::Color(
theme.palette().danger.base.color.scale_alpha(0.18),
)),
border: Border {
width: 1.0,
radius: 4.0.into(),
color: theme.palette().danger.base.color,
},
..Default::default()
});
main_column = column![err_banner, main_column]
.spacing(6)
.width(sizing.width)
.padding(10);
}
main_column.into()
}