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:
Hakan Seven 2026-08-20 23:37:02 +03:00
commit 44efb8fd53
88 changed files with 1529 additions and 693 deletions

4
Cargo.lock generated
View file

@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
[[package]] [[package]]
name = "acadrust" name = "acadrust"
version = "0.4.1" 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 = [ dependencies = [
"ahash 0.8.12", "ahash 0.8.12",
"anyhow", "anyhow",
@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
[[package]] [[package]]
name = "cadkernel" name = "cadkernel"
version = "0.1.0" 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 = [ dependencies = [
"acadrust", "acadrust",
"cavalier_contours", "cavalier_contours",

View file

@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
rfd = "0.17" rfd = "0.17"
clap = { version = "4", features = ["derive"] } clap = { version = "4", features = ["derive"] }
env_logger = "0.11" env_logger = "0.11"
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "3742f38", features = ["serde"] } acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "cf46d04", features = ["serde"] }
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "927f888", features = ["acis", "offset"] } cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b3f3122", features = ["acis", "offset"] }
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" } dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
flate2 = "1" flate2 = "1"
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] } image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }

View file

@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] }
# Pulled in only by the `host` feature, which adds the `acadrust`-typed # Pulled in only by the `host` feature, which adds the `acadrust`-typed
# `HostApi` runtime surface. The default crate stays dependency-free so engine # `HostApi` runtime surface. The default crate stays dependency-free so engine
# crates and external tooling can depend on the manifest/ribbon contract cheaply. # 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). # Runtime IPC and serialization (host feature only).
interprocess = { version = "2", optional = true } interprocess = { version = "2", optional = true }
@ -37,7 +37,7 @@ serde_json = "1"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
cargo-lock = "11" cargo-lock = "11"
# acadrust is scanned at build time to generate the embedded type registry. # 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] [dev-dependencies]
serde_json = "1" serde_json = "1"

View file

@ -8,7 +8,7 @@ publish = false
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [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" bincode = "1.3"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }
console_error_panic_hook = "0.1" console_error_panic_hook = "0.1"

View file

@ -1416,11 +1416,19 @@ draw =
.usage-attdisp-on-off-normal = الاستخدام: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = الاستخدام: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = الاستخدام: POLYSOLID <width> <height> (حدد خطًا متعددًا أولًا) .usage-polysolid-width-height-select-a-polyline = الاستخدام: POLYSOLID <width> <height> (حدد خطًا متعددًا أولًا)
.usage-pyramid-radius-height-sides-default-4-si = الاستخدام: PYRAMID <radius> <height> [sides] (الافتراضي 4 أضلاع) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT حدد النقطة التالية أو [تراجع/إغلاق] (__ocs_arg_n__ نقاط):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT خط متعدد حدد خطًا متعددًا مستويًا مغلقًا: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT خط متعدد حدد خطًا متعددًا مستويًا مغلقًا:
.wipeout-rectangular-specify-first-corner = WIPEOUT مستطيل حدد الزاوية الأولى: .wipeout-rectangular-specify-first-corner = WIPEOUT مستطيل حدد الزاوية الأولى:
.wipeout-rectangular-specify-opposite-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 = أشعة سينية .x-ray = أشعة سينية
.xline-specify-a-point = XLINE حدد نقطة: .xline-specify-a-point = XLINE حدد نقطة:
.xline-specify-through-point = XLINE حدد نقطة العبور: .xline-specify-through-point = XLINE حدد نقطة العبور:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = Употреба: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = Употреба: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = Употреба: POLYSOLID <ширина> <височина> (първо избери полилиния) .usage-polysolid-width-height-select-a-polyline = Употреба: POLYSOLID <ширина> <височина> (първо избери полилиния)
.usage-pyramid-radius-height-sides-default-4-si = Употреба: PYRAMID <радиус> <височина> [страни] (4 страни по подразбиране) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT Посочи следваща точка или [Отмени/Затвори] (__ocs_arg_n__ точки):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline Избери затворена равнинна полилиния: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline Избери затворена равнинна полилиния:
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular Посочи първи ъгъл: .wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular Посочи първи ъгъл:
.wipeout-rectangular-specify-opposite-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 = Рентген .x-ray = Рентген
.xline-specify-a-point = XLINE Посочи точка: .xline-specify-a-point = XLINE Посочи точка:
.xline-specify-through-point = XLINE Посочи точка на преминаване: .xline-specify-through-point = XLINE Посочи точка на преминаване:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = Použití: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Obdélníkový Zadejte první roh:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Obdélníkový Zadejte protilehlý 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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Určete bod: .xline-specify-a-point = XLINE Určete bod:
.xline-specify-through-point = XLINE Určete průchozí bod: .xline-specify-through-point = XLINE Určete průchozí bod:

View file

@ -1404,11 +1404,19 @@ draw =
.usage-attdisp-on-off-normal = Verwendung: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Rechteckig Geben Sie die erste Ecke an:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rechteckig Angeben der gegenüberliegenden Ecke: .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 .x-ray = Röntgenstrahlen
.xline-specify-a-point = XLINE Geben Sie einen Punkt an: .xline-specify-a-point = XLINE Geben Sie einen Punkt an:
.xline-specify-through-point = XLINE Geben Sie durch Punkt an: .xline-specify-through-point = XLINE Geben Sie durch Punkt an:

View file

@ -1405,11 +1405,19 @@ draw =
.usage-attdisp-on-off-normal = Usage: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Rectangular Specify first corner:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Specify opposite 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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Specify a point: .xline-specify-a-point = XLINE Specify a point:
.xline-specify-through-point = XLINE Specify through point: .xline-specify-through-point = XLINE Specify through point:

View file

@ -1404,11 +1404,19 @@ draw =
.usage-attdisp-on-off-normal = Uso: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Rectangular Precise la primera esquina:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Precise la esquina opuesta: .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 .x-ray = Rayos X
.xline-specify-a-point = XLINE Precise un punto: .xline-specify-a-point = XLINE Precise un punto:
.xline-specify-through-point = XLINE Precise el punto de paso: .xline-specify-through-point = XLINE Precise el punto de paso:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = Käyttö: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Suorakulmainen Määritä ensimmäinen kulma:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Suorakulmainen Määritä vastakkainen 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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Määritä piste: .xline-specify-a-point = XLINE Määritä piste:
.xline-specify-through-point = XLINE Määritä pisteen kautta: .xline-specify-through-point = XLINE Määritä pisteen kautta:

View file

@ -1404,11 +1404,19 @@ draw =
.usage-attdisp-on-off-normal = Utilisation : ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Rectangulaire Précisez le premier coin :
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangulaire Spécifiez le coin opposé: .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 .x-ray = Rayon X
.xline-specify-a-point = XLINE Spécifiez un point: .xline-specify-a-point = XLINE Spécifiez un point:
.xline-specify-through-point = XLINE Spécifiez par le point: .xline-specify-through-point = XLINE Spécifiez par le point:

View file

@ -1392,11 +1392,19 @@ draw =
.usage-attdisp-on-off-normal = उपयोग: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = उपयोग: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = उपयोग: POLYSOLID <width> <height> (select a polyline first) .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) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT अगला बिंदु निर्दिष्ट करें या [पूर्ववत करें / बंद करें] (__ocs_arg_n__ बिंदु):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT पॉलीलाइन एक बंद प्लानर पॉलीलाइन चुनें: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT पॉलीलाइन एक बंद प्लानर पॉलीलाइन चुनें:
.wipeout-rectangular-specify-first-corner = WIPEOUT आयताकार पहले कोने निर्दिष्ट करें: .wipeout-rectangular-specify-first-corner = WIPEOUT आयताकार पहले कोने निर्दिष्ट करें:
.wipeout-rectangular-specify-opposite-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-ray = एक्स-रे
.xline-specify-a-point = XLINE एक बिंदु निर्दिष्ट करें: .xline-specify-a-point = XLINE एक बिंदु निर्दिष्ट करें:
.xline-specify-through-point = XLINE बिंदु के माध्यम से निर्दिष्ट करें: .xline-specify-through-point = XLINE बिंदु के माध्यम से निर्दिष्ट करें:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = Használat: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT téglalap Adja meg az első sarkot:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rectangular Adja meg a szemközti 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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Adjon meg egy pontot: .xline-specify-a-point = XLINE Adjon meg egy pontot:
.xline-specify-through-point = XLINE Pont megadása: .xline-specify-through-point = XLINE Pont megadása:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = Utilizzo: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Rettangolare Specificare il primo angolo:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rettangolare Specificare l'angolo opposto: .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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Specificare un punto: .xline-specify-a-point = XLINE Specificare un punto:
.xline-specify-through-point = XLINE Specificare il punto di passaggio: .xline-specify-through-point = XLINE Specificare il punto di passaggio:

View file

@ -1394,11 +1394,19 @@ draw =
.usage-attdisp-on-off-normal = 使用法: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = 使用法: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = 使用法: POLYSOLID <width> <height> (先にポリラインを選択) .usage-polysolid-width-height-select-a-polyline = 使用法: POLYSOLID <width> <height> (先にポリラインを選択)
.usage-pyramid-radius-height-sides-default-4-si = 使用法: PYRAMID <radius> <height> [sides] (既定は 4 側面) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT 次の点を指定または [Undo/Close](__ocs_arg_n__ 点):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline 閉じた平面ポリラインを選択: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Polyline 閉じた平面ポリラインを選択:
.wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular 最初のコーナーを指定: .wipeout-rectangular-specify-first-corner = WIPEOUT Rectangular 最初のコーナーを指定:
.wipeout-rectangular-specify-opposite-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 線 .x-ray = X 線
.xline-specify-a-point = XLINE 点を指定: .xline-specify-a-point = XLINE 点を指定:
.xline-specify-through-point = XLINE 通過点を指定: .xline-specify-through-point = XLINE 通過点を指定:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = 사용법: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = 사용법: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = 사용법: POLYSOLID <width> <height>(폴리라인을 먼저 선택하세요) .usage-polysolid-width-height-select-a-polyline = 사용법: POLYSOLID <width> <height>(폴리라인을 먼저 선택하세요)
.usage-pyramid-radius-height-sides-default-4-si = 사용법: PYRAMID <radius> <height> [sides] (기본 4면) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT 다음 점 지정 또는 [실행 취소/닫기](__ocs_arg_n__ 점):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 폴리선 닫힌 평면형 폴리선을 선택합니다. .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 폴리선 닫힌 평면형 폴리선을 선택합니다.
.wipeout-rectangular-specify-first-corner = WIPEOUT 직사각형 첫 번째 구석 지정: .wipeout-rectangular-specify-first-corner = WIPEOUT 직사각형 첫 번째 구석 지정:
.wipeout-rectangular-specify-opposite-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-레이 .x-ray = X-레이
.xline-specify-a-point = XLINE 점 지정: .xline-specify-a-point = XLINE 점 지정:
.xline-specify-through-point = XLINE 통과점 지정: .xline-specify-through-point = XLINE 통과점 지정:

