forked from 1b-agent/opencadstudio-upstream
feat(frame): complete frame controls
Preserve frame geometry for selection while separating viewport and plot visibility across supported reference entities. Refs #642
This commit is contained in:
commit
44efb8fd53
88 files changed
+1538
-702
No files matched your search
Generated
+2
-2
@@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||
[[package]]
|
||||
name = "acadrust"
|
||||
version = "0.4.1"
|
||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=3742f38#3742f382e6f359178ec7bed6b7719c7ac85f3538"
|
||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=cf46d04#cf46d047211d1adcb37a079812bc38baa45c98f6"
|
||||
dependencies = [
|
||||
"ahash 0.8.12",
|
||||
"anyhow",
|
||||
@@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
[[package]]
|
||||
name = "cadkernel"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=927f888#927f88839bbffe024579e14f2acd4cc87ed4a05a"
|
||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b3f3122#b3f3122176e3f3860dd3bbc23f12c92b56f06095"
|
||||
dependencies = [
|
||||
"acadrust",
|
||||
"cavalier_contours",
|
||||
|
||||
+2
-2
@@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
||||
rfd = "0.17"
|
||||
clap = { version = "4", features = ["derive"] }
|
||||
env_logger = "0.11"
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3742f38", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "927f888", features = ["acis", "offset"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] }
|
||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b3f3122", features = ["acis", "offset"] }
|
||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||
flate2 = "1"
|
||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||
|
||||
@@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
||||
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3742f38", optional = true, features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", optional = true, features = ["serde"] }
|
||||
|
||||
# Runtime IPC and serialization (host feature only).
|
||||
interprocess = { version = "2", optional = true }
|
||||
@@ -37,7 +37,7 @@ serde_json = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
cargo-lock = "11"
|
||||
# acadrust is scanned at build time to generate the embedded type registry.
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3742f38", features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
@@ -8,7 +8,7 @@ publish = false
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3742f38", features = ["serde"] }
|
||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] }
|
||||
bincode = "1.3"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
console_error_panic_hook = "0.1"
|
||||
|
||||
@@ -1416,11 +1416,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = الاستخدام: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = الاستخدام: POLYSOLID <width> <height> (حدد خطًا متعددًا أولًا)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = الاستخدام: PYRAMID <radius> <height> [sides] (الافتراضي 4 أضلاع)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT حدد النقطة الأولى أو [خط متعدد]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT حدد النقطة الأولى أو [الإطارات/خط متعدد] <خط متعدد>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT حدد النقطة التالية أو [تراجع/إغلاق] (__ocs_arg_n__ نقاط):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT خط متعدد حدد خطًا متعددًا مستويًا مغلقًا:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT مستطيل حدد الزاوية الأولى:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT مستطيل حدد الزاوية المقابلة:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT الإطارات أدخل وضع الإطار [إيقاف/تشغيل/عرضبدونطباعة] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT خط متعدد حذف الخط المتعدد المصدر؟ [نعم/لا] <لا>:
|
||||
.wipeout-polyline-invalid = WIPEOUT خط متعدد: حدد خطًا متعددًا ثنائي الأبعاد مستقيمًا ومغلقًا ومستويًا بـ 3 رؤوس غير متقاطعة على الأقل.
|
||||
.setvar-frame-range = SETVAR: يتطلب __ocs_fmt_0__ القيمة 0 أو 1 أو 2.
|
||||
.frames = الإطارات
|
||||
.display-but-not-plot = عرض بدون طباعة
|
||||
.frames-on-not-plotted = الإطارات ظاهرة، غير مطبوعة
|
||||
.image-adjust = ضبط الصورة
|
||||
.x-ray = أشعة سينية
|
||||
.xline-specify-a-point = XLINE حدد نقطة:
|
||||
.xline-specify-through-point = XLINE حدد نقطة العبور:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Употреба: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Употреба: POLYSOLID <ширина> <височина> (първо избери полилиния)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Употреба: PYRAMID <радиус> <височина> [страни] (4 страни по подразбиране)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Посочи първа точка или [Полилиния]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Посочи първа точка или [Рамки/Полилиния] <Полилиния>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Посочи следваща точка или [Отмени/Затвори] (__ocs_arg_n__ точки):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline Избери затворена равнинна полилиния:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular Посочи първи ъгъл:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Посочи противоположен ъгъл:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Рамки Въведи режим на рамката [Изкл./Вкл./ПоказвайБезПечат] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Полилиния Да се изтрие ли изходната полилиния? [Да/Не] <Не>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Полилиния: избери права, затворена, равнинна 2D полилиния с поне 3 непресичащи се върха.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ изисква 0, 1 или 2.
|
||||
.frames = Рамки
|
||||
.display-but-not-plot = Показвай, но не печатай
|
||||
.frames-on-not-plotted = Рамките са включени, не се печатат
|
||||
.image-adjust = Настройка на изображението
|
||||
.x-ray = Рентген
|
||||
.xline-specify-a-point = XLINE Посочи точка:
|
||||
.xline-specify-through-point = XLINE Посочи точка на преминаване:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Použití: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Použití: POLYSOLID <width> <height> (nejprve vyberte křivku)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Použití: PYRAMID <radius> <height> [sides] (výchozí 4 strany)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Zadejte první bod nebo [Polyline]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Zadejte první bod nebo [Rámečky/Křivka] <Křivka>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Zadejte další bod nebo [Undo/Close] (body __ocs_arg_n__):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline Vyberte uzavřenou rovinnou křivku:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Obdélníkový Zadejte první roh:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Obdélníkový Zadejte protilehlý roh:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Rámečky Zadejte režim rámečku [Vypnuto/Zapnuto/ZobrazitNetisknout] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Křivka Vymazat zdrojovou křivku? [Ano/Ne] <Ne>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Křivka: vyberte přímou, uzavřenou, rovinnou 2D křivku s alespoň 3 neprotínajícími se vrcholy.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ vyžaduje 0, 1 nebo 2.
|
||||
.frames = Rámečky
|
||||
.display-but-not-plot = Zobrazit, ale netisknout
|
||||
.frames-on-not-plotted = Rámečky zapnuté, netisknuté
|
||||
.image-adjust = Úprava obrazu
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Určete bod:
|
||||
.xline-specify-through-point = XLINE Určete průchozí bod:
|
||||
|
||||
@@ -1404,11 +1404,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Verwendung: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Verwendung: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Verwendung: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Geben Sie den ersten Punkt oder [Polylinie] an:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Geben Sie den ersten Punkt oder [Rahmen/Polylinie] an <Polylinie>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Nächsten Punkt angeben oder [Zurück/Schließen] (__ocs_arg_n__ Punkte):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polylinie Wählen Sie eine geschlossene planare Polylinie:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rechteckig Geben Sie die erste Ecke an:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rechteckig Angeben der gegenüberliegenden Ecke:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Rahmen Rahmenmodus eingeben [Aus/Ein/AnzeigenAberNichtPlotten] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polylinie Quellpolylinie löschen? [Ja/Nein] <Nein>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polylinie: Wählen Sie eine gerade, geschlossene, planare 2D-Polylinie mit mindestens 3 sich nicht schneidenden Scheitelpunkten.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ erfordert 0, 1 oder 2.
|
||||
.frames = Rahmen
|
||||
.display-but-not-plot = Anzeigen, aber nicht plotten
|
||||
.frames-on-not-plotted = Rahmen ein, nicht geplottet
|
||||
.image-adjust = Bildanpassung
|
||||
.x-ray = Röntgenstrahlen
|
||||
.xline-specify-a-point = XLINE Geben Sie einen Punkt an:
|
||||
.xline-specify-through-point = XLINE Geben Sie durch Punkt an:
|
||||
|
||||
@@ -1405,11 +1405,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Usage: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Usage: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Usage: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Specify first point or [Polyline]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Specify first point or [Frames/Polyline] <Polyline>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Specify next point or [Undo/Close] (__ocs_arg_n__ points):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline Select a closed planar polyline:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular Specify first corner:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Specify opposite corner:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Frames Enter frame mode [Off/On/DisplayButNotPlot] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polyline Erase source polyline? [Yes/No] <No>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polyline: select a straight, closed, planar 2D polyline with at least 3 non-intersecting vertices.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ requires 0, 1, or 2.
|
||||
.frames = Frames
|
||||
.display-but-not-plot = Display but not plot
|
||||
.frames-on-not-plotted = Frames On, Not Plotted
|
||||
.image-adjust = Image Adjust
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Specify a point:
|
||||
.xline-specify-through-point = XLINE Specify through point:
|
||||
|
||||
@@ -1404,11 +1404,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Uso: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Uso: POLYSOLID <anchura> <altura> (seleccione primero una polilínea)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Uso: PYRAMID <radio> <altura> [lados] (4 lados de forma predeterminada)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Precise el primer punto o [Polilínea]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Precise el primer punto o [Marcos/Polilínea] <Polilínea>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Precise el punto siguiente o [Deshacer/Cerrar] (__ocs_arg_n__ puntos):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polilínea Seleccione una polilínea plana cerrada:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular Precise la primera esquina:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Precise la esquina opuesta:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Marcos Indique el modo de marco [Desactivado/Activado/MostrarSinTrazar] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polilínea ¿Borrar la polilínea de origen? [Sí/No] <No>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polilínea: seleccione una polilínea 2D recta, cerrada y plana con al menos 3 vértices que no se intersequen.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ requiere 0, 1 o 2.
|
||||
.frames = Marcos
|
||||
.display-but-not-plot = Mostrar pero no trazar
|
||||
.frames-on-not-plotted = Marcos activados, no trazados
|
||||
.image-adjust = Ajuste de imagen
|
||||
.x-ray = Rayos X
|
||||
.xline-specify-a-point = XLINE Precise un punto:
|
||||
.xline-specify-through-point = XLINE Precise el punto de paso:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Käyttö: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Käyttö: POLYSOLID <width> <height> (valitse ensin polyline)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Käyttö: PYRAMID <radius> <height> [sides] (oletus 4 sivua)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Määritä ensimmäinen piste tai [Polyline]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Määritä ensimmäinen piste tai [Kehykset/Moniviiva] <Moniviiva>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Määritä seuraava piste tai [Kumoa/Sulje] (__ocs_arg_n__-pisteet):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline Valitse suljettu tasoviiva:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Suorakulmainen Määritä ensimmäinen kulma:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Suorakulmainen Määritä vastakkainen kulma:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Kehykset Anna kehystila [Pois/Päällä/NäytäEiTulostusta] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Moniviiva Poistetaanko lähdemoniviiva? [Kyllä/Ei] <Ei>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Moniviiva: valitse suora, suljettu ja tasomainen 2D-moniviiva, jossa on vähintään 3 toisiaan leikkaamatonta kärkeä.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ vaatii arvon 0, 1 tai 2.
|
||||
.frames = Kehykset
|
||||
.display-but-not-plot = Näytä, mutta älä tulosta
|
||||
.frames-on-not-plotted = Kehykset päällä, ei tulosteta
|
||||
.image-adjust = Kuvan säätö
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Määritä piste:
|
||||
.xline-specify-through-point = XLINE Määritä pisteen kautta:
|
||||
|
||||
@@ -1404,11 +1404,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Utilisation : ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Utilisation : POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Utilisation : PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Spécifier le premier point ou [Polyligne]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Spécifier le premier point ou [Cadres/Polyligne] <Polyligne> :
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Spécifiez le point suivant ou [Désactiver/Fermer] (__ocs_arg_n__ points):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyligne Sélectionnez une polyligne plane fermée :
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangulaire Précisez le premier coin :
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangulaire Spécifiez le coin opposé:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Cadres Entrez le mode de cadre [Désactivé/Activé/AfficherSansTracer] <__ocs_arg_mode__> :
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polyligne Effacer la polyligne source ? [Oui/Non] <Non> :
|
||||
.wipeout-polyline-invalid = WIPEOUT Polyligne : sélectionnez une polyligne 2D droite, fermée et plane comportant au moins 3 sommets sans intersection.
|
||||
.setvar-frame-range = SETVAR : __ocs_fmt_0__ exige 0, 1 ou 2.
|
||||
.frames = Cadres
|
||||
.display-but-not-plot = Afficher sans tracer
|
||||
.frames-on-not-plotted = Cadres activés, non tracés
|
||||
.image-adjust = Réglage de l’image
|
||||
.x-ray = Rayon X
|
||||
.xline-specify-a-point = XLINE Spécifiez un point:
|
||||
.xline-specify-through-point = XLINE Spécifiez par le point:
|
||||
|
||||
@@ -1392,11 +1392,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = उपयोग: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = उपयोग: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = उपयोग: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT प्रथम बिंदु या [पॉलिलाइन] निर्दिष्ट करें:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT पहला बिंदु या [फ्रेम/पॉलिलाइन] <पॉलिलाइन> निर्दिष्ट करें:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT अगला बिंदु निर्दिष्ट करें या [पूर्ववत करें / बंद करें] (__ocs_arg_n__ बिंदु):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT पॉलीलाइन एक बंद प्लानर पॉलीलाइन चुनें:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT आयताकार पहले कोने निर्दिष्ट करें:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT आयताकार विपरीत कोने निर्दिष्ट करें:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT फ्रेम फ्रेम मोड दर्ज करें [बंद/चालू/दिखाएंपरप्लॉटनकरें] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT पॉलिलाइन स्रोत पॉलिलाइन मिटाएं? [हां/नहीं] <नहीं>:
|
||||
.wipeout-polyline-invalid = WIPEOUT पॉलिलाइन: कम से कम 3 न कटने वाले शीर्षों वाली सीधी, बंद, समतल 2D पॉलिलाइन चुनें।
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ के लिए 0, 1 या 2 आवश्यक है।
|
||||
.frames = फ्रेम
|
||||
.display-but-not-plot = दिखाएं, पर प्लॉट न करें
|
||||
.frames-on-not-plotted = फ्रेम चालू, प्लॉट नहीं होंगे
|
||||
.image-adjust = छवि समायोजन
|
||||
.x-ray = एक्स-रे
|
||||
.xline-specify-a-point = XLINE एक बिंदु निर्दिष्ट करें:
|
||||
.xline-specify-through-point = XLINE बिंदु के माध्यम से निर्दिष्ट करें:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Használat: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Használat: POLYSOLID <width> <height> (először válasszon ki egy vonalláncot)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Használat: PYRAMID <radius> <height> [sides] (alapértelmezett 4 oldal)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Adja meg az első pontot vagy a [Polyline]-t:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Adja meg az első pontot vagy [Keretek/Vonallánc] <Vonallánc>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Következő pont megadása vagy [Visszavonás/Bezárás] (__ocs_arg_n__ pont):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Vonallánc Válasszon ki egy zárt sík vonalláncot:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT téglalap Adja meg az első sarkot:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Adja meg a szemközti sarkot:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Keretek Adja meg a keretmódot [Ki/Be/MegjelenítNemNyomtat] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Vonallánc Törli a forrás vonalláncot? [Igen/Nem] <Nem>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Vonallánc: válasszon egyenes, zárt, síkbeli 2D vonalláncot legalább 3, egymást nem metsző csúcsponttal.
|
||||
.setvar-frame-range = SETVAR: a __ocs_fmt_0__ értéke 0, 1 vagy 2 lehet.
|
||||
.frames = Keretek
|
||||
.display-but-not-plot = Megjelenítés nyomtatás nélkül
|
||||
.frames-on-not-plotted = Keretek bekapcsolva, nem nyomtatva
|
||||
.image-adjust = Képbeállítás
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Adjon meg egy pontot:
|
||||
.xline-specify-through-point = XLINE Pont megadása:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Utilizzo: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Utilizzo: POLYSOLID <width> <height> (selezionare prima una polilinea)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Utilizzo: PYRAMID <radius> <height> [sides] (predefinito 4 lati)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Specificare il primo punto o [Polyline]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Specificare il primo punto o [Cornici/Polilinea] <Polilinea>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Specificare il punto successivo o [Annulla/Chiudi] (punti __ocs_arg_n__):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polilinea Selezionare una polilinea planare chiusa:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rettangolare Specificare il primo angolo:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rettangolare Specificare l'angolo opposto:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Cornici Immettere la modalità cornice [Disattivata/Attivata/VisualizzaSenzaStampare] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polilinea Cancellare la polilinea di origine? [Sì/No] <No>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polilinea: selezionare una polilinea 2D diritta, chiusa e planare con almeno 3 vertici non intersecanti.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ richiede 0, 1 o 2.
|
||||
.frames = Cornici
|
||||
.display-but-not-plot = Visualizza ma non stampa
|
||||
.frames-on-not-plotted = Cornici attive, non stampate
|
||||
.image-adjust = Regolazione immagine
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Specificare un punto:
|
||||
.xline-specify-through-point = XLINE Specificare il punto di passaggio:
|
||||
|
||||
@@ -1394,11 +1394,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = 使用法: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = 使用法: POLYSOLID <width> <height> (先にポリラインを選択)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = 使用法: PYRAMID <radius> <height> [sides] (既定は 4 側面)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 最初の点を指定または [Polyline]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 最初の点を指定または [フレーム/ポリライン] <ポリライン>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT 次の点を指定または [Undo/Close](__ocs_arg_n__ 点):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline 閉じた平面ポリラインを選択:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular 最初のコーナーを指定:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular 反対側のコーナーを指定:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT フレーム フレームモードを入力 [オフ/オン/表示して印刷しない] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT ポリライン 元のポリラインを削除しますか? [はい/いいえ] <いいえ>:
|
||||
.wipeout-polyline-invalid = WIPEOUT ポリライン: 交差しない3つ以上の頂点を持つ、直線の閉じた平面2Dポリラインを選択してください。
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ には 0、1、2 のいずれかが必要です。
|
||||
.frames = フレーム
|
||||
.display-but-not-plot = 表示するが印刷しない
|
||||
.frames-on-not-plotted = フレームオン、印刷なし
|
||||
.image-adjust = 画像調整
|
||||
.x-ray = X 線
|
||||
.xline-specify-a-point = XLINE 点を指定:
|
||||
.xline-specify-through-point = XLINE 通過点を指定:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = 사용법: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = 사용법: POLYSOLID <width> <height>(폴리라인을 먼저 선택하세요)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = 사용법: PYRAMID <radius> <height> [sides] (기본 4면)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 첫 번째 점 또는 [Polyline] 지정:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 첫 번째 점 또는 [프레임/폴리선] <폴리선> 지정:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT 다음 점 지정 또는 [실행 취소/닫기](__ocs_arg_n__ 점):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 폴리선 닫힌 평면형 폴리선을 선택합니다.
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT 직사각형 첫 번째 구석 지정:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT 직사각형 반대쪽 모서리 지정:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT 프레임 프레임 모드 입력 [끄기/켜기/표시하고플롯안함] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT 폴리선 원본 폴리선을 지우시겠습니까? [예/아니오] <아니오>:
|
||||
.wipeout-polyline-invalid = WIPEOUT 폴리선: 교차하지 않는 꼭짓점이 3개 이상인 직선, 닫힌, 평면 2D 폴리선을 선택하세요.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__에는 0, 1 또는 2가 필요합니다.
|
||||
.frames = 프레임
|
||||
.display-but-not-plot = 표시하지만 플롯하지 않음
|
||||
.frames-on-not-plotted = 프레임 켜기, 플롯 안 함
|
||||
.image-adjust = 이미지 조정
|
||||
.x-ray = X-레이
|
||||
.xline-specify-a-point = XLINE 점 지정:
|
||||
.xline-specify-through-point = XLINE 통과점 지정:
|
||||
|
||||
@@ -1404,11 +1404,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Gebruik: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Gebruik: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Gebruik: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Geef het eerste punt of [Polylijn]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Geef het eerste punt of [Kaders/Polylijn] <Polylijn>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Geef volgend punt op of [Ongedaan maken/Sluiten] (__ocs_arg_n__ punten):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polylijn Selecteer een gesloten planaire polylijn:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Rechthoekige eerste hoek opgeven:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rechthoekig Geef tegenoverhoek aan:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Kaders Voer kadermodus in [Uit/Aan/WeergevenNietPlotten] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polylijn Bronpolylijn wissen? [Ja/Nee] <Nee>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polylijn: selecteer een rechte, gesloten, vlakke 2D-polylijn met ten minste 3 niet-kruisende hoekpunten.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ vereist 0, 1 of 2.
|
||||
.frames = Kaders
|
||||
.display-but-not-plot = Weergeven maar niet plotten
|
||||
.frames-on-not-plotted = Kaders aan, niet geplot
|
||||
.image-adjust = Beeldaanpassing
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Geef een punt op:
|
||||
.xline-specify-through-point = XLINE Geef door punt:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Zastosowanie: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Sposób użycia: POLYSOLID <width> <height> (najpierw wybierz polilinię)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Zastosowanie: PYRAMID <radius> <height> [sides] (domyślnie 4 strony)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Podaj pierwszy punkt lub [Polyline]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Podaj pierwszy punkt lub [Ramki/Polilinia] <Polilinia>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Określ następny punkt lub [Cofnij/Zamknij] (punkty __ocs_arg_n__):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polilinia Wybierz zamkniętą planarną polilinię:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Prostokątny Określ pierwszy narożnik:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Prostokątny Określ przeciwny narożnik:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Ramki Wprowadź tryb ramki [Wyłącz/Włącz/WyświetlBezDrukowania] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polilinia Usunąć polilinię źródłową? [Tak/Nie] <Nie>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polilinia: wybierz prostą, zamkniętą, płaską polilinię 2D z co najmniej 3 nieprzecinającymi się wierzchołkami.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ wymaga wartości 0, 1 lub 2.
|
||||
.frames = Ramki
|
||||
.display-but-not-plot = Wyświetlaj, ale nie drukuj
|
||||
.frames-on-not-plotted = Ramki włączone, niedrukowane
|
||||
.image-adjust = Dopasowanie obrazu
|
||||
.x-ray = X-Ray
|
||||
.xline-specify-a-point = XLINE Określ punkt:
|
||||
.xline-specify-through-point = XLINE Określ punkt przelotowy:
|
||||
|
||||
@@ -1404,11 +1404,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Uso: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Uso: POLYSOLID <largura> <altura> (selecione primeiro uma polilinha)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Uso: PYRAMID <raio> <altura> [lados] (padrão: 4 lados)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Especifique o primeiro ponto ou [Polilinha]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Especifique o primeiro ponto ou [Molduras/Polilinha] <Polilinha>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Especifique o próximo ponto ou [Desfazer/Fechar] (__ocs_arg_n__ pontos):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polilinha Selecione uma polilinha planar fechada:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Retangular Especifique o primeiro canto:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Retangular Especifique o canto oposto:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Molduras Informe o modo da moldura [Desativado/Ativado/ExibirSemPlotar] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Polilinha Apagar a polilinha de origem? [Sim/Não] <Não>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Polilinha: selecione uma polilinha 2D reta, fechada e planar com pelo menos 3 vértices sem interseção.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ requer 0, 1 ou 2.
|
||||
.frames = Molduras
|
||||
.display-but-not-plot = Exibir, mas não plotar
|
||||
.frames-on-not-plotted = Molduras ativadas, não plotadas
|
||||
.image-adjust = Ajuste de imagem
|
||||
.x-ray = Raio X
|
||||
.xline-specify-a-point = XLINE Especifique um ponto:
|
||||
.xline-specify-through-point = XLINE Especifique o ponto de passagem:
|
||||
|
||||
@@ -1407,11 +1407,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Использование: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Использование: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Использование: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Укажите первую точку или [Полилайн]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT Укажите первую точку или [Рамки/Полилиния] <Полилиния>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Укажите следующую точку или [Отменить / Замкнуть] (точек: __ocs_arg_n__):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Выберите замкнутый плоский полилин:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Прямоугольный Укажите первый угол:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Прямоугольный Укажите противоположный угол:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Рамки Введите режим рамки [Выкл/Вкл/ПоказатьНеПечатать] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Полилиния Удалить исходную полилинию? [Да/Нет] <Нет>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Полиния: выберите прямую, замкнутую, плоскую 2D-полилинию с не менее чем 3 непересекающимися вершинами.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ допускает только 0, 1 или 2.
|
||||
.frames = Рамки
|
||||
.display-but-not-plot = Показывать, но не печатать
|
||||
.frames-on-not-plotted = Рамки включены, не печатаются
|
||||
.image-adjust = Настройка изображения
|
||||
.x-ray = Рентгеновский
|
||||
.xline-specify-a-point = XLINE Укажите точку:
|
||||
.xline-specify-through-point = XLINE Укажите через точку:
|
||||
|
||||
@@ -1387,11 +1387,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = Kullanım: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = Kullanım: POLYSOLID <width> <height> (önce bir çoklu çizgi seçin)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = Kullanım: PYRAMID <radius> <height> [sides] (varsayılan 4 kenar)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT İlk noktayı belirtin veya [Çoklu Çizgi]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT İlk noktayı belirtin veya [Çerçeveler/Çoklu Çizgi] <Çoklu Çizgi>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT Sonraki noktayı belirtin veya [Geri Al/Kapat] (__ocs_arg_n__ nokta):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Çoklu Çizgi Kapalı düzlemsel bir çoklu çizgi seçin:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT Dikdörtgen İlk köşeyi belirtin:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Dikdörtgen Karşı köşeyi belirtin:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT Çerçeveler Çerçeve modunu girin [Kapalı/Açık/GösterAmaÇizdirme] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT Çoklu Çizgi Kaynak çoklu çizgi silinsin mi? [Evet/Hayır] <Hayır>:
|
||||
.wipeout-polyline-invalid = WIPEOUT Çoklu Çizgi: en az 3 kesişmeyen köşesi olan düz, kapalı, düzlemsel bir 2B çoklu çizgi seçin.
|
||||
.setvar-frame-range = SETVAR: __ocs_fmt_0__ için 0, 1 veya 2 gerekir.
|
||||
.frames = Çerçeveler
|
||||
.display-but-not-plot = Göster ama çizdirme
|
||||
.frames-on-not-plotted = Çerçeveler Açık, Çizdirilmez
|
||||
.image-adjust = Görüntü Ayarı
|
||||
.x-ray = X Işını
|
||||
.xline-specify-a-point = XLINE Bir nokta belirtin:
|
||||
.xline-specify-through-point = XLINE Geçiş noktasını belirtin:
|
||||
|
||||
@@ -1391,11 +1391,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = 用法: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = 用法: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = 用法: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 指定第一点或 [多段线]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 指定第一点或 [边框/多段线] <多段线>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT 指定下一点或 [放弃/闭合](__ocs_arg_n__ 个点):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 多段线 选择闭合平面多段线:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT 矩形 指定第一个角点:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT 矩形 指定对角点:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT 边框 输入边框模式 [关/开/显示但不打印] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT 多段线 是否删除源多段线?[是/否] <否>:
|
||||
.wipeout-polyline-invalid = WIPEOUT 多段线:请选择一条由至少 3 个不相交顶点组成的直线、闭合、平面 2D 多段线。
|
||||
.setvar-frame-range = SETVAR:__ocs_fmt_0__ 必须为 0、1 或 2。
|
||||
.frames = 边框
|
||||
.display-but-not-plot = 显示但不打印
|
||||
.frames-on-not-plotted = 边框开,不打印
|
||||
.image-adjust = 图像调整
|
||||
.x-ray = X 射线
|
||||
.xline-specify-a-point = XLINE 指定点:
|
||||
.xline-specify-through-point = XLINE 指定通过点:
|
||||
|
||||
@@ -1403,11 +1403,19 @@ draw =
|
||||
.usage-attdisp-on-off-normal = 用法: ATTDISP ON | OFF | NORMAL
|
||||
.usage-polysolid-width-height-select-a-polyline = 用法: POLYSOLID <width> <height> (select a polyline first)
|
||||
.usage-pyramid-radius-height-sides-default-4-si = 用法: PYRAMID <radius> <height> [sides] (default 4 sides)
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 指定第一點或 [多段線]:
|
||||
.wipeout-specify-first-point-or-polyline = WIPEOUT 指定第一點或 [邊框/多段線] <多段線>:
|
||||
.wipeout-specify-next-point-or-undo-close-n-poi = WIPEOUT 指定下一點或 [放棄/閉合](__ocs_arg_n__ 個點):
|
||||
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 多段線 選擇閉合平面多段線:
|
||||
.wipeout-rectangular-specify-first-corner = WIPEOUT 矩形 指定第一個角點:
|
||||
.wipeout-rectangular-specify-opposite-corner = WIPEOUT 矩形 指定對角點:
|
||||
.wipeout-frames-enter-frame-mode = WIPEOUT 邊框 輸入邊框模式 [關/開/顯示但不列印] <__ocs_arg_mode__>:
|
||||
.wipeout-polyline-erase-source = WIPEOUT 多段線 是否刪除來源多段線?[是/否] <否>:
|
||||
.wipeout-polyline-invalid = WIPEOUT 多段線:請選擇一條由至少 3 個不相交頂點組成的直線、閉合、平面 2D 多段線。
|
||||
.setvar-frame-range = SETVAR:__ocs_fmt_0__ 必須為 0、1 或 2。
|
||||
.frames = 邊框
|
||||
.display-but-not-plot = 顯示但不列印
|
||||
.frames-on-not-plotted = 邊框開,不列印
|
||||
.image-adjust = 圖像調整
|
||||
.x-ray = X 射線
|
||||
.xline-specify-a-point = XLINE 指定點:
|
||||
.xline-specify-through-point = XLINE 指定通過點:
|
||||
|
||||
@@ -1014,7 +1014,10 @@ impl OpenCADStudio {
|
||||
self.commit_undo_delta(i, pending);
|
||||
}
|
||||
}
|
||||
CmdResult::WipeoutFromPolyline(handle) => {
|
||||
CmdResult::WipeoutFromPolyline {
|
||||
handle,
|
||||
erase_source,
|
||||
} => {
|
||||
let wipeout = {
|
||||
let scene = &self.tabs[i].scene;
|
||||
scene
|
||||
@@ -1026,16 +1029,23 @@ impl OpenCADStudio {
|
||||
)
|
||||
};
|
||||
if let Some(wipeout) = wipeout {
|
||||
if erase_source {
|
||||
return self.apply_cmd_result(CmdResult::ReplaceMany(
|
||||
vec![(handle, Vec::new())],
|
||||
vec![wipeout],
|
||||
));
|
||||
}
|
||||
return self.apply_cmd_result(CmdResult::CommitAndExit(wipeout));
|
||||
}
|
||||
self.command_line.push_error(
|
||||
"WIPEOUT Polyline: select a closed planar polyline with at least 3 vertices.",
|
||||
crate::t!("WIPEOUT Polyline: select a straight, closed, planar 2D polyline with at least 3 non-intersecting vertices.").as_ref(),
|
||||
);
|
||||
if let Some(prompt) =
|
||||
self.tabs[i].active_cmd.as_ref().map(|command| command.prompt())
|
||||
{
|
||||
self.command_line.push_info(&prompt);
|
||||
}
|
||||
let command =
|
||||
crate::modules::draw::draw::wipeout::WipeoutCommand::new_polyline();
|
||||
self.command_line.push_info(
|
||||
&crate::command::CadCommand::prompt(&command),
|
||||
);
|
||||
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||
}
|
||||
CmdResult::MviewSwitchLayout(layout) => {
|
||||
let task = self.on_layout_switch_preserving_command(layout);
|
||||
|
||||
@@ -48,7 +48,11 @@ impl OpenCADStudio {
|
||||
let wo_cmd = match args.as_str() {
|
||||
"P" | "POLYLINE" => WipeoutCommand::new_polyline(),
|
||||
"R" | "RECTANGULAR" => WipeoutCommand::new_rectangular(),
|
||||
_ => WipeoutCommand::new_polygonal(),
|
||||
_ => WipeoutCommand::new_polygonal(
|
||||
crate::modules::draw::draw::wipeout::wipeout_frame_mode(
|
||||
&self.tabs[i].scene.document,
|
||||
),
|
||||
),
|
||||
};
|
||||
self.command_line.push_info(&wo_cmd.prompt());
|
||||
self.tabs[i].active_cmd = Some(Box::new(wo_cmd));
|
||||
|
||||
@@ -514,7 +514,15 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||
"CMLJUST",
|
||||
"TEXTQLTY",
|
||||
"SORTENTS",
|
||||
"FRAME",
|
||||
"IMAGEFRAME",
|
||||
"PDFFRAME",
|
||||
"POINTCLOUDCLIPFRAME",
|
||||
"XCLIPFRAME",
|
||||
"WIPEOUTFRAME",
|
||||
"FRAMES0",
|
||||
"FRAMES1",
|
||||
"FRAMES2",
|
||||
"HALOGAP",
|
||||
"TRACEWID",
|
||||
"SKETCHINC",
|
||||
|
||||
@@ -3,6 +3,9 @@ use super::*;
|
||||
impl OpenCADStudio {
|
||||
pub(super) fn dispatch_styleprops(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||
match cmd {
|
||||
"FRAMES0" => return self.dispatch_styleprops("SETVAR FRAME 0", i),
|
||||
"FRAMES1" => return self.dispatch_styleprops("SETVAR FRAME 1", i),
|
||||
"FRAMES2" => return self.dispatch_styleprops("SETVAR FRAME 2", i),
|
||||
// COLOR <ByLayer|ByBlock|1-255|name> — the colour applied to new
|
||||
// objects (CECOLOR). Bare COLOR reports the current value.
|
||||
"COLOR" | "COLOUR" | "CECOLOR" | "DDCOLOR" => {
|
||||
@@ -919,7 +922,12 @@ impl OpenCADStudio {
|
||||
| "CMLJUST"
|
||||
| "TEXTQLTY"
|
||||
| "SORTENTS"
|
||||
| "FRAME"
|
||||
| "IMAGEFRAME"
|
||||
| "PDFFRAME"
|
||||
| "POINTCLOUDCLIPFRAME"
|
||||
| "XCLIPFRAME"
|
||||
| "WIPEOUTFRAME"
|
||||
| "HALOGAP"
|
||||
| "TRACEWID"
|
||||
| "SKETCHINC"
|
||||
@@ -945,9 +953,83 @@ impl OpenCADStudio {
|
||||
let value = it.next().map(|s| s.trim().to_string());
|
||||
if name.is_empty() || name == "?" {
|
||||
self.command_line.push_info(
|
||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||
);
|
||||
} else {
|
||||
let frame_kind = crate::scene::frame::kind_for_name(&name);
|
||||
if name == "FRAME" || frame_kind.is_some() {
|
||||
let current = frame_kind.map_or_else(
|
||||
|| crate::scene::frame::master_mode(&self.tabs[i].scene.document),
|
||||
|kind| crate::scene::frame::mode(&self.tabs[i].scene.document, kind),
|
||||
);
|
||||
match &value {
|
||||
Some(value) => match value.parse::<i16>() {
|
||||
Ok(mode @ 0..=2) => {
|
||||
if current != mode {
|
||||
let changed_kinds: Vec<_> = frame_kind.map_or_else(
|
||||
|| {
|
||||
crate::scene::frame::ALL_KINDS
|
||||
.into_iter()
|
||||
.filter(|kind| {
|
||||
crate::scene::frame::mode(
|
||||
&self.tabs[i].scene.document,
|
||||
*kind,
|
||||
) != mode
|
||||
})
|
||||
.collect()
|
||||
},
|
||||
|kind| vec![kind],
|
||||
);
|
||||
self.push_undo_snapshot(i, &name);
|
||||
if let Some(kind) = frame_kind {
|
||||
crate::scene::frame::set_mode(
|
||||
&mut self.tabs[i].scene.document,
|
||||
kind,
|
||||
mode,
|
||||
);
|
||||
} else {
|
||||
crate::scene::frame::set_master_mode(
|
||||
&mut self.tabs[i].scene.document,
|
||||
mode,
|
||||
);
|
||||
}
|
||||
let changes: Vec<_> = self.tabs[i]
|
||||
.scene
|
||||
.document
|
||||
.entities()
|
||||
.filter_map(|entity| {
|
||||
changed_kinds
|
||||
.iter()
|
||||
.any(|kind| {
|
||||
crate::scene::frame::affected(entity, *kind)
|
||||
})
|
||||
.then_some((
|
||||
entity.common().handle,
|
||||
crate::scene::ChangeKind::Modified,
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
if !changes.is_empty() {
|
||||
self.tabs[i].scene.bump_entities(&changes);
|
||||
}
|
||||
self.tabs[i].dirty = true;
|
||||
}
|
||||
self.command_line
|
||||
.push_output(&format!("{name} = {mode}"));
|
||||
}
|
||||
_ => self.command_line.push_error(
|
||||
crate::tf!("SETVAR: {name} requires 0, 1, or 2.").as_ref(),
|
||||
),
|
||||
},
|
||||
None => {
|
||||
self.command_line.push_output(crate::tf!(
|
||||
"Enter new value for {name} <{current}>:"
|
||||
).as_ref());
|
||||
self.pending_setvar = Some(name.clone());
|
||||
}
|
||||
}
|
||||
return Some(self.finish_dispatch(cmd));
|
||||
}
|
||||
// Parse a boolean given as 0/1 or ON/OFF.
|
||||
let parse_bool = |s: &str| match s.to_uppercase().as_str() {
|
||||
"1" | "ON" | "TRUE" => Some(true),
|
||||
@@ -1597,16 +1679,6 @@ impl OpenCADStudio {
|
||||
.map_err(|_| "SETVAR: integer value required.".into()),
|
||||
None => Ok((format!("SORTENTS = {}", h.sort_entities), false)),
|
||||
},
|
||||
"XCLIPFRAME" => match &value {
|
||||
Some(v) => v
|
||||
.parse::<i16>()
|
||||
.map(|x| {
|
||||
h.xclip_frame = x;
|
||||
(format!("XCLIPFRAME = {x}"), true)
|
||||
})
|
||||
.map_err(|_| "SETVAR: integer value required.".into()),
|
||||
None => Ok((format!("XCLIPFRAME = {}", h.xclip_frame), false)),
|
||||
},
|
||||
"HALOGAP" => match &value {
|
||||
Some(v) => v
|
||||
.parse::<i16>()
|
||||
|
||||
@@ -168,7 +168,9 @@ fn plot_scene_content(
|
||||
Vec<crate::scene::model::hatch_model::HatchModel>,
|
||||
crate::io::pdf_export::PlotGroupSplits,
|
||||
) {
|
||||
let (paper_wires, model_wires) = scene.plot_wire_groups(render_mode_override);
|
||||
let (mut paper_wires, mut model_wires) = scene.plot_wire_groups(render_mode_override);
|
||||
paper_wires.retain(|wire| wire.plot_visible);
|
||||
model_wires.retain(|wire| wire.plot_visible);
|
||||
let paper_hatches = scene.paper_canvas_hatches().as_ref().clone();
|
||||
let paper_wipeouts = scene.paper_canvas_wipeouts().as_ref().clone();
|
||||
if scene.current_layout == "Model" {
|
||||
@@ -184,8 +186,9 @@ fn plot_scene_content(
|
||||
splits,
|
||||
);
|
||||
}
|
||||
let (model_pattern_wires, model_hatches, model_wipeouts) =
|
||||
let (mut model_pattern_wires, model_hatches, model_wipeouts) =
|
||||
scene.viewport_plot_fills();
|
||||
model_pattern_wires.retain(|wire| wire.plot_visible);
|
||||
|
||||
let (wires, hatches, wipeouts, splits) = if paper_space_last {
|
||||
let splits = crate::io::pdf_export::PlotGroupSplits {
|
||||
|
||||
@@ -2141,6 +2141,8 @@ impl OpenCADStudio {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
+5
-1
@@ -1297,7 +1297,11 @@ pub enum CmdResult {
|
||||
boundary_handle: Handle,
|
||||
},
|
||||
/// Create a wipeout from an existing closed polyline in the active space.
|
||||
WipeoutFromPolyline(Handle),
|
||||
/// `erase_source` controls whether the source boundary is consumed.
|
||||
WipeoutFromPolyline {
|
||||
handle: Handle,
|
||||
erase_source: bool,
|
||||
},
|
||||
/// Temporarily switch between paper and Model while MVIEW defines a new
|
||||
/// model-space window, keeping the command active.
|
||||
MviewSwitchLayout(String),
|
||||
|
||||
@@ -2069,6 +2069,8 @@ fn tessellate_dimension_inner(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -2100,6 +2102,8 @@ fn tessellate_dimension_inner(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -2131,6 +2135,8 @@ fn tessellate_dimension_inner(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -2163,6 +2169,8 @@ fn tessellate_dimension_inner(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -2211,6 +2219,8 @@ fn tessellate_dimension_inner(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -301,6 +301,11 @@ fn geo_marker_lines(data: &GeoPositionMarkerData) -> Vec<[f64; 3]> {
|
||||
fn point_cloud_lines(data: &PointCloudData) -> Vec<[f64; 3]> {
|
||||
let mut points = Vec::new();
|
||||
push_box(&mut points, data.extents_min, data.extents_max);
|
||||
points
|
||||
}
|
||||
|
||||
fn point_cloud_clip_lines(data: &PointCloudData) -> Vec<[f64; 3]> {
|
||||
let mut points = Vec::new();
|
||||
if data.show_clipping {
|
||||
for clip in &data.clippings {
|
||||
if clip.vertices.len() < 2 {
|
||||
@@ -323,6 +328,11 @@ fn point_cloud_lines(data: &PointCloudData) -> Vec<[f64; 3]> {
|
||||
fn point_cloud_ex_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
|
||||
let mut points = Vec::new();
|
||||
push_box(&mut points, data.extents_min, data.extents_max);
|
||||
points
|
||||
}
|
||||
|
||||
fn point_cloud_ex_clip_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
|
||||
let mut points = Vec::new();
|
||||
if data.show_cropping {
|
||||
for crop in &data.croppings {
|
||||
if crop.points.len() < 2 {
|
||||
@@ -339,6 +349,14 @@ fn point_cloud_ex_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
|
||||
points
|
||||
}
|
||||
|
||||
pub(crate) fn point_cloud_frame_lines(entity: &ExtendedEntity) -> Option<Vec<[f64; 3]>> {
|
||||
match &entity.data {
|
||||
ExtendedEntityData::PointCloud(data) => Some(point_cloud_clip_lines(data)),
|
||||
ExtendedEntityData::PointCloudEx(data) => Some(point_cloud_ex_clip_lines(data)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn camera_lines(document: &acadrust::CadDocument, view_handle: Handle) -> Vec<[f64; 3]> {
|
||||
let Some(view) = document.views.iter().find(|view| view.handle == view_handle) else {
|
||||
return Vec::new();
|
||||
|
||||
@@ -677,6 +677,8 @@ impl LeaderTess for Leader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -853,6 +855,8 @@ impl LeaderTess for Leader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -1506,6 +1506,8 @@ impl MultiLeaderTess for MultiLeader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1843,6 +1845,8 @@ impl MultiLeaderTess for MultiLeader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1913,6 +1917,8 @@ impl MultiLeaderTess for MultiLeader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1998,6 +2004,8 @@ impl MultiLeaderTess for MultiLeader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -2038,6 +2046,8 @@ impl MultiLeaderTess for MultiLeader {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
+207
-185
@@ -7,6 +7,7 @@ use crate::entities::text_support::{resolve_text_style, text_local_bounds};
|
||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
||||
use crate::scene::convert::acad_to_render::{GlyphRun, TextStroke, RenderEntity, RenderObject};
|
||||
use crate::scene::model::object::{GripApply, GripDef, PropSection, PropValue, Property};
|
||||
use crate::scene::model::wire_model::SnapHint;
|
||||
use crate::scene::text::lff;
|
||||
|
||||
// ── Shared geometry helpers ───────────────────────────────────────────────────
|
||||
@@ -416,62 +417,112 @@ impl Transformable for RasterImage {
|
||||
|
||||
// ── Wipeout ───────────────────────────────────────────────────────────────────
|
||||
|
||||
fn wipeout_is_polygonal(wipeout: &Wipeout) -> bool {
|
||||
wipeout.clipping_enabled
|
||||
&& wipeout.clip_boundary_vertices.len() >= 3
|
||||
&& matches!(
|
||||
wipeout.clip_type,
|
||||
acadrust::entities::WipeoutClipType::Polygonal
|
||||
)
|
||||
}
|
||||
|
||||
fn wipeout_clip_to_world(wipeout: &Wipeout, point: &acadrust::types::Vector2) -> [f64; 3] {
|
||||
let x = point.x + wipeout.size.x * 0.5;
|
||||
let y = wipeout.size.y * 0.5 - point.y;
|
||||
wipeout_plane(wipeout).point_at([x, y])
|
||||
}
|
||||
|
||||
fn wipeout_plane(wipeout: &Wipeout) -> cadkernel::space::Plane {
|
||||
cadkernel::space::Plane::from_axes(
|
||||
[
|
||||
wipeout.insertion_point.x,
|
||||
wipeout.insertion_point.y,
|
||||
wipeout.insertion_point.z,
|
||||
],
|
||||
[
|
||||
wipeout.u_vector.x,
|
||||
wipeout.u_vector.y,
|
||||
wipeout.u_vector.z,
|
||||
],
|
||||
[
|
||||
wipeout.v_vector.x,
|
||||
wipeout.v_vector.y,
|
||||
wipeout.v_vector.z,
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn wipeout_boundary(wipeout: &Wipeout) -> Vec<[f64; 3]> {
|
||||
if wipeout_is_polygonal(wipeout) {
|
||||
wipeout
|
||||
.clip_boundary_vertices
|
||||
.iter()
|
||||
.map(|point| wipeout_clip_to_world(wipeout, point))
|
||||
.collect()
|
||||
} else {
|
||||
image_corners(
|
||||
&wipeout.insertion_point,
|
||||
&wipeout.u_vector,
|
||||
&wipeout.v_vector,
|
||||
wipeout.size.x,
|
||||
wipeout.size.y,
|
||||
)
|
||||
.to_vec()
|
||||
}
|
||||
}
|
||||
|
||||
fn wipeout_pick_rings(wipeout: &Wipeout) -> Vec<Vec<[f64; 3]>> {
|
||||
let boundary = wipeout_boundary(wipeout);
|
||||
if wipeout_is_polygonal(wipeout)
|
||||
&& matches!(
|
||||
wipeout.clip_mode,
|
||||
acadrust::entities::WipeoutClipMode::Inside
|
||||
)
|
||||
{
|
||||
vec![
|
||||
image_corners(
|
||||
&wipeout.insertion_point,
|
||||
&wipeout.u_vector,
|
||||
&wipeout.v_vector,
|
||||
wipeout.size.x,
|
||||
wipeout.size.y,
|
||||
)
|
||||
.to_vec(),
|
||||
boundary,
|
||||
]
|
||||
} else {
|
||||
vec![boundary]
|
||||
}
|
||||
}
|
||||
|
||||
fn wipeout_world_to_clip(wipeout: &Wipeout, world: [f64; 3]) -> Option<acadrust::types::Vector2> {
|
||||
let [x, y] = wipeout_plane(wipeout).project(world)?;
|
||||
Some(acadrust::types::Vector2::new(
|
||||
x - wipeout.size.x * 0.5,
|
||||
wipeout.size.y * 0.5 - y,
|
||||
))
|
||||
}
|
||||
|
||||
impl RenderConvertible for Wipeout {
|
||||
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||
let corners = image_corners(
|
||||
&self.insertion_point,
|
||||
&self.u_vector,
|
||||
&self.v_vector,
|
||||
self.size.x,
|
||||
self.size.y,
|
||||
);
|
||||
|
||||
// If clipping is enabled and there's a polygon boundary, show that.
|
||||
let pts = if self.clipping_enabled
|
||||
&& self.clip_boundary_vertices.len() >= 3
|
||||
&& matches!(
|
||||
self.clip_type,
|
||||
acadrust::entities::WipeoutClipType::Polygonal
|
||||
) {
|
||||
// Clip vertices are stored in image-pixel space, centred on the
|
||||
// image (range ±size/2). The image's bottom-left corner sits at
|
||||
// `insertion_point`, the image-Y axis points DOWN (per DXF
|
||||
// "v_vector points down the image"), so map:
|
||||
// x_off = (clip.x + size.x/2) × u_vector
|
||||
// y_off = (size.y/2 − clip.y) × v_vector ← y flipped
|
||||
let ox = self.insertion_point.x;
|
||||
let oy = self.insertion_point.y;
|
||||
let oz = self.insertion_point.z;
|
||||
let mut poly: Vec<[f64; 3]> = self
|
||||
.clip_boundary_vertices
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let cx = v.x + self.size.x * 0.5;
|
||||
let cy = self.size.y * 0.5 - v.y;
|
||||
let wx = self.u_vector.x * cx + self.v_vector.x * cy;
|
||||
let wy = self.u_vector.y * cx + self.v_vector.y * cy;
|
||||
let wz = self.u_vector.z * cx + self.v_vector.z * cy;
|
||||
[ox + wx, oy + wy, oz + wz]
|
||||
})
|
||||
.collect();
|
||||
// Close the polygon.
|
||||
if let Some(&first) = poly.first() {
|
||||
poly.push(first);
|
||||
}
|
||||
poly
|
||||
} else {
|
||||
// Rectangular boundary — just the border, no diagonals (mask area).
|
||||
image_wire(corners, false)
|
||||
};
|
||||
let boundary = wipeout_boundary(self);
|
||||
let mut pts = boundary.clone();
|
||||
if let Some(&first) = pts.first() {
|
||||
pts.push(first);
|
||||
}
|
||||
|
||||
Some(RenderEntity {
|
||||
// Interior pick surface — a wipeout reads as a solid patch, so a
|
||||
// click anywhere on it should select it.
|
||||
pick_tris: crate::entities::common::quad_pick_tris(&corners),
|
||||
pick_tris: cadkernel::space::polygon::triangulate_rings(
|
||||
&wipeout_pick_rings(self),
|
||||
cadkernel::geom2d::Tolerance::default(),
|
||||
),
|
||||
object: RenderObject::Lines(pts),
|
||||
snap_pts: vec![],
|
||||
snap_pts: boundary
|
||||
.iter()
|
||||
.map(|point| (glam::DVec3::from(*point), SnapHint::Endpoint))
|
||||
.collect(),
|
||||
tangent_geoms: vec![],
|
||||
key_vertices: corners.to_vec(),
|
||||
key_vertices: boundary,
|
||||
fill_tris: vec![],
|
||||
})
|
||||
}
|
||||
@@ -479,33 +530,15 @@ impl RenderConvertible for Wipeout {
|
||||
|
||||
impl Grippable for Wipeout {
|
||||
fn grips(&self) -> Vec<GripDef> {
|
||||
// If polygonal clipping is active, expose individual polygon vertices as grips.
|
||||
let is_polygon = self.clipping_enabled
|
||||
&& self.clip_boundary_vertices.len() >= 3
|
||||
&& matches!(
|
||||
self.clip_type,
|
||||
acadrust::entities::WipeoutClipType::Polygonal
|
||||
);
|
||||
|
||||
if is_polygon {
|
||||
let ox = self.insertion_point.x;
|
||||
let oy = self.insertion_point.y;
|
||||
let oz = self.insertion_point.z;
|
||||
// Same image-pixel-space → WCS mapping as `to_render` so grips
|
||||
// sit exactly on the rendered polygon vertices.
|
||||
self.clip_boundary_vertices
|
||||
.iter()
|
||||
if wipeout_is_polygonal(self) {
|
||||
wipeout_boundary(self)
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.map(|(i, v)| {
|
||||
let cx = v.x + self.size.x * 0.5;
|
||||
let cy = self.size.y * 0.5 - v.y;
|
||||
let wx = self.u_vector.x * cx + self.v_vector.x * cy;
|
||||
let wy = self.u_vector.y * cx + self.v_vector.y * cy;
|
||||
let wz = self.u_vector.z * cx + self.v_vector.z * cy;
|
||||
.map(|(i, point)| {
|
||||
if i == 0 {
|
||||
square_grip(i, glam::DVec3::new(ox + wx, oy + wy, oz + wz))
|
||||
square_grip(i, glam::DVec3::from(point))
|
||||
} else {
|
||||
center_grip(i, glam::DVec3::new(ox + wx, oy + wy, oz + wz))
|
||||
center_grip(i, glam::DVec3::from(point))
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
@@ -527,61 +560,103 @@ impl Grippable for Wipeout {
|
||||
}
|
||||
|
||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||
let is_polygon = self.clipping_enabled
|
||||
&& self.clip_boundary_vertices.len() >= 3
|
||||
&& matches!(
|
||||
self.clip_type,
|
||||
acadrust::entities::WipeoutClipType::Polygonal
|
||||
);
|
||||
|
||||
if is_polygon {
|
||||
// Move the clicked polygon vertex in world space → back-project to pixel space.
|
||||
if let Some(v) = self.clip_boundary_vertices.get_mut(grip_id) {
|
||||
// Compute current world position of this vertex.
|
||||
let ox = self.insertion_point.x;
|
||||
let oy = self.insertion_point.y;
|
||||
let oz = self.insertion_point.z;
|
||||
let cur_wx =
|
||||
ox + self.u_vector.x * v.x * self.size.x + self.v_vector.x * v.y * self.size.y;
|
||||
let cur_wy =
|
||||
oy + self.u_vector.y * v.x * self.size.x + self.v_vector.y * v.y * self.size.y;
|
||||
let cur_wz =
|
||||
oz + self.u_vector.z * v.x * self.size.x + self.v_vector.z * v.y * self.size.y;
|
||||
if wipeout_is_polygonal(self) {
|
||||
if let Some(current) = self.clip_boundary_vertices.get(grip_id).cloned() {
|
||||
let current_world = wipeout_clip_to_world(self, ¤t);
|
||||
let new_w = match apply {
|
||||
GripApply::Translate(d) => [
|
||||
cur_wx + d.x as f64,
|
||||
cur_wy + d.y as f64,
|
||||
cur_wz + d.z as f64,
|
||||
current_world[0] + d.x as f64,
|
||||
current_world[1] + d.y as f64,
|
||||
current_world[2] + d.z as f64,
|
||||
],
|
||||
GripApply::Absolute(p) => [p.x as f64, p.y as f64, p.z as f64],
|
||||
};
|
||||
// Back-project: solve for pixel coords using u_vector and v_vector.
|
||||
// In 2D (XY plane): new_w - insertion_point = u_vec * vx * sx + v_vec * vy * sy
|
||||
let dx = new_w[0] - self.insertion_point.x;
|
||||
let dy = new_w[1] - self.insertion_point.y;
|
||||
let ux = self.u_vector.x * self.size.x;
|
||||
let uy = self.u_vector.y * self.size.x;
|
||||
let vx = self.v_vector.x * self.size.y;
|
||||
let vy = self.v_vector.y * self.size.y;
|
||||
let det = ux * vy - uy * vx;
|
||||
if det.abs() > 1e-12 {
|
||||
v.x = (dx * vy - dy * vx) / det;
|
||||
v.y = (ux * dy - uy * dx) / det;
|
||||
if let Some(clip) = wipeout_world_to_clip(self, new_w) {
|
||||
self.clip_boundary_vertices[grip_id] = clip;
|
||||
}
|
||||
}
|
||||
} else if grip_id == 0 {
|
||||
match apply {
|
||||
GripApply::Translate(d) => {
|
||||
self.insertion_point.x += d.x as f64;
|
||||
self.insertion_point.y += d.y as f64;
|
||||
self.insertion_point.z += d.z as f64;
|
||||
}
|
||||
GripApply::Absolute(p) => {
|
||||
self.insertion_point.x = p.x as f64;
|
||||
self.insertion_point.y = p.y as f64;
|
||||
self.insertion_point.z = p.z as f64;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let corners = wipeout_boundary(self);
|
||||
let Some(current) = corners.get(grip_id).copied() else {
|
||||
return;
|
||||
};
|
||||
let target = match apply {
|
||||
GripApply::Translate(delta) => glam::DVec3::from(current) + delta,
|
||||
GripApply::Absolute(point) => point,
|
||||
};
|
||||
let u_hat = glam::DVec3::new(self.u_vector.x, self.u_vector.y, self.u_vector.z)
|
||||
.normalize_or_zero();
|
||||
let v_hat = glam::DVec3::new(self.v_vector.x, self.v_vector.y, self.v_vector.z)
|
||||
.normalize_or_zero();
|
||||
if u_hat == glam::DVec3::ZERO || v_hat == glam::DVec3::ZERO {
|
||||
return;
|
||||
}
|
||||
let resize_plane = cadkernel::space::Plane::from_axes(
|
||||
[0.0; 3],
|
||||
u_hat.to_array(),
|
||||
v_hat.to_array(),
|
||||
);
|
||||
let (width, height, insertion) = match grip_id {
|
||||
0 => {
|
||||
let fixed = glam::DVec3::from(corners[2]);
|
||||
let Some([width, height]) =
|
||||
resize_plane.project_vector((fixed - target).to_array())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
(width, height, target)
|
||||
}
|
||||
1 => {
|
||||
let fixed = glam::DVec3::from(corners[3]);
|
||||
let Some([width, neg_height]) =
|
||||
resize_plane.project_vector((target - fixed).to_array())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let height = -neg_height;
|
||||
(width, height, fixed - v_hat * height)
|
||||
}
|
||||
2 => {
|
||||
let fixed = glam::DVec3::from(corners[0]);
|
||||
let Some([width, height]) =
|
||||
resize_plane.project_vector((target - fixed).to_array())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
(width, height, fixed)
|
||||
}
|
||||
3 => {
|
||||
let fixed = glam::DVec3::from(corners[1]);
|
||||
let Some([neg_width, height]) =
|
||||
resize_plane.project_vector((target - fixed).to_array())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let width = -neg_width;
|
||||
(width, height, fixed - u_hat * width)
|
||||
}
|
||||
_ => return,
|
||||
};
|
||||
if width.abs() > 1e-9 && height.abs() > 1e-9 {
|
||||
self.insertion_point = acadrust::types::Vector3::new(
|
||||
insertion.x,
|
||||
insertion.y,
|
||||
insertion.z,
|
||||
);
|
||||
let sx = self.size.x.abs().max(1e-9);
|
||||
let sy = self.size.y.abs().max(1e-9);
|
||||
self.u_vector = acadrust::types::Vector3::new(
|
||||
u_hat.x * width / sx,
|
||||
u_hat.y * width / sx,
|
||||
u_hat.z * width / sx,
|
||||
);
|
||||
self.v_vector = acadrust::types::Vector3::new(
|
||||
v_hat.x * height / sy,
|
||||
v_hat.y * height / sy,
|
||||
v_hat.z * height / sy,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -607,76 +682,23 @@ impl PropertyEditable for Wipeout {
|
||||
PropSection {
|
||||
title: t!("Misc").into_owned(),
|
||||
props: vec![
|
||||
Property {
|
||||
label: t!("Show image").into_owned(),
|
||||
field: "wo_show_image",
|
||||
value: PropValue::BoolToggle {
|
||||
field: "wo_show_image",
|
||||
value: show_image,
|
||||
},
|
||||
},
|
||||
Property {
|
||||
label: t!("Show clipped").into_owned(),
|
||||
field: "wo_show_clipped",
|
||||
value: PropValue::BoolToggle {
|
||||
field: "wo_show_clipped",
|
||||
value: show_clipped,
|
||||
},
|
||||
},
|
||||
Property {
|
||||
label: t!("Background transparency").into_owned(),
|
||||
field: "wo_bg_transparency",
|
||||
value: PropValue::BoolToggle {
|
||||
field: "wo_bg_transparency",
|
||||
value: bg_transparency,
|
||||
},
|
||||
},
|
||||
ro(t!("Show image").as_ref(), "wo_show_image", if show_image { t!("Yes") } else { t!("No") }),
|
||||
ro(t!("Show clipped").as_ref(), "wo_show_clipped", if show_clipped { t!("Yes") } else { t!("No") }),
|
||||
ro(t!("Background transparency").as_ref(), "wo_bg_transparency", if bg_transparency { t!("Yes") } else { t!("No") }),
|
||||
],
|
||||
},
|
||||
PropSection {
|
||||
title: t!("Image Adjust").into_owned(),
|
||||
props: vec![
|
||||
ro(t!("Brightness").as_ref(), "wo_brightness", self.brightness.to_string()),
|
||||
ro(t!("Contrast").as_ref(), "wo_contrast", self.contrast.to_string()),
|
||||
ro(t!("Fade").as_ref(), "wo_fade", self.fade.to_string()),
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn apply_geom_prop(&mut self, field: &str, value: &str) {
|
||||
match field {
|
||||
"wo_show_image" => {
|
||||
let on = if value == "toggle" {
|
||||
!self.flags.contains(acadrust::entities::WipeoutDisplayFlags::SHOW_IMAGE)
|
||||
} else {
|
||||
value == "true"
|
||||
};
|
||||
self.set_frame_visible(on);
|
||||
return;
|
||||
}
|
||||
"wo_show_clipped" => {
|
||||
let on = if value == "toggle" {
|
||||
!self
|
||||
.flags
|
||||
.contains(acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY)
|
||||
} else {
|
||||
value == "true"
|
||||
};
|
||||
if on {
|
||||
self.flags |= acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY;
|
||||
} else {
|
||||
self.flags -= acadrust::entities::WipeoutDisplayFlags::USE_CLIPPING_BOUNDARY;
|
||||
}
|
||||
return;
|
||||
}
|
||||
"wo_bg_transparency" => {
|
||||
let on = if value == "toggle" {
|
||||
!self.flags.contains(acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON)
|
||||
} else {
|
||||
value == "true"
|
||||
};
|
||||
if on {
|
||||
self.flags |= acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON;
|
||||
} else {
|
||||
self.flags -= acadrust::entities::WipeoutDisplayFlags::TRANSPARENCY_ON;
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let Ok(v) = value.trim().parse::<f64>() else {
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1670,6 +1670,8 @@ pub fn tessellate_table(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -1814,6 +1814,10 @@ fn vardict_value(doc: &CadDocument, name: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn drawing_variable(doc: &CadDocument, name: &str) -> Option<String> {
|
||||
vardict_value(doc, name)
|
||||
}
|
||||
|
||||
/// Write a drawing variable, creating the variable dictionary and record when
|
||||
/// needed so new drawings preserve the value too.
|
||||
pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &str) {
|
||||
|
||||
@@ -875,6 +875,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Discussions" => Some(("common", "discussions")),
|
||||
"Discussions load from GitHub." => Some(("common", "discussions-load-from-github")),
|
||||
"Display" => Some(("common", "display")),
|
||||
"Display but not plot" => Some(("draw", "display-but-not-plot")),
|
||||
"Distance" => Some(("common", "distance")),
|
||||
"Distance = %{dist}, Angle in XY Plane = %{angle_xy}°\n Delta X = %{dx}, Delta Y = %{dy}, Delta Z = %{dz}" => Some(("inquiry", "distance-dist-angle-in-xy-plane-angle-xy-delta")),
|
||||
"Distance = %{dist}, Angle in XY Plane = %{angle_xy}°, Angle from XY Plane = %{angle_z}°\n Delta X = %{dx}, Delta Y = %{dy}, Delta Z = %{dz}" => Some(("inquiry", "distance-dist-angle-in-xy-plane-angle-xy-angle")),
|
||||
@@ -1104,9 +1105,11 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Fraction format (DIMFRAC)" => Some(("common", "fraction-format-dimfrac")),
|
||||
"Fractional" => Some(("common", "fractional")),
|
||||
"Frame Text" => Some(("properties", "frame-text")),
|
||||
"Frames" => Some(("draw", "frames")),
|
||||
"Frames & Print" => Some(("insert", "frames-print")),
|
||||
"Frames Off" => Some(("insert", "frames-off")),
|
||||
"Frames On" => Some(("insert", "frames-on")),
|
||||
"Frames On, Not Plotted" => Some(("draw", "frames-on-not-plotted")),
|
||||
"Freeze" => Some(("layers", "freeze")),
|
||||
"From DXF" => Some(("properties", "from-dxf")),
|
||||
"Front" => Some(("common", "front")),
|
||||
@@ -1205,6 +1208,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Identifier" => Some(("properties", "identifier")),
|
||||
"Ignore" => Some(("properties", "ignore")),
|
||||
"Illuminance Distance" => Some(("properties", "illuminance-distance")),
|
||||
"Image Adjust" => Some(("draw", "image-adjust")),
|
||||
"Import" => Some(("common", "import")),
|
||||
"In" => Some(("annotate", "in")),
|
||||
"Inactive" => Some(("layout", "inactive")),
|
||||
@@ -2223,6 +2227,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"SETBYLAYER: select entities first." => Some(("styles", "setbylayer-select-entities-first")),
|
||||
"SETVAR new value (blank to read):" => Some(("styles", "setvar-new-value-blank-to-read")),
|
||||
"SETVAR variable name:" => Some(("styles", "setvar-variable-name")),
|
||||
"SETVAR: {name} requires 0, 1, or 2." => Some(("draw", "setvar-frame-range")),
|
||||
"SHEETSET: Sheet Set Manager not yet implemented." => Some(("view", "sheetset-sheet-set-manager-not-yet-implemented")),
|
||||
"SHX" => Some(("properties", "shx")),
|
||||
"SKETCH Pen down — move to sketch, click to lift, Enter to record:" => Some(("draw", "sketch-pen-down-move-to-sketch-click-to-lift-e")),
|
||||
@@ -2916,9 +2921,12 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
||||
"Volume" => Some(("properties", "volume")),
|
||||
"WBLOCK Saved \"{block_name}\" → \"{}\"" => Some(("common", "wblock-saved-block-name")),
|
||||
"WBLOCK save failed: {error}" => Some(("common", "wblock-save-failed-error")),
|
||||
"WIPEOUT Specify first point or [Polyline]:" => Some(("draw", "wipeout-specify-first-point-or-polyline")),
|
||||
"WIPEOUT Specify first point or [Frames/Polyline] <Polyline>:" => Some(("draw", "wipeout-specify-first-point-or-polyline")),
|
||||
"WIPEOUT Specify next point or [Undo/Close] (%{n} points):" => Some(("draw", "wipeout-specify-next-point-or-undo-close-n-poi")),
|
||||
"WIPEOUT Frames Enter frame mode [Off/On/DisplayButNotPlot] <%{mode}>:" => Some(("draw", "wipeout-frames-enter-frame-mode")),
|
||||
"WIPEOUT Polyline Erase source polyline? [Yes/No] <No>:" => Some(("draw", "wipeout-polyline-erase-source")),
|
||||
"WIPEOUT Polyline Select a closed planar polyline:" => Some(("draw", "wipeout-polyline-select-a-closed-planar-polyli")),
|
||||
"WIPEOUT Polyline: select a straight, closed, planar 2D polyline with at least 3 non-intersecting vertices." => Some(("draw", "wipeout-polyline-invalid")),
|
||||
"WIPEOUT Rectangular Specify first corner:" => Some(("draw", "wipeout-rectangular-specify-first-corner")),
|
||||
"WIPEOUT Rectangular Specify opposite corner:" => Some(("draw", "wipeout-rectangular-specify-opposite-corner")),
|
||||
"Warning" => Some(("errors", "warning")),
|
||||
|
||||
@@ -223,6 +223,8 @@ impl CadCommand for AlignedDimensionCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -279,6 +281,8 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -164,6 +164,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -195,6 +195,8 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -183,6 +183,8 @@ impl CadCommand for DimBaselineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -163,6 +163,8 @@ impl CadCommand for DimContinueCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -89,6 +89,8 @@ impl CadCommand for DimJogLineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -106,6 +106,8 @@ impl CadCommand for DimTeditCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -303,6 +303,8 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -240,6 +240,8 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -230,6 +230,8 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -465,6 +465,8 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -140,6 +140,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -96,6 +96,8 @@ impl CadCommand for QdimCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -200,6 +200,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -202,6 +202,8 @@ impl CadCommand for TableCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -106,6 +106,8 @@ impl CadCommand for ToleranceCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -204,6 +204,8 @@ impl CadCommand for AttdefCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -348,6 +348,8 @@ impl HatchCommand {
|
||||
scale: source.scale,
|
||||
world_origin: origin,
|
||||
boundary_wcs: Some(std::sync::Arc::new(wcs)),
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
draw_depth: source.draw_depth,
|
||||
@@ -387,6 +389,8 @@ impl HatchCommand {
|
||||
scale: 1.0,
|
||||
world_origin: origin,
|
||||
boundary_wcs: Some(std::sync::Arc::new(wcs)),
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
draw_depth: 0.0,
|
||||
@@ -699,6 +703,8 @@ impl GradientCommand {
|
||||
scale: 1.0,
|
||||
world_origin: origin,
|
||||
boundary_wcs: Some(std::sync::Arc::new(wcs)),
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
draw_depth: 0.0,
|
||||
|
||||
@@ -184,6 +184,8 @@ impl CadCommand for MlineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -126,6 +126,8 @@ impl CadCommand for ImageCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -88,6 +88,8 @@ impl CadCommand for RayCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -188,6 +190,8 @@ impl CadCommand for XLineCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
+188
-243
@@ -2,7 +2,9 @@
|
||||
|
||||
use acadrust::entities::{Wipeout, WipeoutClipType};
|
||||
use acadrust::types::{Vector2, Vector3};
|
||||
use acadrust::{EntityType, Handle};
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
use cadkernel::geom2d::{polygon_frame, Tolerance};
|
||||
use cadkernel::space::Plane;
|
||||
use glam::DVec3;
|
||||
use crate::t;
|
||||
|
||||
@@ -26,6 +28,8 @@ pub struct WipeoutCommand {
|
||||
first: Option<DVec3>,
|
||||
points: Vec<DVec3>,
|
||||
plane: WorkingPlane,
|
||||
selected_polyline: Option<Handle>,
|
||||
frame_mode: i16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
@@ -33,15 +37,19 @@ enum WipeoutMode {
|
||||
Draw,
|
||||
Polyline,
|
||||
Rectangular,
|
||||
Frames,
|
||||
ErasePolyline,
|
||||
}
|
||||
|
||||
impl WipeoutCommand {
|
||||
pub fn new_polygonal() -> Self {
|
||||
pub fn new_polygonal(frame_mode: i16) -> Self {
|
||||
Self {
|
||||
mode: WipeoutMode::Draw,
|
||||
first: None,
|
||||
points: Vec::new(),
|
||||
plane: WorkingPlane::default(),
|
||||
selected_polyline: None,
|
||||
frame_mode: frame_mode.clamp(0, 2),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,6 +59,8 @@ impl WipeoutCommand {
|
||||
first: None,
|
||||
points: Vec::new(),
|
||||
plane: WorkingPlane::default(),
|
||||
selected_polyline: None,
|
||||
frame_mode: 1,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,17 +71,27 @@ impl WipeoutCommand {
|
||||
first: None,
|
||||
points: Vec::new(),
|
||||
plane: WorkingPlane::default(),
|
||||
selected_polyline: None,
|
||||
frame_mode: 1,
|
||||
}
|
||||
}
|
||||
|
||||
fn finish_draw(&self) -> CmdResult {
|
||||
let local: Vec<DVec3> = self
|
||||
let local: Vec<[f64; 2]> = self
|
||||
.points
|
||||
.iter()
|
||||
.map(|point| self.plane.to_local(*point))
|
||||
.map(|point| {
|
||||
let point = self.plane.to_local(*point);
|
||||
[point.x, point.y]
|
||||
})
|
||||
.collect();
|
||||
match make_poly_wipeout(&local) {
|
||||
Some(entity) => CmdResult::CommitAndExit(self.plane.place_entity(entity)),
|
||||
let plane = Plane::from_axes(
|
||||
self.plane.origin.to_array(),
|
||||
self.plane.x.to_array(),
|
||||
self.plane.y.to_array(),
|
||||
);
|
||||
match make_poly_wipeout(&local, plane) {
|
||||
Some(entity) => CmdResult::CommitAndExit(entity),
|
||||
None => CmdResult::NeedPoint,
|
||||
}
|
||||
}
|
||||
@@ -94,7 +114,7 @@ impl CadCommand for WipeoutCommand {
|
||||
fn prompt(&self) -> String {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw if self.points.is_empty() => {
|
||||
t!("WIPEOUT Specify first point or [Polyline]:").into_owned()
|
||||
t!("WIPEOUT Specify first point or [Frames/Polyline] <Polyline>:").into_owned()
|
||||
}
|
||||
WipeoutMode::Draw => {
|
||||
let n = self.points.len();
|
||||
@@ -113,18 +133,38 @@ impl CadCommand for WipeoutCommand {
|
||||
WipeoutMode::Rectangular => {
|
||||
t!("WIPEOUT Rectangular Specify opposite corner:").into_owned()
|
||||
}
|
||||
WipeoutMode::Frames => t!(
|
||||
"WIPEOUT Frames Enter frame mode [Off/On/DisplayButNotPlot] <%{mode}>:",
|
||||
mode = self.frame_mode
|
||||
)
|
||||
.into_owned(),
|
||||
WipeoutMode::ErasePolyline => {
|
||||
t!("WIPEOUT Polyline Erase source polyline? [Yes/No] <No>:").into_owned()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn options(&self) -> Vec<CmdOption> {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw if self.points.is_empty() => {
|
||||
vec![CmdOption::new(t!("Polyline").as_ref(), "P")]
|
||||
vec![
|
||||
CmdOption::new(t!("Frames").as_ref(), "F"),
|
||||
CmdOption::new(t!("Polyline").as_ref(), "P"),
|
||||
]
|
||||
}
|
||||
WipeoutMode::Draw => vec![
|
||||
CmdOption::new(t!("Undo").as_ref(), "U"),
|
||||
CmdOption::new(t!("Close").as_ref(), "C"),
|
||||
],
|
||||
WipeoutMode::Frames => vec![
|
||||
CmdOption::new(t!("Off").as_ref(), "OFF"),
|
||||
CmdOption::new(t!("On").as_ref(), "ON"),
|
||||
CmdOption::new(t!("Display but not plot").as_ref(), "D"),
|
||||
],
|
||||
WipeoutMode::ErasePolyline => vec![
|
||||
CmdOption::new(t!("Yes").as_ref(), "Y"),
|
||||
CmdOption::new(t!("No").as_ref(), "N"),
|
||||
],
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -133,9 +173,13 @@ impl CadCommand for WipeoutCommand {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw => {
|
||||
if let Some(first) = self.points.first() {
|
||||
let distance_squared =
|
||||
(point.x - first.x).powi(2) + (point.y - first.y).powi(2);
|
||||
if self.points.len() >= 3 && distance_squared < 1e-12 {
|
||||
let first = self.plane.to_local(*first);
|
||||
let point = self.plane.to_local(point);
|
||||
let distance = cadkernel::geom2d::Vec2::new(point.x, point.y)
|
||||
.distance(cadkernel::geom2d::Vec2::new(first.x, first.y));
|
||||
if self.points.len() >= 3
|
||||
&& distance <= Tolerance::default().linear()
|
||||
{
|
||||
return self.finish_draw();
|
||||
}
|
||||
}
|
||||
@@ -146,21 +190,35 @@ impl CadCommand for WipeoutCommand {
|
||||
if let Some(first) = self.first {
|
||||
let first = self.plane.to_local(first);
|
||||
let point = self.plane.to_local(point);
|
||||
CmdResult::CommitAndExit(
|
||||
self.plane.place_entity(make_rect_wipeout(first, point)),
|
||||
)
|
||||
let plane = Plane::from_axes(
|
||||
self.plane.origin.to_array(),
|
||||
self.plane.x.to_array(),
|
||||
self.plane.y.to_array(),
|
||||
);
|
||||
make_rect_wipeout(first, point, plane)
|
||||
.map_or(CmdResult::NeedPoint, CmdResult::CommitAndExit)
|
||||
} else {
|
||||
self.first = Some(point);
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
WipeoutMode::Polyline => CmdResult::NeedPoint,
|
||||
WipeoutMode::Polyline | WipeoutMode::Frames | WipeoutMode::ErasePolyline => {
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_enter(&mut self) -> CmdResult {
|
||||
match self.mode {
|
||||
WipeoutMode::Draw if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Polyline;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
WipeoutMode::Draw if self.points.len() >= 3 => self.finish_draw(),
|
||||
WipeoutMode::Frames => {
|
||||
CmdResult::Dispatch(format!("WIPEOUTFRAME {}", self.frame_mode))
|
||||
}
|
||||
WipeoutMode::ErasePolyline => self.finish_polyline(false),
|
||||
_ => CmdResult::Cancel,
|
||||
}
|
||||
}
|
||||
@@ -177,33 +235,55 @@ impl CadCommand for WipeoutCommand {
|
||||
if handle.is_null() {
|
||||
CmdResult::NeedPoint
|
||||
} else {
|
||||
CmdResult::WipeoutFromPolyline(handle)
|
||||
self.selected_polyline = Some(handle);
|
||||
self.mode = WipeoutMode::ErasePolyline;
|
||||
CmdResult::NeedPoint
|
||||
}
|
||||
}
|
||||
|
||||
fn wants_text_input(&self) -> bool {
|
||||
self.mode == WipeoutMode::Draw
|
||||
matches!(
|
||||
self.mode,
|
||||
WipeoutMode::Draw | WipeoutMode::Frames | WipeoutMode::ErasePolyline
|
||||
)
|
||||
}
|
||||
|
||||
fn point_step_accepts_keywords(&self) -> bool {
|
||||
self.mode == WipeoutMode::Draw
|
||||
matches!(
|
||||
self.mode,
|
||||
WipeoutMode::Draw | WipeoutMode::Frames | WipeoutMode::ErasePolyline
|
||||
)
|
||||
}
|
||||
|
||||
fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
|
||||
if self.mode != WipeoutMode::Draw {
|
||||
return None;
|
||||
}
|
||||
match text.trim().to_ascii_uppercase().as_str() {
|
||||
"P" | "POLYLINE" if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Polyline;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"U" | "UNDO" if !self.points.is_empty() => {
|
||||
Some(self.undo_point())
|
||||
}
|
||||
"C" | "CLOSE" if self.points.len() >= 3 => {
|
||||
Some(self.finish_draw())
|
||||
}
|
||||
let text = text.trim().to_ascii_uppercase();
|
||||
match self.mode {
|
||||
WipeoutMode::Draw => match text.as_str() {
|
||||
"F" | "FRAMES" if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Frames;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"P" | "POLYLINE" if self.points.is_empty() => {
|
||||
self.mode = WipeoutMode::Polyline;
|
||||
Some(CmdResult::NeedPoint)
|
||||
}
|
||||
"U" | "UNDO" if !self.points.is_empty() => Some(self.undo_point()),
|
||||
"C" | "CLOSE" if self.points.len() >= 3 => Some(self.finish_draw()),
|
||||
_ => None,
|
||||
},
|
||||
WipeoutMode::Frames => match text.as_str() {
|
||||
"0" | "OFF" => Some(CmdResult::Dispatch("WIPEOUTFRAME 0".into())),
|
||||
"1" | "ON" => Some(CmdResult::Dispatch("WIPEOUTFRAME 1".into())),
|
||||
"2" | "D" | "DISPLAYBUTNOTPLOT" => {
|
||||
Some(CmdResult::Dispatch("WIPEOUTFRAME 2".into()))
|
||||
}
|
||||
_ => None,
|
||||
},
|
||||
WipeoutMode::ErasePolyline => match text.as_str() {
|
||||
"Y" | "YES" => Some(self.finish_polyline(true)),
|
||||
"N" | "NO" => Some(self.finish_polyline(false)),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -264,250 +344,115 @@ impl CadCommand for WipeoutCommand {
|
||||
false,
|
||||
))
|
||||
}
|
||||
WipeoutMode::Polyline => None,
|
||||
WipeoutMode::Polyline | WipeoutMode::Frames | WipeoutMode::ErasePolyline => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn make_rect_wipeout(first: DVec3, second: DVec3) -> EntityType {
|
||||
let corner1 = Vector3::new(
|
||||
first.x.min(second.x),
|
||||
first.y.min(second.y),
|
||||
first.z,
|
||||
);
|
||||
let corner2 = Vector3::new(
|
||||
first.x.max(second.x),
|
||||
first.y.max(second.y),
|
||||
first.z,
|
||||
);
|
||||
EntityType::Wipeout(Wipeout::from_corners(corner1, corner2))
|
||||
}
|
||||
|
||||
fn same_point(first: DVec3, second: DVec3) -> bool {
|
||||
(first - second).length_squared() < 1e-18
|
||||
}
|
||||
|
||||
fn clean_boundary(points: &[DVec3]) -> Vec<DVec3> {
|
||||
let mut clean = Vec::with_capacity(points.len());
|
||||
for point in points.iter().copied().filter(|point| point.is_finite()) {
|
||||
if clean.last().is_none_or(|last| !same_point(*last, point)) {
|
||||
clean.push(point);
|
||||
}
|
||||
}
|
||||
if clean.len() > 1 && same_point(clean[0], *clean.last().unwrap()) {
|
||||
clean.pop();
|
||||
}
|
||||
clean
|
||||
}
|
||||
|
||||
fn make_poly_wipeout(points: &[DVec3]) -> Option<EntityType> {
|
||||
let points = clean_boundary(points);
|
||||
if points.len() < 3 {
|
||||
return None;
|
||||
}
|
||||
let z = points[0].z;
|
||||
if points.iter().any(|point| (point.z - z).abs() > 1e-7) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut min_x = points[0].x;
|
||||
let mut min_y = points[0].y;
|
||||
let mut max_x = points[0].x;
|
||||
let mut max_y = points[0].y;
|
||||
for point in points.iter().skip(1) {
|
||||
min_x = min_x.min(point.x);
|
||||
min_y = min_y.min(point.y);
|
||||
max_x = max_x.max(point.x);
|
||||
max_y = max_y.max(point.y);
|
||||
}
|
||||
let width = max_x - min_x;
|
||||
let height = max_y - min_y;
|
||||
if width < 1e-9 || height < 1e-9 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Stable signed area around a local origin rejects collinear boundaries
|
||||
// without losing precision on large world coordinates.
|
||||
let origin = points[0];
|
||||
let mut twice_area = 0.0;
|
||||
for index in 0..points.len() {
|
||||
let a = points[index] - origin;
|
||||
let b = points[(index + 1) % points.len()] - origin;
|
||||
twice_area += a.x * b.y - b.x * a.y;
|
||||
}
|
||||
if twice_area.abs() <= width * height * 1e-10 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Wipeout clip vertices use image-pixel coordinates centred on the image:
|
||||
// X grows right, Y grows up while the stored V vector points down.
|
||||
let clip_boundary_vertices = points
|
||||
.iter()
|
||||
.map(|point| {
|
||||
let normalized_x = (point.x - min_x) / width;
|
||||
let normalized_y = (point.y - min_y) / height;
|
||||
Vector2::new(normalized_x - 0.5, 0.5 - normalized_y)
|
||||
impl WipeoutCommand {
|
||||
fn finish_polyline(&self, erase_source: bool) -> CmdResult {
|
||||
self.selected_polyline.map_or(CmdResult::Cancel, |handle| {
|
||||
CmdResult::WipeoutFromPolyline {
|
||||
handle,
|
||||
erase_source,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wipeout_frame_mode(document: &CadDocument) -> i16 {
|
||||
crate::scene::frame::mode(document, crate::scene::frame::FrameKind::Wipeout)
|
||||
}
|
||||
|
||||
fn vector(value: [f64; 3]) -> Vector3 {
|
||||
Vector3::new(value[0], value[1], value[2])
|
||||
}
|
||||
|
||||
fn make_rect_wipeout(first: DVec3, second: DVec3, plane: Plane) -> Option<EntityType> {
|
||||
plane.normal()?;
|
||||
let points = [
|
||||
[first.x, first.y],
|
||||
[second.x, first.y],
|
||||
[second.x, second.y],
|
||||
[first.x, second.y],
|
||||
];
|
||||
let frame = polygon_frame(&points, Tolerance::default())?;
|
||||
let mut wipeout = Wipeout::new();
|
||||
wipeout.insertion_point = Vector3::new(min_x, min_y, z);
|
||||
wipeout.u_vector = Vector3::new(width, 0.0, 0.0);
|
||||
wipeout.v_vector = Vector3::new(0.0, height, 0.0);
|
||||
wipeout.insertion_point = vector(plane.point_at(frame.origin));
|
||||
wipeout.u_vector = vector(plane.x_axis) * frame.size[0];
|
||||
wipeout.v_vector = vector(plane.y_axis) * frame.size[1];
|
||||
Some(EntityType::Wipeout(wipeout))
|
||||
}
|
||||
|
||||
fn make_poly_wipeout(points: &[[f64; 2]], plane: Plane) -> Option<EntityType> {
|
||||
plane.normal()?;
|
||||
let frame = polygon_frame(points, Tolerance::default())?;
|
||||
let [width, height] = frame.size;
|
||||
let mut wipeout = Wipeout::new();
|
||||
wipeout.insertion_point = vector(plane.point_at(frame.origin));
|
||||
wipeout.u_vector = vector(plane.x_axis) * width;
|
||||
wipeout.v_vector = vector(plane.y_axis) * height;
|
||||
wipeout.size = Vector2::new(1.0, 1.0);
|
||||
wipeout.clip_type = WipeoutClipType::Polygonal;
|
||||
wipeout.clip_boundary_vertices = clip_boundary_vertices;
|
||||
wipeout.clip_boundary_vertices = frame
|
||||
.points
|
||||
.iter()
|
||||
.map(|point| {
|
||||
Vector2::new(
|
||||
(point[0] - frame.origin[0]) / width - 0.5,
|
||||
0.5 - (point[1] - frame.origin[1]) / height,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
wipeout.clipping_enabled = true;
|
||||
Some(EntityType::Wipeout(wipeout))
|
||||
}
|
||||
|
||||
fn explicitly_closed(points: &[DVec3]) -> bool {
|
||||
points.len() >= 4 && same_point(points[0], *points.last().unwrap())
|
||||
}
|
||||
|
||||
fn sample_2d_polyline(
|
||||
vertices: &[([f64; 2], f64)],
|
||||
elevation: f64,
|
||||
normal: (f64, f64, f64),
|
||||
) -> Vec<DVec3> {
|
||||
if vertices.len() < 3 {
|
||||
return Vec::new();
|
||||
}
|
||||
let to_wcs = |point: [f64; 2]| {
|
||||
let point = crate::scene::view::transform::ocs_point_to_wcs(
|
||||
(point[0], point[1], elevation),
|
||||
normal,
|
||||
);
|
||||
DVec3::new(point.0, point.1, point.2)
|
||||
};
|
||||
let mut points = vec![to_wcs(vertices[0].0)];
|
||||
for index in 0..vertices.len() {
|
||||
let (start, bulge) = vertices[index];
|
||||
let end_index = (index + 1) % vertices.len();
|
||||
let end = vertices[end_index].0;
|
||||
if let Some(arc) =
|
||||
crate::entities::common::BulgeArc::from_bulge(start, end, bulge)
|
||||
{
|
||||
let steps = ((arc.sweep.abs() / std::f64::consts::TAU * 64.0).ceil()
|
||||
as usize)
|
||||
.clamp(4, 64);
|
||||
for step in 1..=steps {
|
||||
if end_index == 0 && step == steps {
|
||||
break;
|
||||
}
|
||||
points.push(to_wcs(arc.sample(step as f64 / steps as f64)));
|
||||
}
|
||||
} else if end_index != 0 {
|
||||
points.push(to_wcs(end));
|
||||
}
|
||||
}
|
||||
points
|
||||
fn explicitly_closed(points: &[[f64; 2]]) -> bool {
|
||||
points.len() >= 4
|
||||
&& cadkernel::geom2d::Vec2::from(points[0])
|
||||
.distance(cadkernel::geom2d::Vec2::from(*points.last().unwrap()))
|
||||
<= Tolerance::default().linear()
|
||||
}
|
||||
|
||||
/// Build a wipeout boundary from a picked closed polyline without consuming
|
||||
/// the source entity. Bulged 2D segments are sampled into the polygonal mask.
|
||||
/// the source entity. Only straight, closed 2D polygonal boundaries qualify.
|
||||
pub(crate) fn wipeout_from_polyline(entity: &EntityType) -> Option<EntityType> {
|
||||
let points = match entity {
|
||||
fn from_ocs(points: &[[f64; 2]], normal: Vector3, elevation: f64) -> Option<EntityType> {
|
||||
make_poly_wipeout(points, crate::entities::curve::ocs_plane(normal, elevation))
|
||||
}
|
||||
|
||||
match entity {
|
||||
EntityType::LwPolyline(polyline) => {
|
||||
let raw: Vec<DVec3> = polyline
|
||||
if polyline.vertices.iter().any(|vertex| vertex.bulge != 0.0) {
|
||||
return None;
|
||||
}
|
||||
let raw: Vec<[f64; 2]> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| DVec3::new(vertex.location.x, vertex.location.y, polyline.elevation))
|
||||
.map(|vertex| [vertex.location.x, vertex.location.y])
|
||||
.collect();
|
||||
if !polyline.is_closed && !explicitly_closed(&raw) {
|
||||
return None;
|
||||
}
|
||||
let vertices: Vec<([f64; 2], f64)> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
(
|
||||
[vertex.location.x, vertex.location.y],
|
||||
vertex.bulge,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
sample_2d_polyline(
|
||||
&vertices,
|
||||
polyline.elevation,
|
||||
(
|
||||
polyline.normal.x,
|
||||
polyline.normal.y,
|
||||
polyline.normal.z,
|
||||
),
|
||||
)
|
||||
from_ocs(&raw, polyline.normal, polyline.elevation)
|
||||
}
|
||||
EntityType::Polyline2D(polyline) => {
|
||||
let raw: Vec<DVec3> = polyline
|
||||
if polyline.vertices.iter().any(|vertex| vertex.bulge != 0.0) {
|
||||
return None;
|
||||
}
|
||||
let raw: Vec<[f64; 2]> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
DVec3::new(
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
polyline.elevation,
|
||||
)
|
||||
})
|
||||
.map(|vertex| [vertex.location.x, vertex.location.y])
|
||||
.collect();
|
||||
if !polyline.is_closed() && !explicitly_closed(&raw) {
|
||||
return None;
|
||||
}
|
||||
let vertices: Vec<([f64; 2], f64)> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
(
|
||||
[vertex.location.x, vertex.location.y],
|
||||
vertex.bulge,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
sample_2d_polyline(
|
||||
&vertices,
|
||||
polyline.elevation,
|
||||
(
|
||||
polyline.normal.x,
|
||||
polyline.normal.y,
|
||||
polyline.normal.z,
|
||||
),
|
||||
)
|
||||
}
|
||||
EntityType::Polyline(polyline) => {
|
||||
let points: Vec<DVec3> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
DVec3::new(
|
||||
vertex.location.x,
|
||||
vertex.location.y,
|
||||
vertex.location.z,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
if !polyline.is_closed() && !explicitly_closed(&points) {
|
||||
return None;
|
||||
}
|
||||
points
|
||||
}
|
||||
EntityType::Polyline3D(polyline) => {
|
||||
let points: Vec<DVec3> = polyline
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|vertex| {
|
||||
DVec3::new(
|
||||
vertex.position.x,
|
||||
vertex.position.y,
|
||||
vertex.position.z,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
if !polyline.flags.closed && !explicitly_closed(&points) {
|
||||
return None;
|
||||
}
|
||||
points
|
||||
from_ocs(&raw, polyline.normal, polyline.elevation)
|
||||
}
|
||||
_ => return None,
|
||||
};
|
||||
make_poly_wipeout(&points)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Autocomplete registry ─────────────────────────────────
|
||||
|
||||
@@ -365,6 +365,8 @@ impl CadCommand for AreaCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -88,6 +88,8 @@ impl CadCommand for DistCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -73,6 +73,8 @@ impl MeasureGeomCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -3724,6 +3724,8 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -69,7 +69,7 @@ impl CadModule for InsertModule {
|
||||
),
|
||||
(
|
||||
"FRAMES2",
|
||||
"Frames & Print",
|
||||
"Frames On, Not Plotted",
|
||||
IconKind::Svg(include_bytes!(
|
||||
"../../../assets/icons/underlay_frames.svg"
|
||||
)),
|
||||
|
||||
@@ -514,6 +514,8 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -57,6 +57,8 @@ impl CadCommand for PlotWindowCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -139,6 +139,8 @@ impl CadCommand for ZoomWindowCommand {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
Vendored
+28
@@ -73,6 +73,8 @@ pub struct LocalWire {
|
||||
/// shader band grows with a scaled insert. `0.0` = a normal wire.
|
||||
pub world_width: f32,
|
||||
pub plinegen: bool,
|
||||
pub plot_visible: bool,
|
||||
pub hide_unselected: bool,
|
||||
/// Set at construction; used to discriminate fill-only GPU batches from
|
||||
/// stroke batches in [`StyleKey`]. Derived from
|
||||
/// `points.is_empty() && !fill_tris.is_empty()`.
|
||||
@@ -729,6 +731,8 @@ fn tessellate_sub_local(
|
||||
return vec![];
|
||||
}
|
||||
|
||||
let frame_mode = crate::scene::frame::entity_kind(sub)
|
||||
.map(|kind| crate::scene::frame::mode(doc, kind));
|
||||
let mut result = Vec::with_capacity(wires_out.len());
|
||||
for wire in wires_out {
|
||||
// Per-wire point-count cap: a single wire that exceeds this is skipped
|
||||
@@ -804,6 +808,8 @@ fn tessellate_sub_local(
|
||||
line_weight_px: lw_px,
|
||||
world_width: wire.world_width,
|
||||
plinegen: wire.plinegen,
|
||||
plot_visible: frame_mode.is_none_or(|mode| mode == 1),
|
||||
hide_unselected: frame_mode == Some(0),
|
||||
is_fill_only,
|
||||
color_is_byblock: color_is_byblock && wire_on_base_color,
|
||||
lt_is_byblock,
|
||||
@@ -1362,6 +1368,8 @@ struct StyleKey {
|
||||
/// switchable after block geometry is merged by style.
|
||||
fill_is_2d_solid: bool,
|
||||
fill_is_3d: bool,
|
||||
plot_visible: bool,
|
||||
hide_unselected: bool,
|
||||
/// Bit-cast composed block-local depth for band wires (`0` = no override).
|
||||
/// Keeps bands of different in-block draw ranks in separate batches so
|
||||
/// each finalized WireModel carries one correct `depth_override`.
|
||||
@@ -1396,6 +1404,8 @@ struct BatchEntry {
|
||||
fill_tris_low: Vec<[f32; 3]>,
|
||||
fill_is_3d: bool,
|
||||
fill_is_2d_solid: bool,
|
||||
plot_visible: bool,
|
||||
hide_unselected: bool,
|
||||
/// Accumulated thickness-wall pick geometry, paired high/low like
|
||||
/// `fill_tris`. Pick-only — no GPU batch reads this.
|
||||
pick_tris: Vec<[f32; 3]>,
|
||||
@@ -1460,6 +1470,8 @@ impl BatchEntry {
|
||||
_is_fill_only: bool,
|
||||
fill_is_2d_solid: bool,
|
||||
fill_is_3d: bool,
|
||||
plot_visible: bool,
|
||||
hide_unselected: bool,
|
||||
) -> Self {
|
||||
// `is_fill_only` is part of the StyleKey hash so greek fills never
|
||||
// share a batch with regular wires (otherwise the finalized
|
||||
@@ -1479,6 +1491,8 @@ impl BatchEntry {
|
||||
plinegen,
|
||||
fill_is_2d_solid,
|
||||
fill_is_3d,
|
||||
plot_visible,
|
||||
hide_unselected,
|
||||
min_x: f32::INFINITY,
|
||||
min_y: f32::INFINITY,
|
||||
max_x: f32::NEG_INFINITY,
|
||||
@@ -1496,6 +1510,10 @@ impl Batches {
|
||||
.into_iter()
|
||||
.chain(self.by_style.into_values())
|
||||
.map(|mut b| {
|
||||
if b.hide_unselected && !selected {
|
||||
b.points.clear();
|
||||
b.points_low.clear();
|
||||
}
|
||||
let aabb = if b.min_x.is_infinite() {
|
||||
WireModel::UNBOUNDED_AABB
|
||||
} else {
|
||||
@@ -1521,6 +1539,8 @@ impl Batches {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: b.world_width,
|
||||
depth_override: b.local_depth,
|
||||
display_visible: !b.hide_unselected || selected,
|
||||
plot_visible: b.plot_visible,
|
||||
fill_is_3d: b.fill_is_3d,
|
||||
fill_is_2d_solid: b.fill_is_2d_solid,
|
||||
render_instance: None,
|
||||
@@ -1573,6 +1593,8 @@ fn style_key(
|
||||
is_fill_only: bool,
|
||||
fill_is_2d_solid: bool,
|
||||
fill_is_3d: bool,
|
||||
plot_visible: bool,
|
||||
hide_unselected: bool,
|
||||
local_depth: Option<f32>,
|
||||
) -> StyleKey {
|
||||
StyleKey {
|
||||
@@ -1603,6 +1625,8 @@ fn style_key(
|
||||
is_fill_only,
|
||||
fill_is_2d_solid,
|
||||
fill_is_3d,
|
||||
plot_visible,
|
||||
hide_unselected,
|
||||
depth_bits: local_depth.map_or(0, f32::to_bits),
|
||||
}
|
||||
}
|
||||
@@ -2014,6 +2038,8 @@ fn emit_wire(
|
||||
lw.is_fill_only,
|
||||
lw.fill_is_2d_solid,
|
||||
lw.fill_is_3d,
|
||||
lw.plot_visible,
|
||||
lw.hide_unselected,
|
||||
local_depth,
|
||||
);
|
||||
|
||||
@@ -2041,6 +2067,8 @@ fn emit_wire(
|
||||
lw.is_fill_only,
|
||||
lw.fill_is_2d_solid,
|
||||
lw.fill_is_3d,
|
||||
lw.plot_visible,
|
||||
lw.hide_unselected,
|
||||
)
|
||||
});
|
||||
entry.local_depth = local_depth;
|
||||
|
||||
@@ -711,6 +711,8 @@ pub(crate) fn tessellate_entity(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1063,6 +1065,8 @@ pub(crate) fn tessellate_entity(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1148,14 +1152,21 @@ pub(crate) fn tessellate_entity(
|
||||
wire.render_instance = Some(instance);
|
||||
}
|
||||
}
|
||||
if document.header.xclip_frame != 0 && polygon.len() >= 3 {
|
||||
wires.push(pick::xclip::frame_wire(
|
||||
let frame_mode = crate::scene::frame::mode(
|
||||
document,
|
||||
crate::scene::frame::FrameKind::Xclip,
|
||||
);
|
||||
if polygon.len() >= 3 {
|
||||
let mut frame = pick::xclip::frame_wire(
|
||||
&polygon,
|
||||
format!("{}_xclipframe", h.value()),
|
||||
h.value().to_string(),
|
||||
ins_color,
|
||||
sel,
|
||||
ins_lw_px,
|
||||
));
|
||||
);
|
||||
frame.display_visible = frame_mode != 0;
|
||||
frame.plot_visible = frame_mode == 1;
|
||||
wires.push(frame);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1200,6 +1211,8 @@ pub(crate) fn tessellate_entity(
|
||||
bg_color,
|
||||
false,
|
||||
);
|
||||
let frame_mode = crate::scene::frame::entity_kind(e)
|
||||
.map(|kind| crate::scene::frame::mode(document, kind));
|
||||
for b in &mut bases {
|
||||
b.aci = aci;
|
||||
// SDF text wires carry a glyph-bounds AABB (the true text extent) set
|
||||
@@ -1208,8 +1221,18 @@ pub(crate) fn tessellate_entity(
|
||||
if b.text_verts.is_empty() {
|
||||
set_wire_aabb(b, aabb);
|
||||
}
|
||||
if let Some(mode) = frame_mode {
|
||||
b.display_visible = mode != 0;
|
||||
b.plot_visible = mode == 1;
|
||||
}
|
||||
if matches!(e, EntityType::Wipeout(_)) {
|
||||
b.depth_override = Some(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
// A hidden mask frame remains selectable and appears while selected, but
|
||||
// contributes no visible line work during normal display. The interior
|
||||
// pick triangles remain intact.
|
||||
// Complex linetypes (with embedded shapes / text) expand the *base*
|
||||
// polyline along its tangent. Text-type entities never have a complex
|
||||
// linetype assigned, so we only consult the first wire here — multi-wire
|
||||
@@ -1437,6 +1460,8 @@ fn lod_stub_wire(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1528,6 +1553,8 @@ fn lod_stub_wire_3d(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -127,6 +127,75 @@ pub(crate) fn points_to_ds(
|
||||
(high, low)
|
||||
}
|
||||
|
||||
fn point_cloud_wires(
|
||||
document: &CadDocument,
|
||||
handle: Handle,
|
||||
entity: &EntityType,
|
||||
selected: bool,
|
||||
color: [f32; 4],
|
||||
line_weight_px: f32,
|
||||
) -> Option<Vec<WireModel>> {
|
||||
let EntityType::Extended(extended) = entity else {
|
||||
return None;
|
||||
};
|
||||
let frame_points = crate::entities::extended::point_cloud_frame_lines(extended)?;
|
||||
let rendered = convert(entity, document)?;
|
||||
let RenderObject::Lines(body_points) = rendered.object else {
|
||||
return None;
|
||||
};
|
||||
let (points, points_low) = points_to_ds(body_points);
|
||||
let mut wires = vec![WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
pick_tris: Vec::new(),
|
||||
pick_tris_low: Vec::new(),
|
||||
dash_from_start: false,
|
||||
dash_align_end: None,
|
||||
text_verts: Vec::new(),
|
||||
name: handle.value().to_string(),
|
||||
points,
|
||||
points_low,
|
||||
color,
|
||||
selected,
|
||||
pattern_length: 0.0,
|
||||
pattern: [0.0; 8],
|
||||
line_weight_px,
|
||||
snap_pts: rendered.snap_pts,
|
||||
tangent_geoms: rendered.tangent_geoms,
|
||||
aci: 0,
|
||||
key_vertices: rendered.key_vertices,
|
||||
aabb: WireModel::UNBOUNDED_AABB,
|
||||
plinegen: true,
|
||||
fill_tris: Vec::new(),
|
||||
fill_tris_low: Vec::new(),
|
||||
}];
|
||||
let mode = crate::scene::frame::mode(
|
||||
document,
|
||||
crate::scene::frame::FrameKind::PointCloudClip,
|
||||
);
|
||||
if !frame_points.is_empty() {
|
||||
let (points, points_low) = points_to_ds(frame_points);
|
||||
let mut frame = WireModel::solid(
|
||||
handle.value().to_string(),
|
||||
points,
|
||||
color,
|
||||
selected,
|
||||
);
|
||||
frame.points_low = points_low;
|
||||
frame.line_weight_px = line_weight_px;
|
||||
frame.display_visible = mode != 0;
|
||||
frame.plot_visible = mode == 1;
|
||||
wires.push(frame);
|
||||
}
|
||||
Some(wires)
|
||||
}
|
||||
|
||||
/// Lift a WireModel built in a local frame (a fixed f64 origin subtracted) back
|
||||
/// to absolute world coordinates, re-splitting every position — polyline points
|
||||
/// and SDF glyph vertices — into double-single so it stays precise at UTM scale.
|
||||
@@ -434,6 +503,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -482,6 +553,17 @@ pub fn tessellate(
|
||||
return out;
|
||||
}
|
||||
|
||||
if let Some(wires) = point_cloud_wires(
|
||||
document,
|
||||
handle,
|
||||
entity,
|
||||
selected,
|
||||
color,
|
||||
line_weight_px,
|
||||
) {
|
||||
return wires;
|
||||
}
|
||||
|
||||
// ── Try the kernel path first ───────────────────────────────────────────
|
||||
// Relative-PDSIZE points size their glyph from the current zoom so they
|
||||
// stay a roughly constant on-screen size; otherwise the header-driven path.
|
||||
@@ -728,6 +810,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -775,6 +859,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -824,6 +910,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -854,6 +942,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -914,6 +1004,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -956,6 +1048,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -995,6 +1089,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1027,6 +1123,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1093,6 +1191,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1214,6 +1314,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: polyline_band_width(entity),
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1279,6 +1381,8 @@ pub fn tessellate(
|
||||
fill_is_2d_solid: matches!(entity, EntityType::Solid(_)),
|
||||
render_instance: None,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1287,6 +1391,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1332,6 +1438,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: polyline_band_width(entity),
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1378,6 +1486,8 @@ pub fn tessellate(
|
||||
taper_widths: widths,
|
||||
world_width,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -1497,6 +1607,8 @@ pub fn tessellate(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
+125
-57
@@ -1393,6 +1393,9 @@ impl Scene {
|
||||
wipeout.v_vector =
|
||||
context.transform.apply_rotation(source.v_vector);
|
||||
}
|
||||
let Some(mut fill_plane) = Self::wipeout_fill_plane(&wipeout) else {
|
||||
return;
|
||||
};
|
||||
let (world_origin, mut boundary) =
|
||||
Self::wipeout_boundary_2d(&wipeout);
|
||||
for clip in &context.clips {
|
||||
@@ -1409,6 +1412,16 @@ impl Scene {
|
||||
if boundary.len() < 3 {
|
||||
return;
|
||||
}
|
||||
if !context.clips.is_empty() {
|
||||
let Some(local) = Self::wipeout_boundary_at_xy(
|
||||
fill_plane.0,
|
||||
world_origin,
|
||||
&boundary,
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
fill_plane.1 = local;
|
||||
}
|
||||
let selection_handle =
|
||||
if tint_insert_selection && context.is_instanced() {
|
||||
context.root_handle
|
||||
@@ -1446,10 +1459,14 @@ impl Scene {
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let fill_plane_boundary = Some(Arc::new(fill_plane.1));
|
||||
let fill_plane = Some(fill_plane.0);
|
||||
models.push(HatchModel {
|
||||
render_instance,
|
||||
boundary: Arc::new(boundary),
|
||||
boundary_wcs: None,
|
||||
fill_plane,
|
||||
fill_plane_boundary,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: model::hatch_model::HatchPattern::Solid,
|
||||
@@ -1474,66 +1491,113 @@ impl Scene {
|
||||
pub(super) fn wipeout_boundary_2d(
|
||||
wo: &acadrust::entities::Wipeout,
|
||||
) -> ([f64; 2], Vec<[f32; 2]>) {
|
||||
use acadrust::entities::WipeoutClipType;
|
||||
|
||||
let origin = [wo.insertion_point.x, wo.insertion_point.y];
|
||||
|
||||
let is_polygon = wo.clipping_enabled
|
||||
&& wo.clip_boundary_vertices.len() >= 3
|
||||
&& matches!(wo.clip_type, WipeoutClipType::Polygonal);
|
||||
|
||||
if is_polygon {
|
||||
// DXF clip vertices live in image-pixel space, centred on the
|
||||
// image (range −size/2 … +size/2). Image-bottom-left → insertion,
|
||||
// image-y-axis points DOWN (per the DXF "v_vector points down the
|
||||
// image" convention), so map:
|
||||
// x_off = (clip.x + size.x/2) × u_vec
|
||||
// y_off = (size.y/2 − clip.y) × v_vec ← y flipped
|
||||
// Offsets are relative to `origin` (the insertion point).
|
||||
let cx_of = |v: &acadrust::types::Vector2| v.x + wo.size.x * 0.5;
|
||||
let cy_of = |v: &acadrust::types::Vector2| wo.size.y * 0.5 - v.y;
|
||||
let mut poly: Vec<[f32; 2]> = wo
|
||||
.clip_boundary_vertices
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let cx = cx_of(v);
|
||||
let cy = cy_of(v);
|
||||
let wx = (wo.u_vector.x * cx + wo.v_vector.x * cy) as f32;
|
||||
let wy = (wo.u_vector.y * cx + wo.v_vector.y * cy) as f32;
|
||||
[wx, wy]
|
||||
})
|
||||
.collect();
|
||||
// Close the loop: the GPU `in_polygon` ray-cast walks
|
||||
// sequential pairs and doesn't wrap, so without an explicit
|
||||
// closing vertex the last edge (vN-1 → v0) is never tested and
|
||||
// the fill bleeds far past the boundary.
|
||||
if let Some(&first) = poly.first() {
|
||||
if poly.last() != Some(&first) {
|
||||
poly.push(first);
|
||||
let plane = cadkernel::space::Plane::from_axes(
|
||||
[wo.insertion_point.x, wo.insertion_point.y, wo.insertion_point.z],
|
||||
[wo.u_vector.x, wo.u_vector.y, wo.u_vector.z],
|
||||
[wo.v_vector.x, wo.v_vector.y, wo.v_vector.z],
|
||||
);
|
||||
let boundary = Self::wipeout_local_boundary(wo)
|
||||
.into_iter()
|
||||
.map(|point| {
|
||||
if point[0].is_finite() && point[1].is_finite() {
|
||||
let offset = plane.vector_at(point);
|
||||
[offset[0] as f32, offset[1] as f32]
|
||||
} else {
|
||||
[f32::NAN; 2]
|
||||
}
|
||||
}
|
||||
(origin, poly)
|
||||
} else {
|
||||
// Rectangular boundary from 4 corners, as offsets from `origin`.
|
||||
let ux = (wo.u_vector.x * wo.size.x) as f32;
|
||||
let uy = (wo.u_vector.y * wo.size.x) as f32;
|
||||
let vx = (wo.v_vector.x * wo.size.y) as f32;
|
||||
let vy = (wo.v_vector.y * wo.size.y) as f32;
|
||||
// Close the loop (repeat corner 0): the GPU `in_polygon` ray-cast
|
||||
// walks sequential vertex pairs and never wraps last→first, so an
|
||||
// unclosed quad leaves the v3→v0 edge untested and the solid mask
|
||||
// bleeds past the boundary — same reason the polygon branch closes.
|
||||
(
|
||||
origin,
|
||||
vec![
|
||||
[0.0, 0.0],
|
||||
[ux, uy],
|
||||
[ux + vx, uy + vy],
|
||||
[vx, vy],
|
||||
[0.0, 0.0],
|
||||
],
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
(origin, boundary)
|
||||
}
|
||||
|
||||
fn wipeout_local_boundary(wo: &acadrust::entities::Wipeout) -> Vec<[f64; 2]> {
|
||||
use acadrust::entities::{WipeoutClipMode, WipeoutClipType};
|
||||
let mut outer = vec![
|
||||
[0.0, 0.0],
|
||||
[wo.size.x, 0.0],
|
||||
[wo.size.x, wo.size.y],
|
||||
[0.0, wo.size.y],
|
||||
[0.0, 0.0],
|
||||
];
|
||||
if !wo.clipping_enabled
|
||||
|| wo.clip_boundary_vertices.len() < 3
|
||||
|| !matches!(wo.clip_type, WipeoutClipType::Polygonal)
|
||||
{
|
||||
return outer;
|
||||
}
|
||||
let mut clip: Vec<[f64; 2]> = wo
|
||||
.clip_boundary_vertices
|
||||
.iter()
|
||||
.map(|point| {
|
||||
[
|
||||
point.x + wo.size.x * 0.5,
|
||||
wo.size.y * 0.5 - point.y,
|
||||
]
|
||||
})
|
||||
.collect();
|
||||
if clip.last() != clip.first() {
|
||||
clip.push(clip[0]);
|
||||
}
|
||||
if matches!(wo.clip_mode, WipeoutClipMode::Inside) {
|
||||
outer.push([f64::NAN; 2]);
|
||||
outer.extend(clip);
|
||||
outer
|
||||
} else {
|
||||
clip
|
||||
}
|
||||
}
|
||||
|
||||
fn wipeout_fill_plane(
|
||||
wipeout: &acadrust::entities::Wipeout,
|
||||
) -> Option<(model::hatch_model::FillPlane, Vec<[f32; 2]>)> {
|
||||
let origin = [
|
||||
wipeout.insertion_point.x,
|
||||
wipeout.insertion_point.y,
|
||||
wipeout.insertion_point.z,
|
||||
];
|
||||
let x_axis = [wipeout.u_vector.x, wipeout.u_vector.y, wipeout.u_vector.z];
|
||||
let y_axis = [wipeout.v_vector.x, wipeout.v_vector.y, wipeout.v_vector.z];
|
||||
cadkernel::space::Plane::from_axes(origin, x_axis, y_axis).normal()?;
|
||||
let boundary = Self::wipeout_local_boundary(wipeout)
|
||||
.into_iter()
|
||||
.map(|point| [point[0] as f32, point[1] as f32])
|
||||
.collect();
|
||||
Some((
|
||||
model::hatch_model::FillPlane {
|
||||
origin,
|
||||
x_axis,
|
||||
y_axis,
|
||||
},
|
||||
boundary,
|
||||
))
|
||||
}
|
||||
|
||||
fn wipeout_boundary_at_xy(
|
||||
plane: model::hatch_model::FillPlane,
|
||||
world_origin: [f64; 2],
|
||||
boundary: &[[f32; 2]],
|
||||
) -> Option<Vec<[f32; 2]>> {
|
||||
let plane = cadkernel::space::Plane::from_axes(
|
||||
plane.origin,
|
||||
plane.x_axis,
|
||||
plane.y_axis,
|
||||
);
|
||||
boundary
|
||||
.iter()
|
||||
.map(|point| {
|
||||
if point[0].is_finite() && point[1].is_finite() {
|
||||
plane
|
||||
.coordinates_at_xy([
|
||||
world_origin[0] + point[0] as f64,
|
||||
world_origin[1] + point[1] as f64,
|
||||
])
|
||||
.map(|point| [point[0] as f32, point[1] as f32])
|
||||
} else {
|
||||
Some([f32::NAN; 2])
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn hatch_model_from_dxf(
|
||||
@@ -1817,6 +1881,8 @@ impl Scene {
|
||||
render_instance: None,
|
||||
boundary: std::sync::Arc::new(boundary_f32),
|
||||
boundary_wcs: None,
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: Some(std::sync::Arc::new(boundary_exterior)),
|
||||
boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
|
||||
pattern,
|
||||
@@ -2133,6 +2199,8 @@ impl Scene {
|
||||
render_instance: None,
|
||||
boundary: std::sync::Arc::new(boundary),
|
||||
boundary_wcs: None,
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: model::hatch_model::HatchPattern::Solid,
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
use acadrust::objects::{ObjectType, RasterVariables, WipeoutVariables};
|
||||
use acadrust::{CadDocument, EntityType, Handle};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub(crate) enum FrameKind {
|
||||
Image,
|
||||
Pdf,
|
||||
Wipeout,
|
||||
Xclip,
|
||||
PointCloudClip,
|
||||
}
|
||||
|
||||
pub(crate) const ALL_KINDS: [FrameKind; 5] = [
|
||||
FrameKind::Image,
|
||||
FrameKind::Pdf,
|
||||
FrameKind::Wipeout,
|
||||
FrameKind::Xclip,
|
||||
FrameKind::PointCloudClip,
|
||||
];
|
||||
|
||||
fn root_entry(document: &CadDocument, name: &str) -> Option<Handle> {
|
||||
let from_dictionary = |handle| match document.objects.get(&handle) {
|
||||
Some(ObjectType::Dictionary(dictionary)) => dictionary
|
||||
.entries
|
||||
.iter()
|
||||
.find(|(key, _)| key.eq_ignore_ascii_case(name))
|
||||
.map(|(_, handle)| *handle),
|
||||
_ => None,
|
||||
};
|
||||
from_dictionary(document.header.named_objects_dict_handle).or_else(|| {
|
||||
document.objects.values().find_map(|object| match object {
|
||||
ObjectType::Dictionary(dictionary) if dictionary.owner.is_null() => {
|
||||
from_dictionary(dictionary.handle)
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn raster_variables_handle(document: &CadDocument) -> Option<Handle> {
|
||||
root_entry(document, "ACAD_IMAGE_VARS")
|
||||
.filter(|handle| matches!(document.objects.get(handle), Some(ObjectType::RasterVariables(_))))
|
||||
}
|
||||
|
||||
fn wipeout_variables_handle(document: &CadDocument) -> Option<Handle> {
|
||||
root_entry(document, "ACAD_WIPEOUT_VARS")
|
||||
.filter(|handle| matches!(document.objects.get(handle), Some(ObjectType::WipeoutVariables(_))))
|
||||
}
|
||||
|
||||
fn drawing_mode(document: &CadDocument, name: &str, default: i16) -> i16 {
|
||||
crate::io::drawing_variable(document, name)
|
||||
.and_then(|value| value.parse::<i16>().ok())
|
||||
.unwrap_or(default)
|
||||
.clamp(0, 2)
|
||||
}
|
||||
|
||||
pub(crate) fn mode(document: &CadDocument, kind: FrameKind) -> i16 {
|
||||
match kind {
|
||||
FrameKind::Image => raster_variables_handle(document)
|
||||
.and_then(|handle| match document.objects.get(&handle) {
|
||||
Some(ObjectType::RasterVariables(value)) => Some(value.display_image_frame),
|
||||
_ => None,
|
||||
})
|
||||
.unwrap_or(1)
|
||||
.clamp(0, 2),
|
||||
FrameKind::Pdf => drawing_mode(document, "PDFFRAME", 1),
|
||||
FrameKind::Wipeout => crate::io::drawing_variable(document, "WIPEOUTFRAME")
|
||||
.and_then(|value| value.parse::<i16>().ok())
|
||||
.or_else(|| {
|
||||
wipeout_variables_handle(document).and_then(|handle| {
|
||||
match document.objects.get(&handle) {
|
||||
Some(ObjectType::WipeoutVariables(value)) => Some(value.display_frame),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or(1)
|
||||
.clamp(0, 2),
|
||||
FrameKind::Xclip => document.header.xclip_frame.clamp(0, 2),
|
||||
FrameKind::PointCloudClip => drawing_mode(document, "POINTCLOUDCLIPFRAME", 2),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn master_mode(document: &CadDocument) -> i16 {
|
||||
let first = mode(document, ALL_KINDS[0]);
|
||||
ALL_KINDS
|
||||
.iter()
|
||||
.skip(1)
|
||||
.all(|kind| mode(document, *kind) == first)
|
||||
.then_some(first)
|
||||
.unwrap_or(3)
|
||||
}
|
||||
|
||||
fn attach_root_entry(document: &mut CadDocument, name: &str, handle: Handle) {
|
||||
let root = crate::scene::annotative::root_named_dict_handle(document);
|
||||
if let Some(ObjectType::Dictionary(dictionary)) = document.objects.get_mut(&root) {
|
||||
dictionary
|
||||
.entries
|
||||
.retain(|(key, _)| !key.eq_ignore_ascii_case(name));
|
||||
dictionary.add_entry(name, handle);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_image_mode(document: &mut CadDocument, value: i16) {
|
||||
let owner = crate::scene::annotative::root_named_dict_handle(document);
|
||||
let handle = raster_variables_handle(document).unwrap_or_else(|| {
|
||||
let handle = document.allocate_handle();
|
||||
let mut variables = RasterVariables::new();
|
||||
variables.handle = handle;
|
||||
variables.owner = owner;
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::RasterVariables(variables));
|
||||
handle
|
||||
});
|
||||
if let Some(ObjectType::RasterVariables(variables)) = document.objects.get_mut(&handle) {
|
||||
variables.owner = owner;
|
||||
variables.display_image_frame = value;
|
||||
}
|
||||
attach_root_entry(document, "ACAD_IMAGE_VARS", handle);
|
||||
}
|
||||
|
||||
fn set_wipeout_mode(document: &mut CadDocument, value: i16) {
|
||||
crate::io::set_drawing_variable(document, "WIPEOUTFRAME", &value.to_string());
|
||||
let owner = crate::scene::annotative::root_named_dict_handle(document);
|
||||
let handle = wipeout_variables_handle(document).unwrap_or_else(|| {
|
||||
let handle = document.allocate_handle();
|
||||
let mut variables = WipeoutVariables::new();
|
||||
variables.handle = handle;
|
||||
variables.owner = owner;
|
||||
document
|
||||
.objects
|
||||
.insert(handle, ObjectType::WipeoutVariables(variables));
|
||||
handle
|
||||
});
|
||||
if let Some(ObjectType::WipeoutVariables(variables)) = document.objects.get_mut(&handle) {
|
||||
variables.owner = owner;
|
||||
variables.display_frame = value;
|
||||
}
|
||||
attach_root_entry(document, "ACAD_WIPEOUT_VARS", handle);
|
||||
}
|
||||
|
||||
pub(crate) fn set_mode(document: &mut CadDocument, kind: FrameKind, value: i16) {
|
||||
let value = value.clamp(0, 2);
|
||||
match kind {
|
||||
FrameKind::Image => set_image_mode(document, value),
|
||||
FrameKind::Pdf => crate::io::set_drawing_variable(document, "PDFFRAME", &value.to_string()),
|
||||
FrameKind::Wipeout => set_wipeout_mode(document, value),
|
||||
FrameKind::Xclip => document.header.xclip_frame = value,
|
||||
FrameKind::PointCloudClip => crate::io::set_drawing_variable(
|
||||
document,
|
||||
"POINTCLOUDCLIPFRAME",
|
||||
&value.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_master_mode(document: &mut CadDocument, value: i16) {
|
||||
let value = value.clamp(0, 2);
|
||||
for kind in ALL_KINDS {
|
||||
if mode(document, kind) != value {
|
||||
set_mode(document, kind, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn kind_for_name(name: &str) -> Option<FrameKind> {
|
||||
match name {
|
||||
"IMAGEFRAME" => Some(FrameKind::Image),
|
||||
"PDFFRAME" => Some(FrameKind::Pdf),
|
||||
"WIPEOUTFRAME" => Some(FrameKind::Wipeout),
|
||||
"XCLIPFRAME" => Some(FrameKind::Xclip),
|
||||
"POINTCLOUDCLIPFRAME" => Some(FrameKind::PointCloudClip),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn entity_kind(entity: &EntityType) -> Option<FrameKind> {
|
||||
match entity {
|
||||
EntityType::RasterImage(_) => Some(FrameKind::Image),
|
||||
EntityType::Underlay(underlay)
|
||||
if matches!(underlay.underlay_type, acadrust::entities::UnderlayType::Pdf) =>
|
||||
{
|
||||
Some(FrameKind::Pdf)
|
||||
}
|
||||
EntityType::Wipeout(_) => Some(FrameKind::Wipeout),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn affected(entity: &EntityType, kind: FrameKind) -> bool {
|
||||
match kind {
|
||||
FrameKind::Image => matches!(entity, EntityType::RasterImage(_)),
|
||||
FrameKind::Pdf => matches!(
|
||||
entity,
|
||||
EntityType::Underlay(underlay)
|
||||
if matches!(underlay.underlay_type, acadrust::entities::UnderlayType::Pdf)
|
||||
),
|
||||
FrameKind::Wipeout => matches!(entity, EntityType::Wipeout(_)),
|
||||
FrameKind::Xclip => matches!(entity, EntityType::Insert(_)),
|
||||
FrameKind::PointCloudClip => matches!(
|
||||
entity,
|
||||
EntityType::Extended(extended)
|
||||
if matches!(
|
||||
&extended.data,
|
||||
acadrust::entities::ExtendedEntityData::PointCloud(_)
|
||||
| acadrust::entities::ExtendedEntityData::PointCloudEx(_)
|
||||
)
|
||||
),
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@ pub mod annotative;
|
||||
pub mod cache;
|
||||
pub mod convert;
|
||||
pub mod creation_style;
|
||||
pub(crate) mod frame;
|
||||
pub mod model;
|
||||
pub mod pick;
|
||||
pub mod pipeline;
|
||||
@@ -3210,6 +3211,8 @@ impl Scene {
|
||||
world_origin: [0.0, 0.0],
|
||||
boundary: Arc::new(vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]]),
|
||||
boundary_wcs: None,
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: crate::scene::model::hatch_model::HatchPattern::Solid,
|
||||
|
||||
@@ -165,6 +165,13 @@ pub enum HatchPattern {
|
||||
/// (anchor-relative), so real coordinates never approach it.
|
||||
pub const GPU_BOUNDARY_SEP: f32 = 1.0e30;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FillPlane {
|
||||
pub origin: [f64; 3],
|
||||
pub x_axis: [f64; 3],
|
||||
pub y_axis: [f64; 3],
|
||||
}
|
||||
|
||||
/// A hatched region defined by a closed polygon boundary.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct HatchModel {
|
||||
@@ -196,6 +203,9 @@ pub struct HatchModel {
|
||||
/// rebuilt from a DXF entity — `add_hatch` then reconstructs the persisted
|
||||
/// vertices from `boundary` + `world_origin` instead.
|
||||
pub boundary_wcs: Option<Arc<Vec<[f64; 2]>>>,
|
||||
/// Optional 3-D placement used by wipeout fills.
|
||||
pub fill_plane: Option<FillPlane>,
|
||||
pub fill_plane_boundary: Option<Arc<Vec<[f32; 2]>>>,
|
||||
/// Per-ring DXF role, aligned with the NaN-separated boundary paths.
|
||||
pub boundary_exterior: Option<Arc<Vec<bool>>>,
|
||||
/// Source entity handles for each boundary ring.
|
||||
|
||||
@@ -146,6 +146,10 @@ pub struct WireModel {
|
||||
/// mirroring how exploded block fills seed their depth, so bands and fills
|
||||
/// from the same block interleave by the block's internal draw order.
|
||||
pub depth_override: Option<f32>,
|
||||
/// Whether this wire is drawn in the normal viewport pass.
|
||||
pub display_visible: bool,
|
||||
/// Whether this wire is included in plotted output.
|
||||
pub plot_visible: bool,
|
||||
/// `true` when [`fill_tris`] is a real 3-D surface (PolyfaceMesh /
|
||||
/// PolygonMesh face) that must render with hidden-surface depth and only in
|
||||
/// shaded modes. `false` for a flat 2-D overlay fill (SOLID arrowhead,
|
||||
@@ -208,6 +212,8 @@ impl WireModel {
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -502,6 +508,8 @@ impl Default for WireModel {
|
||||
fill_tris: Vec::new(),
|
||||
fill_tris_low: Vec::new(),
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -260,6 +260,8 @@ pub fn frame_wire(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -948,10 +948,8 @@ impl Pipeline {
|
||||
);
|
||||
|
||||
// ── Hatch pipeline ─────────────────────────────────────────────────
|
||||
// binding 0 (HatchUniforms) is read by the vertex shader too — it
|
||||
// pulls `origin` to undo the CPU-side hatch-local pre-shift when
|
||||
// computing clip position. bindings 1 (Boundary) and 2
|
||||
// (FamilyBatch) stay fragment-only.
|
||||
// Binding 0 carries the fill plane; boundary and family data are
|
||||
// fragment-only.
|
||||
let hatch_entry = |binding: u32, vis: wgpu::ShaderStages| wgpu::BindGroupLayoutEntry {
|
||||
binding,
|
||||
visibility: vis,
|
||||
@@ -2364,6 +2362,10 @@ impl Pipeline {
|
||||
let mut block_wires: Vec<&WireModel> = Vec::new();
|
||||
let mut i = 0;
|
||||
while i < wires.len() {
|
||||
if !wires[i].display_visible {
|
||||
i += 1;
|
||||
continue;
|
||||
}
|
||||
if wires[i].render_instance.is_some() {
|
||||
block_wires.push(&wires[i]);
|
||||
i += 1;
|
||||
@@ -2372,6 +2374,7 @@ impl Pipeline {
|
||||
let mesh_edge = is_mesh_edge(&wires[i]);
|
||||
let mut j = i + 1;
|
||||
while j < wires.len()
|
||||
&& wires[j].display_visible
|
||||
&& wires[j].render_instance.is_none()
|
||||
&& is_mesh_edge(&wires[j]) == mesh_edge
|
||||
{
|
||||
|
||||
@@ -433,6 +433,9 @@ pub fn upload_block_vertex_refs(
|
||||
let mut slots = rustc_hash::FxHashMap::default();
|
||||
let mut groups: Vec<Vec<&crate::scene::model::wire_model::WireModel>> = Vec::new();
|
||||
for &wire in wires {
|
||||
if tint.is_none() && !wire.display_visible {
|
||||
continue;
|
||||
}
|
||||
let Some(instance) = wire.render_instance else {
|
||||
continue;
|
||||
};
|
||||
|
||||
+107
-115
@@ -9,11 +9,13 @@
|
||||
// a limit on visible hatch fills.
|
||||
//
|
||||
// Group 1 bindings per fill:
|
||||
// binding 0 — HatchUniformData (64 bytes) mode, color, angle_offset, scale, gradient params
|
||||
// binding 0 — HatchUniformData fill and plane parameters
|
||||
// binding 1 — BoundaryData (16384 bytes) boundary polygon vertices
|
||||
// binding 2 — FamilyBatchData (1296 bytes) up to 16 line families + 128 dash values (unused for wipeouts)
|
||||
|
||||
use crate::scene::model::hatch_model::{HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS};
|
||||
use crate::scene::model::hatch_model::{
|
||||
FillPlane, HatchModel, HatchPattern, PatFamily, MAX_HATCH_BOUNDARY_VERTS,
|
||||
};
|
||||
use iced::wgpu;
|
||||
use iced::wgpu::util::DeviceExt;
|
||||
|
||||
@@ -48,10 +50,10 @@ impl HatchVertex {
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
pub struct WipeoutPlacement {
|
||||
pub translation: [f32; 2],
|
||||
pub translation_low: [f32; 2],
|
||||
pub translation: [f32; 3],
|
||||
pub translation_low: [f32; 3],
|
||||
pub draw_depth: f32,
|
||||
pub _pad: [f32; 3],
|
||||
pub _pad: f32,
|
||||
}
|
||||
|
||||
impl WipeoutPlacement {
|
||||
@@ -60,9 +62,9 @@ impl WipeoutPlacement {
|
||||
array_stride: std::mem::size_of::<Self>() as u64,
|
||||
step_mode: wgpu::VertexStepMode::Instance,
|
||||
attributes: &[
|
||||
wgpu::VertexAttribute { offset: 0, shader_location: 1, format: wgpu::VertexFormat::Float32x2 },
|
||||
wgpu::VertexAttribute { offset: 8, shader_location: 2, format: wgpu::VertexFormat::Float32x2 },
|
||||
wgpu::VertexAttribute { offset: 16, shader_location: 3, format: wgpu::VertexFormat::Float32 },
|
||||
wgpu::VertexAttribute { offset: 0, shader_location: 1, format: wgpu::VertexFormat::Float32x3 },
|
||||
wgpu::VertexAttribute { offset: 12, shader_location: 2, format: wgpu::VertexFormat::Float32x3 },
|
||||
wgpu::VertexAttribute { offset: 24, shader_location: 3, format: wgpu::VertexFormat::Float32 },
|
||||
],
|
||||
}
|
||||
}
|
||||
@@ -70,7 +72,7 @@ impl WipeoutPlacement {
|
||||
|
||||
// ── Uniform structs ───────────────────────────────────────────────────────
|
||||
|
||||
/// Per-hatch parameters (binding 0). Must be 96 bytes (16-byte aligned).
|
||||
/// Per-fill parameters (binding 0).
|
||||
///
|
||||
/// `mode` encoding:
|
||||
/// 0 → Pattern (families in FamilyBatchData)
|
||||
@@ -89,18 +91,15 @@ pub struct HatchUniformData {
|
||||
pub grad_sin: f32, // 52: gradient direction sin
|
||||
pub grad_min: f32, // 56: gradient proj_min
|
||||
pub grad_range: f32, // 60: gradient proj_range
|
||||
pub origin: [f32; 2], // 64: hatch-local origin (boundary AABB centre),
|
||||
// // high half of the double-single anchor.
|
||||
// // The pattern fragment shader subtracts this
|
||||
// // from xz before the perp/perp_step quotient
|
||||
// // so f32 doesn't catastrophically cancel when
|
||||
// // world coords are large and pattern spacing
|
||||
// // is small (e.g. UTM drawing + sub-mm hatch).
|
||||
pub origin_low: [f32; 2], // 72: anchor low residual (double-single) so the
|
||||
// // vertex clip position stays precise at UTM scale.
|
||||
pub origin: [f32; 2], // 64: reserved pattern origin
|
||||
pub origin_low: [f32; 2], // 72: reserved low residual
|
||||
pub draw_depth: f32, // 80: signed (-1,1) draw-order bias
|
||||
pub _pad: [f32; 3], // 84..96
|
||||
} // total 96 bytes
|
||||
pub plane_origin_high: [f32; 4],
|
||||
pub plane_origin_low: [f32; 4],
|
||||
pub plane_x: [f32; 4],
|
||||
pub plane_y: [f32; 4],
|
||||
}
|
||||
|
||||
/// Boundary polygon (binding 1). Matches WGSL `Boundary`.
|
||||
#[repr(C)]
|
||||
@@ -194,13 +193,22 @@ impl WipeoutGpu {
|
||||
};
|
||||
|
||||
// ── Bounding box ─────────────────────────────────────────────────
|
||||
let plane = model.fill_plane.unwrap_or(FillPlane {
|
||||
origin: [model.world_origin[0], model.world_origin[1], 0.0],
|
||||
x_axis: [1.0, 0.0, 0.0],
|
||||
y_axis: [0.0, 1.0, 0.0],
|
||||
});
|
||||
let gpu_boundary = model
|
||||
.fill_plane_boundary
|
||||
.as_deref()
|
||||
.unwrap_or(model.boundary.as_ref());
|
||||
let (mut min_x, mut max_x, mut min_y, mut max_y) = (
|
||||
f32::INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
);
|
||||
for &[x, y] in model.boundary.iter() {
|
||||
for &[x, y] in gpu_boundary {
|
||||
if !x.is_finite() || !y.is_finite() {
|
||||
continue;
|
||||
}
|
||||
@@ -218,68 +226,16 @@ impl WipeoutGpu {
|
||||
_ => 5.0,
|
||||
};
|
||||
let diag = ((max_x - min_x).powi(2) + (max_y - min_y).powi(2)).sqrt();
|
||||
let pad = (diag * 0.8 + max_spacing * 2.0 * model.scale).max(1.0);
|
||||
// ── Pattern phase anchor.
|
||||
//
|
||||
// Boundary verts are already stored as small f32 offsets from
|
||||
// `model.world_origin` (an f64 anchor near the AABB centre), so
|
||||
// the in_polygon ray-cast and the pattern math both run at full
|
||||
// f32 precision in the fragment shader. The remaining question
|
||||
// is *which* offset-rel point we hand to the vertex shader as
|
||||
// `h.origin` (so the quad lands at the correct WCS position
|
||||
// after `view_proj * (v.pos + h.origin)`).
|
||||
//
|
||||
// For pattern alignment across adjacent hatches with the same
|
||||
// family, snap `h.origin` to the family's perp/along grid in
|
||||
// f64. Without the snap, each hatch's pattern phase is per-hatch
|
||||
// and stripes don't continue across hatch boundaries (you see
|
||||
// pattern misalignment along a road etc).
|
||||
let mut origin = model.world_origin;
|
||||
if let HatchPattern::Pattern(families) = &model.pattern {
|
||||
if let Some(fam) = families.first() {
|
||||
// Must mirror the shader's pattern math exactly. QCAD PAT
|
||||
// convention (matching build_family_batch): `dy` is the
|
||||
// perpendicular spacing and `dx` is the along-line phase
|
||||
// shift, both in family-local space. Multiplied by
|
||||
// `model.scale` in the fragment shader. Combined angle
|
||||
// (family + pattern_angle) rotates the perp/along axes.
|
||||
let angle = (fam.angle_deg.to_radians() + model.angle_offset) as f64;
|
||||
let cos_a = angle.cos();
|
||||
let sin_a = angle.sin();
|
||||
let scale = model.scale as f64;
|
||||
let perp_step = fam.dy as f64 * scale;
|
||||
let along_step = fam.dx as f64 * scale;
|
||||
let o_perp = -origin[0] * sin_a + origin[1] * cos_a;
|
||||
let o_along = origin[0] * cos_a + origin[1] * sin_a;
|
||||
let snapped_perp = if perp_step.abs() > 1e-12 {
|
||||
(o_perp / perp_step).round() * perp_step
|
||||
} else {
|
||||
o_perp
|
||||
};
|
||||
let snapped_along = if along_step.abs() > 1e-12 {
|
||||
(o_along / along_step).round() * along_step
|
||||
} else {
|
||||
o_along
|
||||
};
|
||||
origin = [
|
||||
-snapped_perp * sin_a + snapped_along * cos_a,
|
||||
snapped_perp * cos_a + snapped_along * sin_a,
|
||||
];
|
||||
}
|
||||
}
|
||||
// Boundary buffer + quad now live in "snap-shifted" hatch-local
|
||||
// coords: subtract any drift between model.world_origin and the
|
||||
// snapped origin so the in_polygon ray-cast in fragment shader
|
||||
// (which reads the boundary verts straight) lines up with v.xz.
|
||||
let drift = [
|
||||
(model.world_origin[0] - origin[0]) as f32,
|
||||
(model.world_origin[1] - origin[1]) as f32,
|
||||
];
|
||||
let pad = if mode == 1 {
|
||||
0.0
|
||||
} else {
|
||||
(diag * 0.8 + max_spacing * 2.0 * model.scale).max(1.0)
|
||||
};
|
||||
let (x0, x1, y0, y1) = (
|
||||
min_x + drift[0] - pad,
|
||||
max_x + drift[0] + pad,
|
||||
min_y + drift[1] - pad,
|
||||
max_y + drift[1] + pad,
|
||||
min_x - pad,
|
||||
max_x + pad,
|
||||
min_y - pad,
|
||||
max_y + pad,
|
||||
);
|
||||
|
||||
let quad = [
|
||||
@@ -316,11 +272,9 @@ impl WipeoutGpu {
|
||||
|
||||
// ── Gradient: projection range (in snapped-local space) ──────────
|
||||
let (grad_min, grad_range) = if mode == 2 {
|
||||
let projs: Vec<f32> = model
|
||||
.boundary
|
||||
.iter()
|
||||
let projs: Vec<f32> = gpu_boundary.iter()
|
||||
.filter(|v| v[0].is_finite() && v[1].is_finite())
|
||||
.map(|&[x, y]| (x + drift[0]) * grad_cos + (y + drift[1]) * grad_sin)
|
||||
.map(|&[x, y]| x * grad_cos + y * grad_sin)
|
||||
.collect();
|
||||
if projs.is_empty() {
|
||||
(0.0, 1.0)
|
||||
@@ -334,7 +288,11 @@ impl WipeoutGpu {
|
||||
};
|
||||
|
||||
// ── HatchUniformData ─────────────────────────────────────────────
|
||||
let n = model.boundary.len().min(MAX_HATCH_BOUNDARY_VERTS);
|
||||
let n = gpu_boundary.len().min(MAX_HATCH_BOUNDARY_VERTS);
|
||||
let plane_origin_high = plane.origin.map(|value| value as f32);
|
||||
let plane_origin_low: [f32; 3] = std::array::from_fn(|index| {
|
||||
(plane.origin[index] - plane_origin_high[index] as f64) as f32
|
||||
});
|
||||
let uniform_data = HatchUniformData {
|
||||
color: model.color,
|
||||
color2,
|
||||
@@ -346,30 +304,46 @@ impl WipeoutGpu {
|
||||
grad_sin,
|
||||
grad_min,
|
||||
grad_range,
|
||||
origin: [origin[0] as f32, origin[1] as f32],
|
||||
origin_low: [
|
||||
(origin[0] - origin[0] as f32 as f64) as f32,
|
||||
(origin[1] - origin[1] as f32 as f64) as f32,
|
||||
],
|
||||
origin: [0.0; 2],
|
||||
origin_low: [0.0; 2],
|
||||
draw_depth: 0.0,
|
||||
_pad: [0.0; 3],
|
||||
plane_origin_high: [
|
||||
plane_origin_high[0],
|
||||
plane_origin_high[1],
|
||||
plane_origin_high[2],
|
||||
0.0,
|
||||
],
|
||||
plane_origin_low: [
|
||||
plane_origin_low[0],
|
||||
plane_origin_low[1],
|
||||
plane_origin_low[2],
|
||||
0.0,
|
||||
],
|
||||
plane_x: [
|
||||
plane.x_axis[0] as f32,
|
||||
plane.x_axis[1] as f32,
|
||||
plane.x_axis[2] as f32,
|
||||
0.0,
|
||||
],
|
||||
plane_y: [
|
||||
plane.y_axis[0] as f32,
|
||||
plane.y_axis[1] as f32,
|
||||
plane.y_axis[2] as f32,
|
||||
0.0,
|
||||
],
|
||||
};
|
||||
|
||||
// ── BoundaryData (in snapped-local space) ────────────────────────
|
||||
let mut boundary_data = BoundaryData {
|
||||
verts: [[0.0; 4]; MAX_HATCH_BOUNDARY_VERTS],
|
||||
};
|
||||
for (i, &[x, y]) in model
|
||||
.boundary
|
||||
.iter()
|
||||
for (i, &[x, y]) in gpu_boundary.iter()
|
||||
.take(MAX_HATCH_BOUNDARY_VERTS)
|
||||
.enumerate()
|
||||
{
|
||||
// Path-break separators go up as the finite GPU sentinel, not
|
||||
// NaN — the shader NaN self-compare is folded to `true` on some
|
||||
// drivers (#386, #416) — and must not be shifted by `drift`.
|
||||
if x.is_finite() && y.is_finite() {
|
||||
boundary_data.verts[i] = [x + drift[0], y + drift[1], 0.0, 0.0];
|
||||
boundary_data.verts[i] = [x, y, 0.0, 0.0];
|
||||
} else {
|
||||
let s = crate::scene::model::hatch_model::GPU_BOUNDARY_SEP;
|
||||
boundary_data.verts[i] = [s, s, 0.0, 0.0];
|
||||
@@ -433,16 +407,15 @@ impl WipeoutGpu {
|
||||
let translation = model
|
||||
.render_instance
|
||||
.map_or([0.0; 3], |instance| instance.translation);
|
||||
let delta = [translation[0] - base[0], translation[1] - base[1]];
|
||||
let high = [delta[0] as f32, delta[1] as f32];
|
||||
let delta = std::array::from_fn(|index| translation[index] - base[index]);
|
||||
let high = delta.map(|value| value as f32);
|
||||
WipeoutPlacement {
|
||||
translation: high,
|
||||
translation_low: [
|
||||
(delta[0] - high[0] as f64) as f32,
|
||||
(delta[1] - high[1] as f64) as f32,
|
||||
],
|
||||
translation_low: std::array::from_fn(|index| {
|
||||
(delta[index] - high[index] as f64) as f32
|
||||
}),
|
||||
draw_depth: model.draw_depth,
|
||||
_pad: [0.0; 3],
|
||||
_pad: 0.0,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
@@ -451,19 +424,38 @@ impl WipeoutGpu {
|
||||
contents: bytemuck::cast_slice(&placements),
|
||||
usage: wgpu::BufferUsages::VERTEX,
|
||||
});
|
||||
let ox = model.world_origin[0] as f32;
|
||||
let oy = model.world_origin[1] as f32;
|
||||
let world_aabb = if min_x.is_finite() && min_y.is_finite() {
|
||||
let mut world_bounds = [
|
||||
f32::INFINITY,
|
||||
f32::INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
f32::NEG_INFINITY,
|
||||
];
|
||||
for &[x, y] in model.boundary.iter() {
|
||||
if x.is_finite() && y.is_finite() {
|
||||
world_bounds[0] = world_bounds[0].min(x);
|
||||
world_bounds[1] = world_bounds[1].min(y);
|
||||
world_bounds[2] = world_bounds[2].max(x);
|
||||
world_bounds[3] = world_bounds[3].max(y);
|
||||
}
|
||||
}
|
||||
let tilted = plane.origin[2].abs() > f64::EPSILON
|
||||
|| plane.x_axis[2].abs() > f64::EPSILON
|
||||
|| plane.y_axis[2].abs() > f64::EPSILON;
|
||||
let world_aabb = if tilted {
|
||||
[f32::NAN; 4]
|
||||
} else if world_bounds[0].is_finite() && world_bounds[1].is_finite() {
|
||||
let ox = model.world_origin[0] as f32;
|
||||
let oy = model.world_origin[1] as f32;
|
||||
let mut aabb = [f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY];
|
||||
for placement in &placements {
|
||||
aabb[0] = aabb[0].min(min_x + ox + placement.translation[0]);
|
||||
aabb[1] = aabb[1].min(min_y + oy + placement.translation[1]);
|
||||
aabb[2] = aabb[2].max(max_x + ox + placement.translation[0]);
|
||||
aabb[3] = aabb[3].max(max_y + oy + placement.translation[1]);
|
||||
aabb[0] = aabb[0].min(world_bounds[0] + ox + placement.translation[0]);
|
||||
aabb[1] = aabb[1].min(world_bounds[1] + oy + placement.translation[1]);
|
||||
aabb[2] = aabb[2].max(world_bounds[2] + ox + placement.translation[0]);
|
||||
aabb[3] = aabb[3].max(world_bounds[3] + oy + placement.translation[1]);
|
||||
}
|
||||
aabb
|
||||
} else {
|
||||
[min_x, min_y, max_x, max_y]
|
||||
world_bounds
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -482,7 +474,7 @@ impl WipeoutGpu {
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
/// Perpendicular spacing between adjacent parallel lines.
|
||||
/// QCAD PAT format stores (dx, dy) in line-local coordinates: dy = perpendicular, dx = along-line.
|
||||
/// Pattern spacing perpendicular to adjacent parallel lines.
|
||||
fn perp_spacing(f: &PatFamily) -> f32 {
|
||||
f.dy
|
||||
}
|
||||
@@ -519,7 +511,7 @@ fn build_family_batch(pattern: &HatchPattern) -> FamilyBatchData {
|
||||
let angle_r = family.angle_deg.to_radians();
|
||||
let cos_a = angle_r.cos();
|
||||
let sin_a = angle_r.sin();
|
||||
// QCAD PAT local-frame convention: dy = perpendicular spacing, dx = along-line phase shift.
|
||||
// Pattern offsets are stored in the line-local frame.
|
||||
let perp_step = family.dy;
|
||||
let along_step = family.dx;
|
||||
let line_width = 0.0_f32; // unused: shader uses screen-space derivative for 1px lines
|
||||
|
||||
@@ -655,6 +655,9 @@ impl BlockWireGpu {
|
||||
rustc_hash::FxHashMap::default();
|
||||
let mut groups: Vec<(bool, Vec<&WireModel>)> = Vec::new();
|
||||
for &wire in wires {
|
||||
if color_override.is_none() && !wire.display_visible {
|
||||
continue;
|
||||
}
|
||||
let Some(instance) = wire.render_instance else {
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -158,6 +158,8 @@ impl Scene {
|
||||
world_origin: origin,
|
||||
boundary: std::sync::Arc::new(boundary),
|
||||
boundary_wcs: None,
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: HatchPattern::Solid,
|
||||
|
||||
+36
-13
@@ -409,8 +409,8 @@ impl Scene {
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
let project = |x: f64, y: f64| -> Option<[f32; 2]> {
|
||||
let delta = glam::DVec3::new(x, y, 0.0) - camera.target;
|
||||
let project_3d = |point: [f64; 3]| -> Option<[f32; 2]> {
|
||||
let delta = glam::DVec3::from(point) - camera.target;
|
||||
let u = delta.dot(view_right.as_dvec3());
|
||||
let v = delta.dot(view_up.as_dvec3());
|
||||
let factor = if perspective {
|
||||
@@ -427,6 +427,7 @@ impl Scene {
|
||||
(center_y + v * factor * viewport_scale) as f32,
|
||||
])
|
||||
};
|
||||
let project = |x: f64, y: f64| project_3d([x, y, 0.0]);
|
||||
|
||||
let frozen: rustc_hash::FxHashSet<Handle> =
|
||||
viewport.frozen_layers.iter().copied().collect();
|
||||
@@ -481,7 +482,7 @@ impl Scene {
|
||||
continue;
|
||||
}
|
||||
if let Some(hatch) =
|
||||
project_plot_fill(hatch, &project, xmin, ymin, xmax, ymax)
|
||||
project_plot_fill(hatch, &project_3d, xmin, ymin, xmax, ymax)
|
||||
{
|
||||
projected_hatches.push(hatch);
|
||||
}
|
||||
@@ -495,7 +496,7 @@ impl Scene {
|
||||
false,
|
||||
) {
|
||||
if let Some(wipeout) =
|
||||
project_plot_fill(wipeout, &project, xmin, ymin, xmax, ymax)
|
||||
project_plot_fill(wipeout, &project_3d, xmin, ymin, xmax, ymax)
|
||||
{
|
||||
projected_wipeouts.push(wipeout);
|
||||
}
|
||||
@@ -753,7 +754,7 @@ fn project_plot_fill<F>(
|
||||
ymax: f32,
|
||||
) -> Option<HatchModel>
|
||||
where
|
||||
F: Fn(f64, f64) -> Option<[f32; 2]>,
|
||||
F: Fn([f64; 3]) -> Option<[f32; 2]>,
|
||||
{
|
||||
let mut output = Vec::new();
|
||||
let mut ring = Vec::new();
|
||||
@@ -778,15 +779,35 @@ where
|
||||
}
|
||||
output.extend(clipped);
|
||||
};
|
||||
for &[x, y] in fill.boundary.iter() {
|
||||
if x.is_nan() || y.is_nan() {
|
||||
flush_ring(&mut ring, &mut output);
|
||||
continue;
|
||||
if let (Some(plane), Some(boundary)) = (fill.fill_plane, fill.fill_plane_boundary.as_deref()) {
|
||||
let plane = cadkernel::space::Plane::from_axes(
|
||||
plane.origin,
|
||||
plane.x_axis,
|
||||
plane.y_axis,
|
||||
);
|
||||
for &[x, y] in boundary {
|
||||
if x.is_nan() || y.is_nan() {
|
||||
flush_ring(&mut ring, &mut output);
|
||||
continue;
|
||||
}
|
||||
let point = plane.point_at([x as f64, y as f64]);
|
||||
if let Some(point) = project(point) {
|
||||
ring.push(point);
|
||||
}
|
||||
}
|
||||
let absolute_x = fill.world_origin[0] + x as f64;
|
||||
let absolute_y = fill.world_origin[1] + y as f64;
|
||||
if let Some(point) = project(absolute_x, absolute_y) {
|
||||
ring.push(point);
|
||||
} else {
|
||||
for &[x, y] in fill.boundary.iter() {
|
||||
if x.is_nan() || y.is_nan() {
|
||||
flush_ring(&mut ring, &mut output);
|
||||
continue;
|
||||
}
|
||||
if let Some(point) = project([
|
||||
fill.world_origin[0] + x as f64,
|
||||
fill.world_origin[1] + y as f64,
|
||||
0.0,
|
||||
]) {
|
||||
ring.push(point);
|
||||
}
|
||||
}
|
||||
}
|
||||
flush_ring(&mut ring, &mut output);
|
||||
@@ -796,6 +817,8 @@ where
|
||||
fill.world_origin = [0.0, 0.0];
|
||||
fill.boundary = std::sync::Arc::new(output);
|
||||
fill.boundary_wcs = None;
|
||||
fill.fill_plane = None;
|
||||
fill.fill_plane_boundary = None;
|
||||
Some(fill)
|
||||
}
|
||||
|
||||
|
||||
@@ -312,6 +312,8 @@ pub fn apply_along(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
@@ -356,6 +358,8 @@ pub fn apply_along(
|
||||
taper_widths: Vec::new(),
|
||||
world_width: 0.0,
|
||||
depth_override: None,
|
||||
display_visible: true,
|
||||
plot_visible: true,
|
||||
fill_is_3d: false,
|
||||
fill_is_2d_solid: false,
|
||||
render_instance: None,
|
||||
|
||||
@@ -2925,6 +2925,9 @@ impl Scene {
|
||||
}
|
||||
let mut out: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
|
||||
for w in wires {
|
||||
if !w.display_visible {
|
||||
continue;
|
||||
}
|
||||
if w.render_instance.is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
+12
-30
@@ -46,15 +46,15 @@ struct HatchUniforms {
|
||||
grad_sin: f32, // 52: gradient direction sin
|
||||
grad_min: f32, // 56: gradient proj_min
|
||||
grad_range: f32, // 60: gradient proj_range
|
||||
origin: vec2<f32>, // 64: hatch-local pattern origin (boundary
|
||||
// // centre). Pattern math runs against
|
||||
// // `xz - origin` so the f32 modulo doesn't
|
||||
// // catastrophically cancel when world coords
|
||||
// // are large and pattern spacing is small.
|
||||
origin_low: vec2<f32>, // 72: anchor low residual (double-single)
|
||||
origin: vec2<f32>, // 64: reserved pattern origin
|
||||
origin_low: vec2<f32>, // 72: reserved low residual
|
||||
draw_depth: f32, // 80: signed (-1,1) draw-order bias; 0 = neutral
|
||||
_pad0: f32, // 84
|
||||
_pad1: vec2<f32>, // 88
|
||||
plane_origin_high: vec4<f32>,
|
||||
plane_origin_low: vec4<f32>,
|
||||
plane_x: vec4<f32>,
|
||||
plane_y: vec4<f32>,
|
||||
}
|
||||
@group(1) @binding(0) var<uniform> h: HatchUniforms;
|
||||
|
||||
@@ -91,8 +91,8 @@ struct FamilyBatch {
|
||||
|
||||
struct VIn {
|
||||
@location(0) pos: vec3<f32>,
|
||||
@location(1) translation: vec2<f32>,
|
||||
@location(2) translation_low: vec2<f32>,
|
||||
@location(1) translation: vec3<f32>,
|
||||
@location(2) translation_low: vec3<f32>,
|
||||
@location(3) draw_depth: f32,
|
||||
}
|
||||
struct VOut {
|
||||
@@ -102,22 +102,9 @@ struct VOut {
|
||||
|
||||
@vertex fn vs_main(v: VIn) -> VOut {
|
||||
var o: VOut;
|
||||
// v.pos comes in pre-shifted to hatch-local space (CPU subtracted
|
||||
// h.origin from the quad). Add origin back inside the view_proj
|
||||
// multiply to land at the correct world-space clip position. The
|
||||
// f32+f32 addition can lose sub-mm precision at very far hatches
|
||||
// but the result feeds view_proj for screen NDC where sub-pixel
|
||||
// jitter is invisible. The local-space `xz` we export to the
|
||||
// fragment shader stays at full f32 precision so the in_polygon /
|
||||
// pattern math doesn't catastrophically cancel.
|
||||
// Double-single relative-to-eye: anchor high cancels eye_high (Sterbenz);
|
||||
// boundary-local v.pos + anchor low + (−eye_low) carry the residual.
|
||||
let hi = vec3<f32>(h.origin.x + v.translation.x - u.eye_high.x,
|
||||
h.origin.y + v.translation.y - u.eye_high.y,
|
||||
-u.eye_high.z);
|
||||
let lo = vec3<f32>(v.pos.x + h.origin_low.x + v.translation_low.x - u.eye_low.x,
|
||||
v.pos.y + h.origin_low.y + v.translation_low.y - u.eye_low.y,
|
||||
v.pos.z - u.eye_low.z);
|
||||
let local_world = h.plane_x.xyz * v.pos.x + h.plane_y.xyz * v.pos.y;
|
||||
let hi = h.plane_origin_high.xyz + v.translation - u.eye_high;
|
||||
let lo = local_world + h.plane_origin_low.xyz + v.translation_low - u.eye_low;
|
||||
o.clip = u.view_rot * vec4<f32>(hi + lo, 1.0);
|
||||
// Draw-order bias (see wire.wgsl): the mask erases what draws BELOW its
|
||||
// entity and loses to what draws above — including its own frame wire,
|
||||
@@ -303,12 +290,7 @@ fn check_family(
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Pattern: v.xz is already in hatch-local space (CPU pre-shifted
|
||||
// quad + BoundaryData by h.origin). The PAT family origin
|
||||
// (typically (0,0)) is treated as relative to h.origin, so
|
||||
// pattern phase is per-hatch rather than world-aligned — that
|
||||
// trade-off is the price of f32 precision at large drawing
|
||||
// extents.
|
||||
// 4. Pattern coordinates are fill-local.
|
||||
let cos_off = cos(h.angle_offset);
|
||||
let sin_off = sin(h.angle_offset);
|
||||
for (var i = 0u; i < f.n_families; i++) {
|
||||
|
||||
@@ -130,6 +130,8 @@ impl canvas::Program<Message> for HatchPatternPreview {
|
||||
[pad, bounds.height - pad],
|
||||
]),
|
||||
boundary_wcs: None,
|
||||
fill_plane: None,
|
||||
fill_plane_boundary: None,
|
||||
boundary_exterior: None,
|
||||
boundary_sources: None,
|
||||
pattern: self.pattern.clone(),
|
||||
|
||||
Reference in new issue
Block a user