View file

@ -1404,11 +1404,19 @@ draw =
.usage-attdisp-on-off-normal = Gebruik: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Rechthoekige eerste hoek opgeven:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Rechthoekig Geef tegenoverhoek aan: .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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Geef een punt op: .xline-specify-a-point = XLINE Geef een punt op:
.xline-specify-through-point = XLINE Geef door punt: .xline-specify-through-point = XLINE Geef door punt:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = Zastosowanie: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Prostokątny Określ pierwszy narożnik:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Prostokątny Określ przeciwny 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 .x-ray = X-Ray
.xline-specify-a-point = XLINE Określ punkt: .xline-specify-a-point = XLINE Określ punkt:
.xline-specify-through-point = XLINE Określ punkt przelotowy: .xline-specify-through-point = XLINE Określ punkt przelotowy:

View file

@ -1404,11 +1404,19 @@ draw =
.usage-attdisp-on-off-normal = Uso: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Retangular Especifique o primeiro canto:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Retangular Especifique o canto oposto: .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 .x-ray = Raio X
.xline-specify-a-point = XLINE Especifique um ponto: .xline-specify-a-point = XLINE Especifique um ponto:
.xline-specify-through-point = XLINE Especifique o ponto de passagem: .xline-specify-through-point = XLINE Especifique o ponto de passagem:

View file

@ -1407,11 +1407,19 @@ draw =
.usage-attdisp-on-off-normal = Использование: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = Использование: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = Использование: POLYSOLID <width> <height> (select a polyline first) .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) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT Укажите следующую точку или [Отменить / Замкнуть] (точек: __ocs_arg_n__):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Выберите замкнутый плоский полилин: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT Выберите замкнутый плоский полилин:
.wipeout-rectangular-specify-first-corner = WIPEOUT Прямоугольный Укажите первый угол: .wipeout-rectangular-specify-first-corner = WIPEOUT Прямоугольный Укажите первый угол:
.wipeout-rectangular-specify-opposite-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 = Рентгеновский .x-ray = Рентгеновский
.xline-specify-a-point = XLINE Укажите точку: .xline-specify-a-point = XLINE Укажите точку:
.xline-specify-through-point = XLINE Укажите через точку: .xline-specify-through-point = XLINE Укажите через точку:

View file

@ -1387,11 +1387,19 @@ draw =
.usage-attdisp-on-off-normal = Kullanım: ATTDISP ON | OFF | NORMAL .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-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) .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-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-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-first-corner = WIPEOUT Dikdörtgen İlk köşeyi belirtin:
.wipeout-rectangular-specify-opposite-corner = WIPEOUT Dikdörtgen Karşı 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ı .x-ray = X Işını
.xline-specify-a-point = XLINE Bir nokta belirtin: .xline-specify-a-point = XLINE Bir nokta belirtin:
.xline-specify-through-point = XLINE Geçiş noktasını belirtin: .xline-specify-through-point = XLINE Geçiş noktasını belirtin:

View file

@ -1391,11 +1391,19 @@ draw =
.usage-attdisp-on-off-normal = 用法: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = 用法: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = 用法: POLYSOLID <width> <height> (select a polyline first) .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) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT 指定下一点或 [放弃/闭合](__ocs_arg_n__ 个点):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 多段线 选择闭合平面多段线: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 多段线 选择闭合平面多段线:
.wipeout-rectangular-specify-first-corner = WIPEOUT 矩形 指定第一个角点: .wipeout-rectangular-specify-first-corner = WIPEOUT 矩形 指定第一个角点:
.wipeout-rectangular-specify-opposite-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 射线 .x-ray = X 射线
.xline-specify-a-point = XLINE 指定点: .xline-specify-a-point = XLINE 指定点:
.xline-specify-through-point = XLINE 指定通过点: .xline-specify-through-point = XLINE 指定通过点:

View file

@ -1403,11 +1403,19 @@ draw =
.usage-attdisp-on-off-normal = 用法: ATTDISP ON | OFF | NORMAL .usage-attdisp-on-off-normal = 用法: ATTDISP ON | OFF | NORMAL
.usage-polysolid-width-height-select-a-polyline = 用法: POLYSOLID <width> <height> (select a polyline first) .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) .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-specify-next-point-or-undo-close-n-poi = WIPEOUT 指定下一點或 [放棄/閉合](__ocs_arg_n__ 個點):
.wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 多段線 選擇閉合平面多段線: .wipeout-polyline-select-a-closed-planar-polyli = WIPEOUT 多段線 選擇閉合平面多段線:
.wipeout-rectangular-specify-first-corner = WIPEOUT 矩形 指定第一個角點: .wipeout-rectangular-specify-first-corner = WIPEOUT 矩形 指定第一個角點:
.wipeout-rectangular-specify-opposite-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 射線 .x-ray = X 射線
.xline-specify-a-point = XLINE 指定點: .xline-specify-a-point = XLINE 指定點:
.xline-specify-through-point = XLINE 指定通過點: .xline-specify-through-point = XLINE 指定通過點:

View file

@ -1014,7 +1014,10 @@ impl OpenCADStudio {
self.commit_undo_delta(i, pending); self.commit_undo_delta(i, pending);
} }
} }
CmdResult::WipeoutFromPolyline(handle) => { CmdResult::WipeoutFromPolyline {
handle,
erase_source,
} => {
let wipeout = { let wipeout = {
let scene = &self.tabs[i].scene; let scene = &self.tabs[i].scene;
scene scene
@ -1026,16 +1029,23 @@ impl OpenCADStudio {
) )
}; };
if let Some(wipeout) = wipeout { 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)); return self.apply_cmd_result(CmdResult::CommitAndExit(wipeout));
} }
self.command_line.push_error( 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) = let command =
self.tabs[i].active_cmd.as_ref().map(|command| command.prompt()) crate::modules::draw::draw::wipeout::WipeoutCommand::new_polyline();
{ self.command_line.push_info(
self.command_line.push_info(&prompt); &crate::command::CadCommand::prompt(&command),
} );
self.tabs[i].active_cmd = Some(Box::new(command));
} }
CmdResult::MviewSwitchLayout(layout) => { CmdResult::MviewSwitchLayout(layout) => {
let task = self.on_layout_switch_preserving_command(layout); let task = self.on_layout_switch_preserving_command(layout);

View file

@ -48,7 +48,11 @@ impl OpenCADStudio {
let wo_cmd = match args.as_str() { let wo_cmd = match args.as_str() {
"P" | "POLYLINE" => WipeoutCommand::new_polyline(), "P" | "POLYLINE" => WipeoutCommand::new_polyline(),
"R" | "RECTANGULAR" => WipeoutCommand::new_rectangular(), "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.command_line.push_info(&wo_cmd.prompt());
self.tabs[i].active_cmd = Some(Box::new(wo_cmd)); self.tabs[i].active_cmd = Some(Box::new(wo_cmd));

View file

@ -514,7 +514,15 @@ inventory::submit!(crate::command::CommandRegistration {
"CMLJUST", "CMLJUST",
"TEXTQLTY", "TEXTQLTY",
"SORTENTS", "SORTENTS",
"FRAME",
"IMAGEFRAME",
"PDFFRAME",
"POINTCLOUDCLIPFRAME",
"XCLIPFRAME", "XCLIPFRAME",
"WIPEOUTFRAME",
"FRAMES0",
"FRAMES1",
"FRAMES2",
"HALOGAP", "HALOGAP",
"TRACEWID", "TRACEWID",
"SKETCHINC", "SKETCHINC",

View file

@ -3,6 +3,9 @@ use super::*;
impl OpenCADStudio { impl OpenCADStudio {
pub(super) fn dispatch_styleprops(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> { pub(super) fn dispatch_styleprops(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
match cmd { 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 // COLOR <ByLayer|ByBlock|1-255|name> — the colour applied to new
// objects (CECOLOR). Bare COLOR reports the current value. // objects (CECOLOR). Bare COLOR reports the current value.
"COLOR" | "COLOUR" | "CECOLOR" | "DDCOLOR" => { "COLOR" | "COLOUR" | "CECOLOR" | "DDCOLOR" => {
@ -919,7 +922,12 @@ impl OpenCADStudio {
| "CMLJUST" | "CMLJUST"
| "TEXTQLTY" | "TEXTQLTY"
| "SORTENTS" | "SORTENTS"
| "FRAME"
| "IMAGEFRAME"
| "PDFFRAME"
| "POINTCLOUDCLIPFRAME"
| "XCLIPFRAME" | "XCLIPFRAME"
| "WIPEOUTFRAME"
| "HALOGAP" | "HALOGAP"
| "TRACEWID" | "TRACEWID"
| "SKETCHINC" | "SKETCHINC"
@ -945,9 +953,83 @@ impl OpenCADStudio {
let value = it.next().map(|s| s.trim().to_string()); let value = it.next().map(|s| s.trim().to_string());
if name.is_empty() || name == "?" { if name.is_empty() || name == "?" {
self.command_line.push_info( 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 { } 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. // Parse a boolean given as 0/1 or ON/OFF.
let parse_bool = |s: &str| match s.to_uppercase().as_str() { let parse_bool = |s: &str| match s.to_uppercase().as_str() {
"1" | "ON" | "TRUE" => Some(true), "1" | "ON" | "TRUE" => Some(true),
@ -1597,16 +1679,6 @@ impl OpenCADStudio {
.map_err(|_| "SETVAR: integer value required.".into()), .map_err(|_| "SETVAR: integer value required.".into()),
None => Ok((format!("SORTENTS = {}", h.sort_entities), false)), 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 { "HALOGAP" => match &value {
Some(v) => v Some(v) => v
.parse::<i16>() .parse::<i16>()

View file

@ -168,7 +168,9 @@ fn plot_scene_content(
Vec<crate::scene::model::hatch_model::HatchModel>, Vec<crate::scene::model::hatch_model::HatchModel>,
crate::io::pdf_export::PlotGroupSplits, 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_hatches = scene.paper_canvas_hatches().as_ref().clone();
let paper_wipeouts = scene.paper_canvas_wipeouts().as_ref().clone(); let paper_wipeouts = scene.paper_canvas_wipeouts().as_ref().clone();
if scene.current_layout == "Model" { if scene.current_layout == "Model" {
@ -184,8 +186,9 @@ fn plot_scene_content(
splits, splits,
); );
} }
let (model_pattern_wires, model_hatches, model_wipeouts) = let (mut model_pattern_wires, model_hatches, model_wipeouts) =
scene.viewport_plot_fills(); scene.viewport_plot_fills();
model_pattern_wires.retain(|wire| wire.plot_visible);
let (wires, hatches, wipeouts, splits) = if paper_space_last { let (wires, hatches, wipeouts, splits) = if paper_space_last {
let splits = crate::io::pdf_export::PlotGroupSplits { let splits = crate::io::pdf_export::PlotGroupSplits {

View file

@ -2141,6 +2141,8 @@ impl OpenCADStudio {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -1297,7 +1297,11 @@ pub enum CmdResult {
boundary_handle: Handle, boundary_handle: Handle,
}, },
/// Create a wipeout from an existing closed polyline in the active space. /// 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 /// Temporarily switch between paper and Model while MVIEW defines a new
/// model-space window, keeping the command active. /// model-space window, keeping the command active.
MviewSwitchLayout(String), MviewSwitchLayout(String),

View file

@ -2069,6 +2069,8 @@ fn tessellate_dimension_inner(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -2100,6 +2102,8 @@ fn tessellate_dimension_inner(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -2131,6 +2135,8 @@ fn tessellate_dimension_inner(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -2163,6 +2169,8 @@ fn tessellate_dimension_inner(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -2211,6 +2219,8 @@ fn tessellate_dimension_inner(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -301,6 +301,11 @@ fn geo_marker_lines(data: &GeoPositionMarkerData) -> Vec<[f64; 3]> {
fn point_cloud_lines(data: &PointCloudData) -> Vec<[f64; 3]> { fn point_cloud_lines(data: &PointCloudData) -> Vec<[f64; 3]> {
let mut points = Vec::new(); let mut points = Vec::new();
push_box(&mut points, data.extents_min, data.extents_max); 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 { if data.show_clipping {
for clip in &data.clippings { for clip in &data.clippings {
if clip.vertices.len() < 2 { 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]> { fn point_cloud_ex_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
let mut points = Vec::new(); let mut points = Vec::new();
push_box(&mut points, data.extents_min, data.extents_max); 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 { if data.show_cropping {
for crop in &data.croppings { for crop in &data.croppings {
if crop.points.len() < 2 { if crop.points.len() < 2 {
@ -339,6 +349,14 @@ fn point_cloud_ex_lines(data: &PointCloudExData) -> Vec<[f64; 3]> {
points 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]> { 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 { let Some(view) = document.views.iter().find(|view| view.handle == view_handle) else {
return Vec::new(); return Vec::new();

View file

@ -677,6 +677,8 @@ impl LeaderTess for Leader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -853,6 +855,8 @@ impl LeaderTess for Leader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -1506,6 +1506,8 @@ impl MultiLeaderTess for MultiLeader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1843,6 +1845,8 @@ impl MultiLeaderTess for MultiLeader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1913,6 +1917,8 @@ impl MultiLeaderTess for MultiLeader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1998,6 +2004,8 @@ impl MultiLeaderTess for MultiLeader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -2038,6 +2046,8 @@ impl MultiLeaderTess for MultiLeader {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -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::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
use crate::scene::convert::acad_to_render::{GlyphRun, TextStroke, RenderEntity, RenderObject}; 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::object::{GripApply, GripDef, PropSection, PropValue, Property};
use crate::scene::model::wire_model::SnapHint;
use crate::scene::text::lff; use crate::scene::text::lff;
// ── Shared geometry helpers ─────────────────────────────────────────────────── // ── Shared geometry helpers ───────────────────────────────────────────────────
@ -416,62 +417,112 @@ impl Transformable for RasterImage {
// ── Wipeout ─────────────────────────────────────────────────────────────────── // ── 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 { impl RenderConvertible for Wipeout {
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> { fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
let corners = image_corners( let boundary = wipeout_boundary(self);
&self.insertion_point, let mut pts = boundary.clone();
&self.u_vector, if let Some(&first) = pts.first() {
&self.v_vector, pts.push(first);
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)
};
Some(RenderEntity { Some(RenderEntity {
// Interior pick surface — a wipeout reads as a solid patch, so a pick_tris: cadkernel::space::polygon::triangulate_rings(
// click anywhere on it should select it. &wipeout_pick_rings(self),
pick_tris: crate::entities::common::quad_pick_tris(&corners), cadkernel::geom2d::Tolerance::default(),
),
object: RenderObject::Lines(pts), object: RenderObject::Lines(pts),
snap_pts: vec![], snap_pts: boundary
.iter()
.map(|point| (glam::DVec3::from(*point), SnapHint::Endpoint))
.collect(),
tangent_geoms: vec![], tangent_geoms: vec![],
key_vertices: corners.to_vec(), key_vertices: boundary,
fill_tris: vec![], fill_tris: vec![],
}) })
} }
@ -479,33 +530,15 @@ impl RenderConvertible for Wipeout {
impl Grippable for Wipeout { impl Grippable for Wipeout {
fn grips(&self) -> Vec<GripDef> { fn grips(&self) -> Vec<GripDef> {
// If polygonal clipping is active, expose individual polygon vertices as grips. if wipeout_is_polygonal(self) {
let is_polygon = self.clipping_enabled wipeout_boundary(self)
&& self.clip_boundary_vertices.len() >= 3 .into_iter()
&& 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()
.enumerate() .enumerate()
.map(|(i, v)| { .map(|(i, point)| {
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;
if i == 0 { if i == 0 {
square_grip(i, glam::DVec3::new(ox + wx, oy + wy, oz + wz)) square_grip(i, glam::DVec3::from(point))
} else { } else {
center_grip(i, glam::DVec3::new(ox + wx, oy + wy, oz + wz)) center_grip(i, glam::DVec3::from(point))
} }
}) })
.collect() .collect()
@ -527,61 +560,103 @@ impl Grippable for Wipeout {
} }
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) { fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
let is_polygon = self.clipping_enabled if wipeout_is_polygonal(self) {
&& self.clip_boundary_vertices.len() >= 3 if let Some(current) = self.clip_boundary_vertices.get(grip_id).cloned() {
&& matches!( let current_world = wipeout_clip_to_world(self, &current);
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;
let new_w = match apply { let new_w = match apply {
GripApply::Translate(d) => [ GripApply::Translate(d) => [
cur_wx + d.x as f64, current_world[0] + d.x as f64,
cur_wy + d.y as f64, current_world[1] + d.y as f64,
cur_wz + d.z as f64, current_world[2] + d.z as f64,
], ],
GripApply::Absolute(p) => [p.x as f64, p.y as f64, p.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. if let Some(clip) = wipeout_world_to_clip(self, new_w) {
// In 2D (XY plane): new_w - insertion_point = u_vec * vx * sx + v_vec * vy * sy self.clip_boundary_vertices[grip_id] = clip;
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;
} }
} }
} else if grip_id == 0 { return;
match apply { }
GripApply::Translate(d) => {
self.insertion_point.x += d.x as f64; let corners = wipeout_boundary(self);
self.insertion_point.y += d.y as f64; let Some(current) = corners.get(grip_id).copied() else {
self.insertion_point.z += d.z as f64; return;
} };
GripApply::Absolute(p) => { let target = match apply {
self.insertion_point.x = p.x as f64; GripApply::Translate(delta) => glam::DVec3::from(current) + delta,
self.insertion_point.y = p.y as f64; GripApply::Absolute(point) => point,
self.insertion_point.z = p.z as f64; };
} 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 { PropSection {
title: t!("Misc").into_owned(), title: t!("Misc").into_owned(),
props: vec![ props: vec![
Property { ro(t!("Show image").as_ref(), "wo_show_image", if show_image { t!("Yes") } else { t!("No") }),
label: t!("Show image").into_owned(), ro(t!("Show clipped").as_ref(), "wo_show_clipped", if show_clipped { t!("Yes") } else { t!("No") }),
field: "wo_show_image", ro(t!("Background transparency").as_ref(), "wo_bg_transparency", if bg_transparency { t!("Yes") } else { t!("No") }),
value: PropValue::BoolToggle { ],
field: "wo_show_image", },
value: show_image, PropSection {
}, title: t!("Image Adjust").into_owned(),
}, props: vec![
Property { ro(t!("Brightness").as_ref(), "wo_brightness", self.brightness.to_string()),
label: t!("Show clipped").into_owned(), ro(t!("Contrast").as_ref(), "wo_contrast", self.contrast.to_string()),
field: "wo_show_clipped", ro(t!("Fade").as_ref(), "wo_fade", self.fade.to_string()),
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,
},
},
], ],
}, },
] ]
} }
fn apply_geom_prop(&mut self, field: &str, value: &str) { 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 { let Ok(v) = value.trim().parse::<f64>() else {
return; return;
}; };

View file

@ -1670,6 +1670,8 @@ pub fn tessellate_table(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -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 /// Write a drawing variable, creating the variable dictionary and record when
/// needed so new drawings preserve the value too. /// needed so new drawings preserve the value too.
pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &str) { pub(crate) fn set_drawing_variable(doc: &mut CadDocument, name: &str, value: &str) {

View file

@ -875,6 +875,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Discussions" => Some(("common", "discussions")), "Discussions" => Some(("common", "discussions")),
"Discussions load from GitHub." => Some(("common", "discussions-load-from-github")), "Discussions load from GitHub." => Some(("common", "discussions-load-from-github")),
"Display" => Some(("common", "display")), "Display" => Some(("common", "display")),
"Display but not plot" => Some(("draw", "display-but-not-plot")),
"Distance" => Some(("common", "distance")), "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}°\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")), "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")), "Fraction format (DIMFRAC)" => Some(("common", "fraction-format-dimfrac")),
"Fractional" => Some(("common", "fractional")), "Fractional" => Some(("common", "fractional")),
"Frame Text" => Some(("properties", "frame-text")), "Frame Text" => Some(("properties", "frame-text")),
"Frames" => Some(("draw", "frames")),
"Frames & Print" => Some(("insert", "frames-print")), "Frames & Print" => Some(("insert", "frames-print")),
"Frames Off" => Some(("insert", "frames-off")), "Frames Off" => Some(("insert", "frames-off")),
"Frames On" => Some(("insert", "frames-on")), "Frames On" => Some(("insert", "frames-on")),
"Frames On, Not Plotted" => Some(("draw", "frames-on-not-plotted")),
"Freeze" => Some(("layers", "freeze")), "Freeze" => Some(("layers", "freeze")),
"From DXF" => Some(("properties", "from-dxf")), "From DXF" => Some(("properties", "from-dxf")),
"Front" => Some(("common", "front")), "Front" => Some(("common", "front")),
@ -1205,6 +1208,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
"Identifier" => Some(("properties", "identifier")), "Identifier" => Some(("properties", "identifier")),
"Ignore" => Some(("properties", "ignore")), "Ignore" => Some(("properties", "ignore")),
"Illuminance Distance" => Some(("properties", "illuminance-distance")), "Illuminance Distance" => Some(("properties", "illuminance-distance")),
"Image Adjust" => Some(("draw", "image-adjust")),
"Import" => Some(("common", "import")), "Import" => Some(("common", "import")),
"In" => Some(("annotate", "in")), "In" => Some(("annotate", "in")),
"Inactive" => Some(("layout", "inactive")), "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")), "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 new value (blank to read):" => Some(("styles", "setvar-new-value-blank-to-read")),
"SETVAR variable name:" => Some(("styles", "setvar-variable-name")), "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")), "SHEETSET: Sheet Set Manager not yet implemented." => Some(("view", "sheetset-sheet-set-manager-not-yet-implemented")),
"SHX" => Some(("properties", "shx")), "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")), "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")), "Volume" => Some(("properties", "volume")),
"WBLOCK Saved \"{block_name}\" → \"{}\"" => Some(("common", "wblock-saved-block-name")), "WBLOCK Saved \"{block_name}\" → \"{}\"" => Some(("common", "wblock-saved-block-name")),
"WBLOCK save failed: {error}" => Some(("common", "wblock-save-failed-error")), "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 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 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 first corner:" => Some(("draw", "wipeout-rectangular-specify-first-corner")),
"WIPEOUT Rectangular Specify opposite corner:" => Some(("draw", "wipeout-rectangular-specify-opposite-corner")), "WIPEOUT Rectangular Specify opposite corner:" => Some(("draw", "wipeout-rectangular-specify-opposite-corner")),
"Warning" => Some(("errors", "warning")), "Warning" => Some(("errors", "warning")),

View file

@ -223,6 +223,8 @@ impl CadCommand for AlignedDimensionCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -279,6 +281,8 @@ fn preview_aligned(p1: DVec3, p2: DVec3, dim_pt: DVec3) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -164,6 +164,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -195,6 +195,8 @@ fn preview_line(a: Vec3, b: Vec3) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -183,6 +183,8 @@ impl CadCommand for DimBaselineCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -163,6 +163,8 @@ impl CadCommand for DimContinueCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -89,6 +89,8 @@ impl CadCommand for DimJogLineCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -106,6 +106,8 @@ impl CadCommand for DimTeditCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -303,6 +303,8 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -240,6 +240,8 @@ fn preview_wire(points: Vec<DVec3>) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -230,6 +230,8 @@ fn preview_wire(pts: &[Vec3], arrow_size: f32) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -465,6 +465,8 @@ fn preview_wire(pts: &[Vec3]) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -140,6 +140,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -96,6 +96,8 @@ impl CadCommand for QdimCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -200,6 +200,8 @@ fn preview_wire(points: Vec<Vec3>) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -202,6 +202,8 @@ impl CadCommand for TableCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -106,6 +106,8 @@ impl CadCommand for ToleranceCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -204,6 +204,8 @@ impl CadCommand for AttdefCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -348,6 +348,8 @@ impl HatchCommand {
scale: source.scale, scale: source.scale,
world_origin: origin, world_origin: origin,
boundary_wcs: Some(std::sync::Arc::new(wcs)), boundary_wcs: Some(std::sync::Arc::new(wcs)),
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: Some(std::sync::Arc::new(exterior)), boundary_exterior: Some(std::sync::Arc::new(exterior)),
boundary_sources: Some(std::sync::Arc::new(boundary_sources)), boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
draw_depth: source.draw_depth, draw_depth: source.draw_depth,
@ -387,6 +389,8 @@ impl HatchCommand {
scale: 1.0, scale: 1.0,
world_origin: origin, world_origin: origin,
boundary_wcs: Some(std::sync::Arc::new(wcs)), boundary_wcs: Some(std::sync::Arc::new(wcs)),
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: Some(std::sync::Arc::new(exterior)), boundary_exterior: Some(std::sync::Arc::new(exterior)),
boundary_sources: Some(std::sync::Arc::new(boundary_sources)), boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
draw_depth: 0.0, draw_depth: 0.0,
@ -699,6 +703,8 @@ impl GradientCommand {
scale: 1.0, scale: 1.0,
world_origin: origin, world_origin: origin,
boundary_wcs: Some(std::sync::Arc::new(wcs)), boundary_wcs: Some(std::sync::Arc::new(wcs)),
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: Some(std::sync::Arc::new(exterior)), boundary_exterior: Some(std::sync::Arc::new(exterior)),
boundary_sources: Some(std::sync::Arc::new(boundary_sources)), boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
draw_depth: 0.0, draw_depth: 0.0,

View file

@ -184,6 +184,8 @@ impl CadCommand for MlineCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -126,6 +126,8 @@ impl CadCommand for ImageCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -88,6 +88,8 @@ impl CadCommand for RayCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -188,6 +190,8 @@ impl CadCommand for XLineCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -2,7 +2,9 @@
use acadrust::entities::{Wipeout, WipeoutClipType}; use acadrust::entities::{Wipeout, WipeoutClipType};
use acadrust::types::{Vector2, Vector3}; 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 glam::DVec3;
use crate::t; use crate::t;
@ -26,6 +28,8 @@ pub struct WipeoutCommand {
first: Option<DVec3>, first: Option<DVec3>,
points: Vec<DVec3>, points: Vec<DVec3>,
plane: WorkingPlane, plane: WorkingPlane,
selected_polyline: Option<Handle>,
frame_mode: i16,
} }
#[derive(Clone, Copy, PartialEq)] #[derive(Clone, Copy, PartialEq)]
@ -33,15 +37,19 @@ enum WipeoutMode {
Draw, Draw,
Polyline, Polyline,
Rectangular, Rectangular,
Frames,
ErasePolyline,
} }
impl WipeoutCommand { impl WipeoutCommand {
pub fn new_polygonal() -> Self { pub fn new_polygonal(frame_mode: i16) -> Self {
Self { Self {
mode: WipeoutMode::Draw, mode: WipeoutMode::Draw,
first: None, first: None,
points: Vec::new(), points: Vec::new(),
plane: WorkingPlane::default(), plane: WorkingPlane::default(),
selected_polyline: None,
frame_mode: frame_mode.clamp(0, 2),
} }
} }
@ -51,6 +59,8 @@ impl WipeoutCommand {
first: None, first: None,
points: Vec::new(), points: Vec::new(),
plane: WorkingPlane::default(), plane: WorkingPlane::default(),
selected_polyline: None,
frame_mode: 1,
} }
} }
@ -61,17 +71,27 @@ impl WipeoutCommand {
first: None, first: None,
points: Vec::new(), points: Vec::new(),
plane: WorkingPlane::default(), plane: WorkingPlane::default(),
selected_polyline: None,
frame_mode: 1,
} }
} }
fn finish_draw(&self) -> CmdResult { fn finish_draw(&self) -> CmdResult {
let local: Vec<DVec3> = self let local: Vec<[f64; 2]> = self
.points .points
.iter() .iter()
.map(|point| self.plane.to_local(*point)) .map(|point| {
let point = self.plane.to_local(*point);
[point.x, point.y]
})
.collect(); .collect();
match make_poly_wipeout(&local) { let plane = Plane::from_axes(
Some(entity) => CmdResult::CommitAndExit(self.plane.place_entity(entity)), 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, None => CmdResult::NeedPoint,
} }
} }
@ -94,7 +114,7 @@ impl CadCommand for WipeoutCommand {
fn prompt(&self) -> String { fn prompt(&self) -> String {
match self.mode { match self.mode {
WipeoutMode::Draw if self.points.is_empty() => { 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 => { WipeoutMode::Draw => {
let n = self.points.len(); let n = self.points.len();
@ -113,18 +133,38 @@ impl CadCommand for WipeoutCommand {
WipeoutMode::Rectangular => { WipeoutMode::Rectangular => {
t!("WIPEOUT Rectangular Specify opposite corner:").into_owned() 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> { fn options(&self) -> Vec<CmdOption> {
match self.mode { match self.mode {
WipeoutMode::Draw if self.points.is_empty() => { 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![ WipeoutMode::Draw => vec![
CmdOption::new(t!("Undo").as_ref(), "U"), CmdOption::new(t!("Undo").as_ref(), "U"),
CmdOption::new(t!("Close").as_ref(), "C"), 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(), _ => Vec::new(),
} }
} }
@ -133,9 +173,13 @@ impl CadCommand for WipeoutCommand {
match self.mode { match self.mode {
WipeoutMode::Draw => { WipeoutMode::Draw => {
if let Some(first) = self.points.first() { if let Some(first) = self.points.first() {
let distance_squared = let first = self.plane.to_local(*first);
(point.x - first.x).powi(2) + (point.y - first.y).powi(2); let point = self.plane.to_local(point);
if self.points.len() >= 3 && distance_squared < 1e-12 { 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(); return self.finish_draw();
} }
} }
@ -146,21 +190,35 @@ impl CadCommand for WipeoutCommand {
if let Some(first) = self.first { if let Some(first) = self.first {
let first = self.plane.to_local(first); let first = self.plane.to_local(first);
let point = self.plane.to_local(point); let point = self.plane.to_local(point);
CmdResult::CommitAndExit( let plane = Plane::from_axes(
self.plane.place_entity(make_rect_wipeout(first, point)), 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 { } else {
self.first = Some(point); self.first = Some(point);
CmdResult::NeedPoint CmdResult::NeedPoint
} }
} }
WipeoutMode::Polyline => CmdResult::NeedPoint, WipeoutMode::Polyline | WipeoutMode::Frames | WipeoutMode::ErasePolyline => {
CmdResult::NeedPoint
}
} }
} }
fn on_enter(&mut self) -> CmdResult { fn on_enter(&mut self) -> CmdResult {
match self.mode { 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::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, _ => CmdResult::Cancel,
} }
} }
@ -177,33 +235,55 @@ impl CadCommand for WipeoutCommand {
if handle.is_null() { if handle.is_null() {
CmdResult::NeedPoint CmdResult::NeedPoint
} else { } else {
CmdResult::WipeoutFromPolyline(handle) self.selected_polyline = Some(handle);
self.mode = WipeoutMode::ErasePolyline;
CmdResult::NeedPoint
} }
} }
fn wants_text_input(&self) -> bool { 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 { 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> { fn on_text_input(&mut self, text: &str) -> Option<CmdResult> {
if self.mode != WipeoutMode::Draw { let text = text.trim().to_ascii_uppercase();
return None; match self.mode {
} WipeoutMode::Draw => match text.as_str() {
match text.trim().to_ascii_uppercase().as_str() { "F" | "FRAMES" if self.points.is_empty() => {
"P" | "POLYLINE" if self.points.is_empty() => { self.mode = WipeoutMode::Frames;
self.mode = WipeoutMode::Polyline; Some(CmdResult::NeedPoint)
Some(CmdResult::NeedPoint) }
} "P" | "POLYLINE" if self.points.is_empty() => {
"U" | "UNDO" if !self.points.is_empty() => { self.mode = WipeoutMode::Polyline;
Some(self.undo_point()) Some(CmdResult::NeedPoint)
} }
"C" | "CLOSE" if self.points.len() >= 3 => { "U" | "UNDO" if !self.points.is_empty() => Some(self.undo_point()),
Some(self.finish_draw()) "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, _ => None,
} }
} }
@ -264,250 +344,115 @@ impl CadCommand for WipeoutCommand {
false, false,
)) ))
} }
WipeoutMode::Polyline => None, WipeoutMode::Polyline | WipeoutMode::Frames | WipeoutMode::ErasePolyline => None,
} }
} }
} }
fn make_rect_wipeout(first: DVec3, second: DVec3) -> EntityType { impl WipeoutCommand {
let corner1 = Vector3::new( fn finish_polyline(&self, erase_source: bool) -> CmdResult {
first.x.min(second.x), self.selected_polyline.map_or(CmdResult::Cancel, |handle| {
first.y.min(second.y), CmdResult::WipeoutFromPolyline {
first.z, handle,
); erase_source,
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)
}) })
.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(); let mut wipeout = Wipeout::new();
wipeout.insertion_point = Vector3::new(min_x, min_y, z); wipeout.insertion_point = vector(plane.point_at(frame.origin));
wipeout.u_vector = Vector3::new(width, 0.0, 0.0); wipeout.u_vector = vector(plane.x_axis) * frame.size[0];
wipeout.v_vector = Vector3::new(0.0, height, 0.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.size = Vector2::new(1.0, 1.0);
wipeout.clip_type = WipeoutClipType::Polygonal; 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; wipeout.clipping_enabled = true;
Some(EntityType::Wipeout(wipeout)) Some(EntityType::Wipeout(wipeout))
} }
fn explicitly_closed(points: &[DVec3]) -> bool { fn explicitly_closed(points: &[[f64; 2]]) -> bool {
points.len() >= 4 && same_point(points[0], *points.last().unwrap()) points.len() >= 4
} && cadkernel::geom2d::Vec2::from(points[0])
.distance(cadkernel::geom2d::Vec2::from(*points.last().unwrap()))
fn sample_2d_polyline( <= Tolerance::default().linear()
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
} }
/// Build a wipeout boundary from a picked closed polyline without consuming /// 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> { 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) => { 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 .vertices
.iter() .iter()
.map(|vertex| DVec3::new(vertex.location.x, vertex.location.y, polyline.elevation)) .map(|vertex| [vertex.location.x, vertex.location.y])
.collect(); .collect();
if !polyline.is_closed && !explicitly_closed(&raw) { if !polyline.is_closed && !explicitly_closed(&raw) {
return None; return None;
} }
let vertices: Vec<([f64; 2], f64)> = polyline from_ocs(&raw, polyline.normal, polyline.elevation)
.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::Polyline2D(polyline) => { 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 .vertices
.iter() .iter()
.map(|vertex| { .map(|vertex| [vertex.location.x, vertex.location.y])
DVec3::new(
vertex.location.x,
vertex.location.y,
polyline.elevation,
)
})
.collect(); .collect();
if !polyline.is_closed() && !explicitly_closed(&raw) { if !polyline.is_closed() && !explicitly_closed(&raw) {
return None; return None;
} }
let vertices: Vec<([f64; 2], f64)> = polyline from_ocs(&raw, polyline.normal, polyline.elevation)
.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
} }
_ => return None, _ => return None,
}; }
make_poly_wipeout(&points)
} }
// ── Autocomplete registry ───────────────────────────────── // ── Autocomplete registry ─────────────────────────────────

View file

@ -365,6 +365,8 @@ impl CadCommand for AreaCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -88,6 +88,8 @@ impl CadCommand for DistCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -73,6 +73,8 @@ impl MeasureGeomCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -3724,6 +3724,8 @@ fn preview_wire(points: Vec<[f32; 3]>, color: [f32; 4], name: &str) -> WireModel
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -69,7 +69,7 @@ impl CadModule for InsertModule {
), ),
( (
"FRAMES2", "FRAMES2",
"Frames & Print", "Frames On, Not Plotted",
IconKind::Svg(include_bytes!( IconKind::Svg(include_bytes!(
"../../../assets/icons/underlay_frames.svg" "../../../assets/icons/underlay_frames.svg"
)), )),

View file

@ -514,6 +514,8 @@ fn wire(name: &str, points: Vec<[f32; 3]>) -> WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -57,6 +57,8 @@ impl CadCommand for PlotWindowCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -139,6 +139,8 @@ impl CadCommand for ZoomWindowCommand {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -73,6 +73,8 @@ pub struct LocalWire {
/// shader band grows with a scaled insert. `0.0` = a normal wire. /// shader band grows with a scaled insert. `0.0` = a normal wire.
pub world_width: f32, pub world_width: f32,
pub plinegen: bool, pub plinegen: bool,
pub plot_visible: bool,
pub hide_unselected: bool,
/// Set at construction; used to discriminate fill-only GPU batches from /// Set at construction; used to discriminate fill-only GPU batches from
/// stroke batches in [`StyleKey`]. Derived from /// stroke batches in [`StyleKey`]. Derived from
/// `points.is_empty() && !fill_tris.is_empty()`. /// `points.is_empty() && !fill_tris.is_empty()`.
@ -729,6 +731,8 @@ fn tessellate_sub_local(
return vec![]; 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()); let mut result = Vec::with_capacity(wires_out.len());
for wire in wires_out { for wire in wires_out {
// Per-wire point-count cap: a single wire that exceeds this is skipped // 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, line_weight_px: lw_px,
world_width: wire.world_width, world_width: wire.world_width,
plinegen: wire.plinegen, plinegen: wire.plinegen,
plot_visible: frame_mode.is_none_or(|mode| mode == 1),
hide_unselected: frame_mode == Some(0),
is_fill_only, is_fill_only,
color_is_byblock: color_is_byblock && wire_on_base_color, color_is_byblock: color_is_byblock && wire_on_base_color,
lt_is_byblock, lt_is_byblock,
@ -1362,6 +1368,8 @@ struct StyleKey {
/// switchable after block geometry is merged by style. /// switchable after block geometry is merged by style.
fill_is_2d_solid: bool, fill_is_2d_solid: bool,
fill_is_3d: bool, fill_is_3d: bool,
plot_visible: bool,
hide_unselected: bool,
/// Bit-cast composed block-local depth for band wires (`0` = no override). /// Bit-cast composed block-local depth for band wires (`0` = no override).
/// Keeps bands of different in-block draw ranks in separate batches so /// Keeps bands of different in-block draw ranks in separate batches so
/// each finalized WireModel carries one correct `depth_override`. /// each finalized WireModel carries one correct `depth_override`.
@ -1396,6 +1404,8 @@ struct BatchEntry {
fill_tris_low: Vec<[f32; 3]>, fill_tris_low: Vec<[f32; 3]>,
fill_is_3d: bool, fill_is_3d: bool,
fill_is_2d_solid: bool, fill_is_2d_solid: bool,
plot_visible: bool,
hide_unselected: bool,
/// Accumulated thickness-wall pick geometry, paired high/low like /// Accumulated thickness-wall pick geometry, paired high/low like
/// `fill_tris`. Pick-only — no GPU batch reads this. /// `fill_tris`. Pick-only — no GPU batch reads this.
pick_tris: Vec<[f32; 3]>, pick_tris: Vec<[f32; 3]>,
@ -1460,6 +1470,8 @@ impl BatchEntry {
_is_fill_only: bool, _is_fill_only: bool,
fill_is_2d_solid: bool, fill_is_2d_solid: bool,
fill_is_3d: bool, fill_is_3d: bool,
plot_visible: bool,
hide_unselected: bool,
) -> Self { ) -> Self {
// `is_fill_only` is part of the StyleKey hash so greek fills never // `is_fill_only` is part of the StyleKey hash so greek fills never
// share a batch with regular wires (otherwise the finalized // share a batch with regular wires (otherwise the finalized
@ -1479,6 +1491,8 @@ impl BatchEntry {
plinegen, plinegen,
fill_is_2d_solid, fill_is_2d_solid,
fill_is_3d, fill_is_3d,
plot_visible,
hide_unselected,
min_x: f32::INFINITY, min_x: f32::INFINITY,
min_y: f32::INFINITY, min_y: f32::INFINITY,
max_x: f32::NEG_INFINITY, max_x: f32::NEG_INFINITY,
@ -1496,6 +1510,10 @@ impl Batches {
.into_iter() .into_iter()
.chain(self.by_style.into_values()) .chain(self.by_style.into_values())
.map(|mut b| { .map(|mut b| {
if b.hide_unselected && !selected {
b.points.clear();
b.points_low.clear();
}
let aabb = if b.min_x.is_infinite() { let aabb = if b.min_x.is_infinite() {
WireModel::UNBOUNDED_AABB WireModel::UNBOUNDED_AABB
} else { } else {
@ -1521,6 +1539,8 @@ impl Batches {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: b.world_width, world_width: b.world_width,
depth_override: b.local_depth, 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_3d: b.fill_is_3d,
fill_is_2d_solid: b.fill_is_2d_solid, fill_is_2d_solid: b.fill_is_2d_solid,
render_instance: None, render_instance: None,
@ -1573,6 +1593,8 @@ fn style_key(
is_fill_only: bool, is_fill_only: bool,
fill_is_2d_solid: bool, fill_is_2d_solid: bool,
fill_is_3d: bool, fill_is_3d: bool,
plot_visible: bool,
hide_unselected: bool,
local_depth: Option<f32>, local_depth: Option<f32>,
) -> StyleKey { ) -> StyleKey {
StyleKey { StyleKey {
@ -1603,6 +1625,8 @@ fn style_key(
is_fill_only, is_fill_only,
fill_is_2d_solid, fill_is_2d_solid,
fill_is_3d, fill_is_3d,
plot_visible,
hide_unselected,
depth_bits: local_depth.map_or(0, f32::to_bits), depth_bits: local_depth.map_or(0, f32::to_bits),
} }
} }
@ -2014,6 +2038,8 @@ fn emit_wire(
lw.is_fill_only, lw.is_fill_only,
lw.fill_is_2d_solid, lw.fill_is_2d_solid,
lw.fill_is_3d, lw.fill_is_3d,
lw.plot_visible,
lw.hide_unselected,
local_depth, local_depth,
); );
@ -2041,6 +2067,8 @@ fn emit_wire(
lw.is_fill_only, lw.is_fill_only,
lw.fill_is_2d_solid, lw.fill_is_2d_solid,
lw.fill_is_3d, lw.fill_is_3d,
lw.plot_visible,
lw.hide_unselected,
) )
}); });
entry.local_depth = local_depth; entry.local_depth = local_depth;

View file

@ -711,6 +711,8 @@ pub(crate) fn tessellate_entity(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1063,6 +1065,8 @@ pub(crate) fn tessellate_entity(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1148,14 +1152,21 @@ pub(crate) fn tessellate_entity(
wire.render_instance = Some(instance); wire.render_instance = Some(instance);
} }
} }
if document.header.xclip_frame != 0 && polygon.len() >= 3 { let frame_mode = crate::scene::frame::mode(
wires.push(pick::xclip::frame_wire( document,
crate::scene::frame::FrameKind::Xclip,
);
if polygon.len() >= 3 {
let mut frame = pick::xclip::frame_wire(
&polygon, &polygon,
format!("{}_xclipframe", h.value()), h.value().to_string(),
ins_color, ins_color,
sel, sel,
ins_lw_px, 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, bg_color,
false, false,
); );
let frame_mode = crate::scene::frame::entity_kind(e)
.map(|kind| crate::scene::frame::mode(document, kind));
for b in &mut bases { for b in &mut bases {
b.aci = aci; b.aci = aci;
// SDF text wires carry a glyph-bounds AABB (the true text extent) set // 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() { if b.text_verts.is_empty() {
set_wire_aabb(b, aabb); 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* // Complex linetypes (with embedded shapes / text) expand the *base*
// polyline along its tangent. Text-type entities never have a complex // polyline along its tangent. Text-type entities never have a complex
// linetype assigned, so we only consult the first wire here — multi-wire // linetype assigned, so we only consult the first wire here — multi-wire
@ -1437,6 +1460,8 @@ fn lod_stub_wire(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1528,6 +1553,8 @@ fn lod_stub_wire_3d(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -127,6 +127,75 @@ pub(crate) fn points_to_ds(
(high, low) (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 /// Lift a WireModel built in a local frame (a fixed f64 origin subtracted) back
/// to absolute world coordinates, re-splitting every position — polyline points /// to absolute world coordinates, re-splitting every position — polyline points
/// and SDF glyph vertices — into double-single so it stays precise at UTM scale. /// 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(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -482,6 +553,17 @@ pub fn tessellate(
return out; return out;
} }
if let Some(wires) = point_cloud_wires(
document,
handle,
entity,
selected,
color,
line_weight_px,
) {
return wires;
}
// ── Try the kernel path first ─────────────────────────────────────────── // ── Try the kernel path first ───────────────────────────────────────────
// Relative-PDSIZE points size their glyph from the current zoom so they // Relative-PDSIZE points size their glyph from the current zoom so they
// stay a roughly constant on-screen size; otherwise the header-driven path. // stay a roughly constant on-screen size; otherwise the header-driven path.
@ -728,6 +810,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -775,6 +859,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -824,6 +910,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -854,6 +942,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -914,6 +1004,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -956,6 +1048,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -995,6 +1089,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1027,6 +1123,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1093,6 +1191,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1214,6 +1314,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: polyline_band_width(entity), world_width: polyline_band_width(entity),
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d, fill_is_3d,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1279,6 +1381,8 @@ pub fn tessellate(
fill_is_2d_solid: matches!(entity, EntityType::Solid(_)), fill_is_2d_solid: matches!(entity, EntityType::Solid(_)),
render_instance: None, render_instance: None,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
}); });
} }
@ -1287,6 +1391,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1332,6 +1438,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: polyline_band_width(entity), world_width: polyline_band_width(entity),
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1378,6 +1486,8 @@ pub fn tessellate(
taper_widths: widths, taper_widths: widths,
world_width, world_width,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -1497,6 +1607,8 @@ pub fn tessellate(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -1393,6 +1393,9 @@ impl Scene {
wipeout.v_vector = wipeout.v_vector =
context.transform.apply_rotation(source.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) = let (world_origin, mut boundary) =
Self::wipeout_boundary_2d(&wipeout); Self::wipeout_boundary_2d(&wipeout);
for clip in &context.clips { for clip in &context.clips {
@ -1409,6 +1412,16 @@ impl Scene {
if boundary.len() < 3 { if boundary.len() < 3 {
return; 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 = let selection_handle =
if tint_insert_selection && context.is_instanced() { if tint_insert_selection && context.is_instanced() {
context.root_handle context.root_handle
@ -1446,10 +1459,14 @@ impl Scene {
} else { } else {
None None
}; };
let fill_plane_boundary = Some(Arc::new(fill_plane.1));
let fill_plane = Some(fill_plane.0);
models.push(HatchModel { models.push(HatchModel {
render_instance, render_instance,
boundary: Arc::new(boundary), boundary: Arc::new(boundary),
boundary_wcs: None, boundary_wcs: None,
fill_plane,
fill_plane_boundary,
boundary_exterior: None, boundary_exterior: None,
boundary_sources: None, boundary_sources: None,
pattern: model::hatch_model::HatchPattern::Solid, pattern: model::hatch_model::HatchPattern::Solid,
@ -1474,66 +1491,113 @@ impl Scene {
pub(super) fn wipeout_boundary_2d( pub(super) fn wipeout_boundary_2d(
wo: &acadrust::entities::Wipeout, wo: &acadrust::entities::Wipeout,
) -> ([f64; 2], Vec<[f32; 2]>) { ) -> ([f64; 2], Vec<[f32; 2]>) {
use acadrust::entities::WipeoutClipType;
let origin = [wo.insertion_point.x, wo.insertion_point.y]; let origin = [wo.insertion_point.x, wo.insertion_point.y];
let plane = cadkernel::space::Plane::from_axes(
let is_polygon = wo.clipping_enabled [wo.insertion_point.x, wo.insertion_point.y, wo.insertion_point.z],
&& wo.clip_boundary_vertices.len() >= 3 [wo.u_vector.x, wo.u_vector.y, wo.u_vector.z],
&& matches!(wo.clip_type, WipeoutClipType::Polygonal); [wo.v_vector.x, wo.v_vector.y, wo.v_vector.z],
);
if is_polygon { let boundary = Self::wipeout_local_boundary(wo)
// DXF clip vertices live in image-pixel space, centred on the .into_iter()
// image (range −size/2 … +size/2). Image-bottom-left → insertion, .map(|point| {
// image-y-axis points DOWN (per the DXF "v_vector points down the if point[0].is_finite() && point[1].is_finite() {
// image" convention), so map: let offset = plane.vector_at(point);
// x_off = (clip.x + size.x/2) × u_vec [offset[0] as f32, offset[1] as f32]
// y_off = (size.y/2 − clip.y) × v_vec ← y flipped } else {
// Offsets are relative to `origin` (the insertion point). [f32::NAN; 2]
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);
} }
} })
(origin, poly) .collect();
} else { (origin, boundary)
// 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; fn wipeout_local_boundary(wo: &acadrust::entities::Wipeout) -> Vec<[f64; 2]> {
let vx = (wo.v_vector.x * wo.size.y) as f32; use acadrust::entities::{WipeoutClipMode, WipeoutClipType};
let vy = (wo.v_vector.y * wo.size.y) as f32; let mut outer = vec![
// Close the loop (repeat corner 0): the GPU `in_polygon` ray-cast [0.0, 0.0],
// walks sequential vertex pairs and never wraps last→first, so an [wo.size.x, 0.0],
// unclosed quad leaves the v3→v0 edge untested and the solid mask [wo.size.x, wo.size.y],
// bleeds past the boundary — same reason the polygon branch closes. [0.0, wo.size.y],
( [0.0, 0.0],
origin, ];
vec![ if !wo.clipping_enabled
[0.0, 0.0], || wo.clip_boundary_vertices.len() < 3
[ux, uy], || !matches!(wo.clip_type, WipeoutClipType::Polygonal)
[ux + vx, uy + vy], {
[vx, vy], return outer;
[0.0, 0.0],
],
)
} }
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( pub(crate) fn hatch_model_from_dxf(
@ -1817,6 +1881,8 @@ impl Scene {
render_instance: None, render_instance: None,
boundary: std::sync::Arc::new(boundary_f32), boundary: std::sync::Arc::new(boundary_f32),
boundary_wcs: None, boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: Some(std::sync::Arc::new(boundary_exterior)), boundary_exterior: Some(std::sync::Arc::new(boundary_exterior)),
boundary_sources: Some(std::sync::Arc::new(boundary_sources)), boundary_sources: Some(std::sync::Arc::new(boundary_sources)),
pattern, pattern,
@ -2133,6 +2199,8 @@ impl Scene {
render_instance: None, render_instance: None,
boundary: std::sync::Arc::new(boundary), boundary: std::sync::Arc::new(boundary),
boundary_wcs: None, boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: None, boundary_exterior: None,
boundary_sources: None, boundary_sources: None,
pattern: model::hatch_model::HatchPattern::Solid, pattern: model::hatch_model::HatchPattern::Solid,

211
src/scene/frame.rs Normal file
View file

@ -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(_)
)
),
}
}

View file

@ -9,6 +9,7 @@ pub mod annotative;
pub mod cache; pub mod cache;
pub mod convert; pub mod convert;
pub mod creation_style; pub mod creation_style;
pub(crate) mod frame;
pub mod model; pub mod model;
pub mod pick; pub mod pick;
pub mod pipeline; pub mod pipeline;
@ -3210,6 +3211,8 @@ impl Scene {
world_origin: [0.0, 0.0], world_origin: [0.0, 0.0],
boundary: Arc::new(vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]]), boundary: Arc::new(vec![[x0, y0], [x1, y0], [x1, y1], [x0, y1], [x0, y0]]),
boundary_wcs: None, boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: None, boundary_exterior: None,
boundary_sources: None, boundary_sources: None,
pattern: crate::scene::model::hatch_model::HatchPattern::Solid, pattern: crate::scene::model::hatch_model::HatchPattern::Solid,

View file

@ -165,6 +165,13 @@ pub enum HatchPattern {
/// (anchor-relative), so real coordinates never approach it. /// (anchor-relative), so real coordinates never approach it.
pub const GPU_BOUNDARY_SEP: f32 = 1.0e30; 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. /// A hatched region defined by a closed polygon boundary.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct HatchModel { pub struct HatchModel {
@ -196,6 +203,9 @@ pub struct HatchModel {
/// rebuilt from a DXF entity — `add_hatch` then reconstructs the persisted /// rebuilt from a DXF entity — `add_hatch` then reconstructs the persisted
/// vertices from `boundary` + `world_origin` instead. /// vertices from `boundary` + `world_origin` instead.
pub boundary_wcs: Option<Arc<Vec<[f64; 2]>>>, 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. /// Per-ring DXF role, aligned with the NaN-separated boundary paths.
pub boundary_exterior: Option<Arc<Vec<bool>>>, pub boundary_exterior: Option<Arc<Vec<bool>>>,
/// Source entity handles for each boundary ring. /// Source entity handles for each boundary ring.

View file

@ -146,6 +146,10 @@ pub struct WireModel {
/// mirroring how exploded block fills seed their depth, so bands and fills /// mirroring how exploded block fills seed their depth, so bands and fills
/// from the same block interleave by the block's internal draw order. /// from the same block interleave by the block's internal draw order.
pub depth_override: Option<f32>, 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 / /// `true` when [`fill_tris`] is a real 3-D surface (PolyfaceMesh /
/// PolygonMesh face) that must render with hidden-surface depth and only in /// PolygonMesh face) that must render with hidden-surface depth and only in
/// shaded modes. `false` for a flat 2-D overlay fill (SOLID arrowhead, /// shaded modes. `false` for a flat 2-D overlay fill (SOLID arrowhead,
@ -208,6 +212,8 @@ impl WireModel {
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -502,6 +508,8 @@ impl Default for WireModel {
fill_tris: Vec::new(), fill_tris: Vec::new(),
fill_tris_low: Vec::new(), fill_tris_low: Vec::new(),
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -260,6 +260,8 @@ pub fn frame_wire(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -948,10 +948,8 @@ impl Pipeline {
); );
// ── Hatch pipeline ───────────────────────────────────────────────── // ── Hatch pipeline ─────────────────────────────────────────────────
// binding 0 (HatchUniforms) is read by the vertex shader too — it // Binding 0 carries the fill plane; boundary and family data are
// pulls `origin` to undo the CPU-side hatch-local pre-shift when // fragment-only.
// computing clip position. bindings 1 (Boundary) and 2
// (FamilyBatch) stay fragment-only.
let hatch_entry = |binding: u32, vis: wgpu::ShaderStages| wgpu::BindGroupLayoutEntry { let hatch_entry = |binding: u32, vis: wgpu::ShaderStages| wgpu::BindGroupLayoutEntry {
binding, binding,
visibility: vis, visibility: vis,
@ -2364,6 +2362,10 @@ impl Pipeline {
let mut block_wires: Vec<&WireModel> = Vec::new(); let mut block_wires: Vec<&WireModel> = Vec::new();
let mut i = 0; let mut i = 0;
while i < wires.len() { while i < wires.len() {
if !wires[i].display_visible {
i += 1;
continue;
}
if wires[i].render_instance.is_some() { if wires[i].render_instance.is_some() {
block_wires.push(&wires[i]); block_wires.push(&wires[i]);
i += 1; i += 1;
@ -2372,6 +2374,7 @@ impl Pipeline {
let mesh_edge = is_mesh_edge(&wires[i]); let mesh_edge = is_mesh_edge(&wires[i]);
let mut j = i + 1; let mut j = i + 1;
while j < wires.len() while j < wires.len()
&& wires[j].display_visible
&& wires[j].render_instance.is_none() && wires[j].render_instance.is_none()
&& is_mesh_edge(&wires[j]) == mesh_edge && is_mesh_edge(&wires[j]) == mesh_edge
{ {

View file

@ -433,6 +433,9 @@ pub fn upload_block_vertex_refs(
let mut slots = rustc_hash::FxHashMap::default(); let mut slots = rustc_hash::FxHashMap::default();
let mut groups: Vec<Vec<&crate::scene::model::wire_model::WireModel>> = Vec::new(); let mut groups: Vec<Vec<&crate::scene::model::wire_model::WireModel>> = Vec::new();
for &wire in wires { for &wire in wires {
if tint.is_none() && !wire.display_visible {
continue;
}
let Some(instance) = wire.render_instance else { let Some(instance) = wire.render_instance else {
continue; continue;
}; };

View file

@ -9,11 +9,13 @@
// a limit on visible hatch fills. // a limit on visible hatch fills.
// //
// Group 1 bindings per fill: // 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 1 — BoundaryData (16384 bytes) boundary polygon vertices
// binding 2 — FamilyBatchData (1296 bytes) up to 16 line families + 128 dash values (unused for wipeouts) // 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;
use iced::wgpu::util::DeviceExt; use iced::wgpu::util::DeviceExt;
@ -48,10 +50,10 @@ impl HatchVertex {
#[repr(C)] #[repr(C)]
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] #[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
pub struct WipeoutPlacement { pub struct WipeoutPlacement {
pub translation: [f32; 2], pub translation: [f32; 3],
pub translation_low: [f32; 2], pub translation_low: [f32; 3],
pub draw_depth: f32, pub draw_depth: f32,
pub _pad: [f32; 3], pub _pad: f32,
} }
impl WipeoutPlacement { impl WipeoutPlacement {
@ -60,9 +62,9 @@ impl WipeoutPlacement {
array_stride: std::mem::size_of::<Self>() as u64, array_stride: std::mem::size_of::<Self>() as u64,
step_mode: wgpu::VertexStepMode::Instance, step_mode: wgpu::VertexStepMode::Instance,
attributes: &[ attributes: &[
wgpu::VertexAttribute { offset: 0, shader_location: 1, format: wgpu::VertexFormat::Float32x2 }, wgpu::VertexAttribute { offset: 0, shader_location: 1, format: wgpu::VertexFormat::Float32x3 },
wgpu::VertexAttribute { offset: 8, shader_location: 2, format: wgpu::VertexFormat::Float32x2 }, wgpu::VertexAttribute { offset: 12, shader_location: 2, format: wgpu::VertexFormat::Float32x3 },
wgpu::VertexAttribute { offset: 16, shader_location: 3, format: wgpu::VertexFormat::Float32 }, wgpu::VertexAttribute { offset: 24, shader_location: 3, format: wgpu::VertexFormat::Float32 },
], ],
} }
} }
@ -70,7 +72,7 @@ impl WipeoutPlacement {
// ── Uniform structs ─────────────────────────────────────────────────────── // ── Uniform structs ───────────────────────────────────────────────────────
/// Per-hatch parameters (binding 0). Must be 96 bytes (16-byte aligned). /// Per-fill parameters (binding 0).
/// ///
/// `mode` encoding: /// `mode` encoding:
/// 0 → Pattern (families in FamilyBatchData) /// 0 → Pattern (families in FamilyBatchData)
@ -89,18 +91,15 @@ pub struct HatchUniformData {
pub grad_sin: f32, // 52: gradient direction sin pub grad_sin: f32, // 52: gradient direction sin
pub grad_min: f32, // 56: gradient proj_min pub grad_min: f32, // 56: gradient proj_min
pub grad_range: f32, // 60: gradient proj_range pub grad_range: f32, // 60: gradient proj_range
pub origin: [f32; 2], // 64: hatch-local origin (boundary AABB centre), pub origin: [f32; 2], // 64: reserved pattern origin
// // high half of the double-single anchor. pub origin_low: [f32; 2], // 72: reserved low residual
// // 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 draw_depth: f32, // 80: signed (-1,1) draw-order bias pub draw_depth: f32, // 80: signed (-1,1) draw-order bias
pub _pad: [f32; 3], // 84..96 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`. /// Boundary polygon (binding 1). Matches WGSL `Boundary`.
#[repr(C)] #[repr(C)]
@ -194,13 +193,22 @@ impl WipeoutGpu {
}; };
// ── Bounding box ───────────────────────────────────────────────── // ── 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) = ( let (mut min_x, mut max_x, mut min_y, mut max_y) = (
f32::INFINITY, f32::INFINITY,
f32::NEG_INFINITY, f32::NEG_INFINITY,
f32::INFINITY, f32::INFINITY,
f32::NEG_INFINITY, f32::NEG_INFINITY,
); );
for &[x, y] in model.boundary.iter() { for &[x, y] in gpu_boundary {
if !x.is_finite() || !y.is_finite() { if !x.is_finite() || !y.is_finite() {
continue; continue;
} }
@ -218,68 +226,16 @@ impl WipeoutGpu {
_ => 5.0, _ => 5.0,
}; };
let diag = ((max_x - min_x).powi(2) + (max_y - min_y).powi(2)).sqrt(); 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); let pad = if mode == 1 {
// ── Pattern phase anchor. 0.0
// } else {
// Boundary verts are already stored as small f32 offsets from (diag * 0.8 + max_spacing * 2.0 * model.scale).max(1.0)
// `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 (x0, x1, y0, y1) = ( let (x0, x1, y0, y1) = (
min_x + drift[0] - pad, min_x - pad,
max_x + drift[0] + pad, max_x + pad,
min_y + drift[1] - pad, min_y - pad,
max_y + drift[1] + pad, max_y + pad,
); );
let quad = [ let quad = [
@ -316,11 +272,9 @@ impl WipeoutGpu {
// ── Gradient: projection range (in snapped-local space) ────────── // ── Gradient: projection range (in snapped-local space) ──────────
let (grad_min, grad_range) = if mode == 2 { let (grad_min, grad_range) = if mode == 2 {
let projs: Vec<f32> = model let projs: Vec<f32> = gpu_boundary.iter()
.boundary
.iter()
.filter(|v| v[0].is_finite() && v[1].is_finite()) .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(); .collect();
if projs.is_empty() { if projs.is_empty() {
(0.0, 1.0) (0.0, 1.0)
@ -334,7 +288,11 @@ impl WipeoutGpu {
}; };
// ── HatchUniformData ───────────────────────────────────────────── // ── 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 { let uniform_data = HatchUniformData {
color: model.color, color: model.color,
color2, color2,
@ -346,30 +304,46 @@ impl WipeoutGpu {
grad_sin, grad_sin,
grad_min, grad_min,
grad_range, grad_range,
origin: [origin[0] as f32, origin[1] as f32], origin: [0.0; 2],
origin_low: [ origin_low: [0.0; 2],
(origin[0] - origin[0] as f32 as f64) as f32,
(origin[1] - origin[1] as f32 as f64) as f32,
],
draw_depth: 0.0, draw_depth: 0.0,
_pad: [0.0; 3], _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) ──────────────────────── // ── BoundaryData (in snapped-local space) ────────────────────────
let mut boundary_data = BoundaryData { let mut boundary_data = BoundaryData {
verts: [[0.0; 4]; MAX_HATCH_BOUNDARY_VERTS], verts: [[0.0; 4]; MAX_HATCH_BOUNDARY_VERTS],
}; };
for (i, &[x, y]) in model for (i, &[x, y]) in gpu_boundary.iter()
.boundary
.iter()
.take(MAX_HATCH_BOUNDARY_VERTS) .take(MAX_HATCH_BOUNDARY_VERTS)
.enumerate() .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() { 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 { } else {
let s = crate::scene::model::hatch_model::GPU_BOUNDARY_SEP; let s = crate::scene::model::hatch_model::GPU_BOUNDARY_SEP;
boundary_data.verts[i] = [s, s, 0.0, 0.0]; boundary_data.verts[i] = [s, s, 0.0, 0.0];
@ -433,16 +407,15 @@ impl WipeoutGpu {
let translation = model let translation = model
.render_instance .render_instance
.map_or([0.0; 3], |instance| instance.translation); .map_or([0.0; 3], |instance| instance.translation);
let delta = [translation[0] - base[0], translation[1] - base[1]]; let delta = std::array::from_fn(|index| translation[index] - base[index]);
let high = [delta[0] as f32, delta[1] as f32]; let high = delta.map(|value| value as f32);
WipeoutPlacement { WipeoutPlacement {
translation: high, translation: high,
translation_low: [ translation_low: std::array::from_fn(|index| {
(delta[0] - high[0] as f64) as f32, (delta[index] - high[index] as f64) as f32
(delta[1] - high[1] as f64) as f32, }),
],
draw_depth: model.draw_depth, draw_depth: model.draw_depth,
_pad: [0.0; 3], _pad: 0.0,
} }
}) })
.collect(); .collect();
@ -451,19 +424,38 @@ impl WipeoutGpu {
contents: bytemuck::cast_slice(&placements), contents: bytemuck::cast_slice(&placements),
usage: wgpu::BufferUsages::VERTEX, usage: wgpu::BufferUsages::VERTEX,
}); });
let ox = model.world_origin[0] as f32; let mut world_bounds = [
let oy = model.world_origin[1] as f32; f32::INFINITY,
let world_aabb = if min_x.is_finite() && min_y.is_finite() { 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]; let mut aabb = [f32::INFINITY, f32::INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY];
for placement in &placements { for placement in &placements {
aabb[0] = aabb[0].min(min_x + ox + placement.translation[0]); aabb[0] = aabb[0].min(world_bounds[0] + ox + placement.translation[0]);
aabb[1] = aabb[1].min(min_y + oy + placement.translation[1]); aabb[1] = aabb[1].min(world_bounds[1] + oy + placement.translation[1]);
aabb[2] = aabb[2].max(max_x + ox + placement.translation[0]); aabb[2] = aabb[2].max(world_bounds[2] + ox + placement.translation[0]);
aabb[3] = aabb[3].max(max_y + oy + placement.translation[1]); aabb[3] = aabb[3].max(world_bounds[3] + oy + placement.translation[1]);
} }
aabb aabb
} else { } else {
[min_x, min_y, max_x, max_y] world_bounds
}; };
Self { Self {
@ -482,7 +474,7 @@ impl WipeoutGpu {
// ── Helpers ─────────────────────────────────────────────────────────────── // ── Helpers ───────────────────────────────────────────────────────────────
/// Perpendicular spacing between adjacent parallel lines. /// 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 { fn perp_spacing(f: &PatFamily) -> f32 {
f.dy f.dy
} }
@ -519,7 +511,7 @@ fn build_family_batch(pattern: &HatchPattern) -> FamilyBatchData {
let angle_r = family.angle_deg.to_radians(); let angle_r = family.angle_deg.to_radians();
let cos_a = angle_r.cos(); let cos_a = angle_r.cos();
let sin_a = angle_r.sin(); 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 perp_step = family.dy;
let along_step = family.dx; let along_step = family.dx;
let line_width = 0.0_f32; // unused: shader uses screen-space derivative for 1px lines let line_width = 0.0_f32; // unused: shader uses screen-space derivative for 1px lines

View file

@ -655,6 +655,9 @@ impl BlockWireGpu {
rustc_hash::FxHashMap::default(); rustc_hash::FxHashMap::default();
let mut groups: Vec<(bool, Vec<&WireModel>)> = Vec::new(); let mut groups: Vec<(bool, Vec<&WireModel>)> = Vec::new();
for &wire in wires { for &wire in wires {
if color_override.is_none() && !wire.display_visible {
continue;
}
let Some(instance) = wire.render_instance else { let Some(instance) = wire.render_instance else {
continue; continue;
}; };

View file

@ -158,6 +158,8 @@ impl Scene {
world_origin: origin, world_origin: origin,
boundary: std::sync::Arc::new(boundary), boundary: std::sync::Arc::new(boundary),
boundary_wcs: None, boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: None, boundary_exterior: None,
boundary_sources: None, boundary_sources: None,
pattern: HatchPattern::Solid, pattern: HatchPattern::Solid,

View file

@ -409,8 +409,8 @@ impl Scene {
} else { } else {
0.0 0.0
}; };
let project = |x: f64, y: f64| -> Option<[f32; 2]> { let project_3d = |point: [f64; 3]| -> Option<[f32; 2]> {
let delta = glam::DVec3::new(x, y, 0.0) - camera.target; let delta = glam::DVec3::from(point) - camera.target;
let u = delta.dot(view_right.as_dvec3()); let u = delta.dot(view_right.as_dvec3());
let v = delta.dot(view_up.as_dvec3()); let v = delta.dot(view_up.as_dvec3());
let factor = if perspective { let factor = if perspective {
@ -427,6 +427,7 @@ impl Scene {
(center_y + v * factor * viewport_scale) as f32, (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> = let frozen: rustc_hash::FxHashSet<Handle> =
viewport.frozen_layers.iter().copied().collect(); viewport.frozen_layers.iter().copied().collect();
@ -481,7 +482,7 @@ impl Scene {
continue; continue;
} }
if let Some(hatch) = 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); projected_hatches.push(hatch);
} }
@ -495,7 +496,7 @@ impl Scene {
false, false,
) { ) {
if let Some(wipeout) = 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); projected_wipeouts.push(wipeout);
} }
@ -753,7 +754,7 @@ fn project_plot_fill<F>(
ymax: f32, ymax: f32,
) -> Option<HatchModel> ) -> Option<HatchModel>
where where
F: Fn(f64, f64) -> Option<[f32; 2]>, F: Fn([f64; 3]) -> Option<[f32; 2]>,
{ {
let mut output = Vec::new(); let mut output = Vec::new();
let mut ring = Vec::new(); let mut ring = Vec::new();
@ -778,15 +779,35 @@ where
} }
output.extend(clipped); output.extend(clipped);
}; };
for &[x, y] in fill.boundary.iter() { if let (Some(plane), Some(boundary)) = (fill.fill_plane, fill.fill_plane_boundary.as_deref()) {
if x.is_nan() || y.is_nan() { let plane = cadkernel::space::Plane::from_axes(
flush_ring(&mut ring, &mut output); plane.origin,
continue; 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; } else {
let absolute_y = fill.world_origin[1] + y as f64; for &[x, y] in fill.boundary.iter() {
if let Some(point) = project(absolute_x, absolute_y) { if x.is_nan() || y.is_nan() {
ring.push(point); 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); flush_ring(&mut ring, &mut output);
@ -796,6 +817,8 @@ where
fill.world_origin = [0.0, 0.0]; fill.world_origin = [0.0, 0.0];
fill.boundary = std::sync::Arc::new(output); fill.boundary = std::sync::Arc::new(output);
fill.boundary_wcs = None; fill.boundary_wcs = None;
fill.fill_plane = None;
fill.fill_plane_boundary = None;
Some(fill) Some(fill)
} }

View file

@ -312,6 +312,8 @@ pub fn apply_along(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,
@ -356,6 +358,8 @@ pub fn apply_along(
taper_widths: Vec::new(), taper_widths: Vec::new(),
world_width: 0.0, world_width: 0.0,
depth_override: None, depth_override: None,
display_visible: true,
plot_visible: true,
fill_is_3d: false, fill_is_3d: false,
fill_is_2d_solid: false, fill_is_2d_solid: false,
render_instance: None, render_instance: None,

View file

@ -2925,6 +2925,9 @@ impl Scene {
} }
let mut out: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new(); let mut out: Vec<crate::scene::pipeline::text_gpu::TextVertex> = Vec::new();
for w in wires { for w in wires {
if !w.display_visible {
continue;
}
if w.render_instance.is_some() { if w.render_instance.is_some() {
continue; continue;
} }

View file

@ -46,15 +46,15 @@ struct HatchUniforms {
grad_sin: f32, // 52: gradient direction sin grad_sin: f32, // 52: gradient direction sin
grad_min: f32, // 56: gradient proj_min grad_min: f32, // 56: gradient proj_min
grad_range: f32, // 60: gradient proj_range grad_range: f32, // 60: gradient proj_range
origin: vec2<f32>, // 64: hatch-local pattern origin (boundary origin: vec2<f32>, // 64: reserved pattern origin
// // centre). Pattern math runs against origin_low: vec2<f32>, // 72: reserved low residual
// // `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)
draw_depth: f32, // 80: signed (-1,1) draw-order bias; 0 = neutral draw_depth: f32, // 80: signed (-1,1) draw-order bias; 0 = neutral
_pad0: f32, // 84 _pad0: f32, // 84
_pad1: vec2<f32>, // 88 _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; @group(1) @binding(0) var<uniform> h: HatchUniforms;
@ -91,8 +91,8 @@ struct FamilyBatch {
struct VIn { struct VIn {
@location(0) pos: vec3<f32>, @location(0) pos: vec3<f32>,
@location(1) translation: vec2<f32>, @location(1) translation: vec3<f32>,
@location(2) translation_low: vec2<f32>, @location(2) translation_low: vec3<f32>,
@location(3) draw_depth: f32, @location(3) draw_depth: f32,
} }
struct VOut { struct VOut {
@ -102,22 +102,9 @@ struct VOut {
@vertex fn vs_main(v: VIn) -> VOut { @vertex fn vs_main(v: VIn) -> VOut {
var o: VOut; var o: VOut;
// v.pos comes in pre-shifted to hatch-local space (CPU subtracted let local_world = h.plane_x.xyz * v.pos.x + h.plane_y.xyz * v.pos.y;
// h.origin from the quad). Add origin back inside the view_proj let hi = h.plane_origin_high.xyz + v.translation - u.eye_high;
// multiply to land at the correct world-space clip position. The let lo = local_world + h.plane_origin_low.xyz + v.translation_low - u.eye_low;
// 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);
o.clip = u.view_rot * vec4<f32>(hi + lo, 1.0); o.clip = u.view_rot * vec4<f32>(hi + lo, 1.0);
// Draw-order bias (see wire.wgsl): the mask erases what draws BELOW its // 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, // 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 // 4. Pattern coordinates are fill-local.
// 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.
let cos_off = cos(h.angle_offset); let cos_off = cos(h.angle_offset);
let sin_off = sin(h.angle_offset); let sin_off = sin(h.angle_offset);
for (var i = 0u; i < f.n_families; i++) { for (var i = 0u; i < f.n_families; i++) {

View file

@ -130,6 +130,8 @@ impl canvas::Program<Message> for HatchPatternPreview {
[pad, bounds.height - pad], [pad, bounds.height - pad],
]), ]),
boundary_wcs: None, boundary_wcs: None,
fill_plane: None,
fill_plane_boundary: None,
boundary_exterior: None, boundary_exterior: None,
boundary_sources: None, boundary_sources: None,
pattern: self.pattern.clone(), pattern: self.pattern.clone(),