forked from 1b-agent/opencadstudio-upstream
Merge remote-tracking branch 'origin/main' into codex/extrude-taper-history
This commit is contained in:
commit
5e6452f1b5
53 files changed
+817
-86
No files matched your search
Generated
+4
-4
@@ -4205,7 +4205,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadcodec"
|
name = "opencadcodec"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=ab2eecd#ab2eecdbffc31120b5ad6d899f6fc67cf21ede39"
|
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=c9ffcac#c9ffcac5ecdc7a504cc4a7e3ecb52ac9d587c8bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags 2.13.1",
|
"bitflags 2.13.1",
|
||||||
@@ -4228,7 +4228,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadgraph"
|
name = "opencadgraph"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=194e619#194e6192c9eca5c916371e7434b9fde18911aa6a"
|
source = "git+https://github.com/HakanSeven12/opencadgraph.git?rev=7f6aae7#7f6aae7aff6b7de87cc1dcdb38a06c3e64ae1922"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"opencadkernel",
|
"opencadkernel",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4237,7 +4237,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadkernel"
|
name = "opencadkernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=bf44e68#bf44e6856f2582eb5df5038650b6a6910eb9dac5"
|
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=99b6b48#99b6b4890704d300596ba502b5a1c8ec1eefa954"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
"opencadcodec",
|
"opencadcodec",
|
||||||
@@ -4248,7 +4248,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadkernel-constraints"
|
name = "opencadkernel-constraints"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=bf44e68#bf44e6856f2582eb5df5038650b6a6910eb9dac5"
|
source = "git+https://github.com/HakanSeven12/opencadkernel.git?rev=99b6b48#99b6b4890704d300596ba502b5a1c8ec1eefa954"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"nalgebra",
|
"nalgebra",
|
||||||
]
|
]
|
||||||
|
|||||||
+4
-4
@@ -34,10 +34,10 @@ rfd = "0.17"
|
|||||||
clap = { version = "4", features = ["derive", "string"] }
|
clap = { version = "4", features = ["derive", "string"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ab2eecd", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", features = ["serde"] }
|
||||||
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "bf44e68", features = ["acis", "offset"] }
|
kernel = { package = "opencadkernel", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "99b6b48", features = ["acis", "offset"] }
|
||||||
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "bf44e68" }
|
kernel-constraints = { package = "opencadkernel-constraints", git = "https://github.com/HakanSeven12/opencadkernel.git", rev = "99b6b48" }
|
||||||
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "194e619" }
|
graph = { package = "opencadgraph", git = "https://github.com/HakanSeven12/opencadgraph.git", rev = "7f6aae7" }
|
||||||
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"] }
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ serde_json = { version = "1", optional = true }
|
|||||||
# Pulled in only by the `host` feature, which adds the `opencadcodec`-typed
|
# Pulled in only by the `host` feature, which adds the `opencadcodec`-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.
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ab2eecd", optional = true, features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", 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 }
|
||||||
@@ -39,7 +39,7 @@ serde_json = "1"
|
|||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
cargo-lock = "11"
|
cargo-lock = "11"
|
||||||
# opencadcodec is scanned at build time to generate the embedded type registry.
|
# opencadcodec is scanned at build time to generate the embedded type registry.
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ab2eecd", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
@@ -8,7 +8,7 @@ publish = false
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "ab2eecd", features = ["serde"] }
|
codec = { package = "opencadcodec", git = "https://github.com/HakanSeven12/opencadcodec.git", rev = "c9ffcac", 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"
|
||||||
|
|||||||
@@ -1451,6 +1451,7 @@ common =
|
|||||||
.zoom = التكبير
|
.zoom = التكبير
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = عكس التكبير بعجلة الفأرة (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = عكس التكبير بعجلة الفأرة (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = لوحة اللمس: التمرير يحرك العرض، Ctrl+تمرير أو القرص يكبّر
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = لوحة اللمس: التمرير يحرك العرض، Ctrl+تمرير أو القرص يكبّر
|
||||||
|
.interface-scale = مقياس الواجهة
|
||||||
.zoom-factor = معامل التكبير
|
.zoom-factor = معامل التكبير
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = مقدار التكبير لكل درجة من العجلة (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = مقدار التكبير لكل درجة من العجلة (ZOOMFACTOR).
|
||||||
.text-and-dimensions = النص والأبعاد
|
.text-and-dimensions = النص والأبعاد
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = Мащабиране
|
.zoom = Мащабиране
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Обръщане на мащабирането с колелцето (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Обръщане на мащабирането с колелцето (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Тъчпад: превъртането мести изгледа, Ctrl+превъртане или щипване мащабира
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Тъчпад: превъртането мести изгледа, Ctrl+превъртане или щипване мащабира
|
||||||
|
.interface-scale = Мащаб на интерфейса
|
||||||
.zoom-factor = Коефициент на мащабиране
|
.zoom-factor = Коефициент на мащабиране
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Колко мащабира едно щракване на колелцето (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Колко мащабира едно щракване на колелцето (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Текст и размери
|
.text-and-dimensions = Текст и размери
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Obrátit zoom kolečkem myši (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Obrátit zoom kolečkem myši (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: posun posouvá pohled, Ctrl+posun nebo sevření zvětšuje
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: posun posouvá pohled, Ctrl+posun nebo sevření zvětšuje
|
||||||
|
.interface-scale = Měřítko rozhraní
|
||||||
.zoom-factor = Faktor zoomu
|
.zoom-factor = Faktor zoomu
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = O kolik přiblíží jeden zub kolečka (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = O kolik přiblíží jeden zub kolečka (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Text a kóty
|
.text-and-dimensions = Text a kóty
|
||||||
|
|||||||
@@ -1439,6 +1439,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Mausradzoom umkehren (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Mausradzoom umkehren (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: Scrollen verschiebt, Strg+Scrollen oder Zusammenziehen zoomt
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: Scrollen verschiebt, Strg+Scrollen oder Zusammenziehen zoomt
|
||||||
|
.interface-scale = Oberflächenskalierung
|
||||||
.zoom-factor = Zoomfaktor
|
.zoom-factor = Zoomfaktor
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Wie weit eine Radrastung zoomt (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Wie weit eine Radrastung zoomt (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Text und Bemaßung
|
.text-and-dimensions = Text und Bemaßung
|
||||||
|
|||||||
@@ -1440,6 +1440,7 @@ common =
|
|||||||
.zoom = Ζουμ
|
.zoom = Ζουμ
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Αντιστροφή ζουμ με τον τροχό (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Αντιστροφή ζουμ με τον τροχό (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Επιφάνεια αφής: η κύλιση μετακινεί, Ctrl+κύλιση ή τσίμπημα κάνει ζουμ
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Επιφάνεια αφής: η κύλιση μετακινεί, Ctrl+κύλιση ή τσίμπημα κάνει ζουμ
|
||||||
|
.interface-scale = Κλίμακα διεπαφής
|
||||||
.zoom-factor = Συντελεστής ζουμ
|
.zoom-factor = Συντελεστής ζουμ
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Πόσο ζουμάρει ένα σκαλί του τροχού (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Πόσο ζουμάρει ένα σκαλί του τροχού (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Κείμενο και διαστάσεις
|
.text-and-dimensions = Κείμενο και διαστάσεις
|
||||||
|
|||||||
@@ -1440,6 +1440,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Reverse mouse wheel zoom (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Reverse mouse wheel zoom (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: scroll pans, Ctrl+scroll or pinch zooms
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: scroll pans, Ctrl+scroll or pinch zooms
|
||||||
|
.interface-scale = Interface scale
|
||||||
.zoom-factor = Zoom factor
|
.zoom-factor = Zoom factor
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = How far one wheel notch zooms (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = How far one wheel notch zooms (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Text and Dimensions
|
.text-and-dimensions = Text and Dimensions
|
||||||
|
|||||||
@@ -1439,6 +1439,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Invertir el zoom de la rueda del ratón (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Invertir el zoom de la rueda del ratón (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Panel táctil: el desplazamiento encuadra, Ctrl+desplazamiento o pellizco hace zoom
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Panel táctil: el desplazamiento encuadra, Ctrl+desplazamiento o pellizco hace zoom
|
||||||
|
.interface-scale = Escala de la interfaz
|
||||||
.zoom-factor = Factor de zoom
|
.zoom-factor = Factor de zoom
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Cuánto amplía un paso de la rueda (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Cuánto amplía un paso de la rueda (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Texto y acotación
|
.text-and-dimensions = Texto y acotación
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = Zoomaus
|
.zoom = Zoomaus
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Käännä hiiren rullan zoomaus (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Käännä hiiren rullan zoomaus (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Kosketuslevy: vieritys panoroi, Ctrl+vieritys tai nipistys zoomaa
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Kosketuslevy: vieritys panoroi, Ctrl+vieritys tai nipistys zoomaa
|
||||||
|
.interface-scale = Käyttöliittymän skaalaus
|
||||||
.zoom-factor = Zoomauskerroin
|
.zoom-factor = Zoomauskerroin
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Kuinka paljon yksi rullan pykälä zoomaa (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Kuinka paljon yksi rullan pykälä zoomaa (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Teksti ja mitoitus
|
.text-and-dimensions = Teksti ja mitoitus
|
||||||
|
|||||||
@@ -1439,6 +1439,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Inverser le zoom à la molette (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Inverser le zoom à la molette (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Pavé tactile : le défilement déplace, Ctrl+défilement ou pincement zoome
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Pavé tactile : le défilement déplace, Ctrl+défilement ou pincement zoome
|
||||||
|
.interface-scale = Échelle de l'interface
|
||||||
.zoom-factor = Facteur de zoom
|
.zoom-factor = Facteur de zoom
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = De combien zoome un cran de molette (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = De combien zoome un cran de molette (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Texte et cotation
|
.text-and-dimensions = Texte et cotation
|
||||||
|
|||||||
@@ -1427,6 +1427,7 @@ common =
|
|||||||
.zoom = ज़ूम
|
.zoom = ज़ूम
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = माउस व्हील ज़ूम उलटें (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = माउस व्हील ज़ूम उलटें (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = टचपैड: स्क्रॉल पैन करता है, Ctrl+स्क्रॉल या पिंच ज़ूम करता है
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = टचपैड: स्क्रॉल पैन करता है, Ctrl+स्क्रॉल या पिंच ज़ूम करता है
|
||||||
|
.interface-scale = इंटरफ़ेस स्केल
|
||||||
.zoom-factor = ज़ूम गुणक
|
.zoom-factor = ज़ूम गुणक
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = व्हील का एक पायदान कितना ज़ूम करता है (ZOOMFACTOR)।
|
.how-far-one-wheel-notch-zooms-zoomfactor = व्हील का एक पायदान कितना ज़ूम करता है (ZOOMFACTOR)।
|
||||||
.text-and-dimensions = पाठ और आयाम
|
.text-and-dimensions = पाठ और आयाम
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = Nagyítás
|
.zoom = Nagyítás
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Egérgörgős nagyítás megfordítása (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Egérgörgős nagyítás megfordítása (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Érintőpad: a görgetés mozgat, a Ctrl+görgetés vagy csippentés nagyít
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Érintőpad: a görgetés mozgat, a Ctrl+görgetés vagy csippentés nagyít
|
||||||
|
.interface-scale = Felület mérete
|
||||||
.zoom-factor = Nagyítási tényező
|
.zoom-factor = Nagyítási tényező
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Mennyit nagyít egy görgőkattanás (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Mennyit nagyít egy görgőkattanás (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Szöveg és méretezés
|
.text-and-dimensions = Szöveg és méretezés
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Inverti lo zoom con la rotellina (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Inverti lo zoom con la rotellina (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: lo scorrimento sposta la vista, Ctrl+scorrimento o pizzico esegue lo zoom
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: lo scorrimento sposta la vista, Ctrl+scorrimento o pizzico esegue lo zoom
|
||||||
|
.interface-scale = Scala dell'interfaccia
|
||||||
.zoom-factor = Fattore di zoom
|
.zoom-factor = Fattore di zoom
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Di quanto ingrandisce uno scatto della rotellina (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Di quanto ingrandisce uno scatto della rotellina (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Testo e quote
|
.text-and-dimensions = Testo e quote
|
||||||
|
|||||||
@@ -1429,6 +1429,7 @@ common =
|
|||||||
.zoom = ズーム
|
.zoom = ズーム
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = マウス ホイールのズーム方向を反転 (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = マウス ホイールのズーム方向を反転 (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = タッチパッド: スクロールで画面移動、Ctrl+スクロールまたはピンチでズーム
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = タッチパッド: スクロールで画面移動、Ctrl+スクロールまたはピンチでズーム
|
||||||
|
.interface-scale = インターフェースの拡大率
|
||||||
.zoom-factor = ズーム倍率
|
.zoom-factor = ズーム倍率
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = ホイール 1 ノッチのズーム量 (ZOOMFACTOR)。
|
.how-far-one-wheel-notch-zooms-zoomfactor = ホイール 1 ノッチのズーム量 (ZOOMFACTOR)。
|
||||||
.text-and-dimensions = 文字と寸法
|
.text-and-dimensions = 文字と寸法
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = 줌
|
.zoom = 줌
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = 마우스 휠 줌 방향 반전 (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = 마우스 휠 줌 방향 반전 (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = 터치패드: 스크롤은 화면 이동, Ctrl+스크롤 또는 핀치는 확대/축소
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = 터치패드: 스크롤은 화면 이동, Ctrl+스크롤 또는 핀치는 확대/축소
|
||||||
|
.interface-scale = 인터페이스 배율
|
||||||
.zoom-factor = 줌 배율
|
.zoom-factor = 줌 배율
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = 휠 한 칸이 확대하는 정도 (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = 휠 한 칸이 확대하는 정도 (ZOOMFACTOR).
|
||||||
.text-and-dimensions = 문자 및 치수
|
.text-and-dimensions = 문자 및 치수
|
||||||
|
|||||||
@@ -1440,6 +1440,7 @@ common =
|
|||||||
.zoom = ຊູມ
|
.zoom = ຊູມ
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = ສະຫຼັບທິດທາງການໝຸນລໍ້ເມົາສ໌ຊູມ (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = ສະຫຼັບທິດທາງການໝຸນລໍ້ເມົາສ໌ຊູມ (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = ທັດແພດ: ເລື່ອນເພື່ອຍ້າຍມຸມມອງ, Ctrl+ເລື່ອນ ຫຼື ບີບເພື່ອຊູມ
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = ທັດແພດ: ເລື່ອນເພື່ອຍ້າຍມຸມມອງ, Ctrl+ເລື່ອນ ຫຼື ບີບເພື່ອຊູມ
|
||||||
|
.interface-scale = ຂະໜາດສ່ວນຕິດຕໍ່
|
||||||
.zoom-factor = ຕົວຄູນການຊູມ
|
.zoom-factor = ຕົວຄູນການຊູມ
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = ໄລຍະຊູມຕໍ່ການໝຸນລໍ້ເມົາສ໌ໜຶ່ງຄລິກ (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = ໄລຍະຊູມຕໍ່ການໝຸນລໍ້ເມົາສ໌ໜຶ່ງຄລິກ (ZOOMFACTOR).
|
||||||
.text-and-dimensions = ຂໍ້ຄວາມ ແລະ ຂະໜາດ
|
.text-and-dimensions = ຂໍ້ຄວາມ ແລະ ຂະໜາດ
|
||||||
|
|||||||
@@ -1437,6 +1437,7 @@ common =
|
|||||||
.zoom = Zoomen
|
.zoom = Zoomen
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Muiswielzoom omkeren (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Muiswielzoom omkeren (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: scrollen verschuift, Ctrl+scrollen of knijpen zoomt
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: scrollen verschuift, Ctrl+scrollen of knijpen zoomt
|
||||||
|
.interface-scale = Interfaceschaal
|
||||||
.zoom-factor = Zoomfactor
|
.zoom-factor = Zoomfactor
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Hoeveel één wielklik zoomt (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Hoeveel één wielklik zoomt (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Tekst en maatvoering
|
.text-and-dimensions = Tekst en maatvoering
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = Powiększenie
|
.zoom = Powiększenie
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Odwróć powiększanie kółkiem myszy (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Odwróć powiększanie kółkiem myszy (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: przewijanie przesuwa widok, Ctrl+przewijanie lub szczypanie powiększa
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: przewijanie przesuwa widok, Ctrl+przewijanie lub szczypanie powiększa
|
||||||
|
.interface-scale = Skala interfejsu
|
||||||
.zoom-factor = Współczynnik powiększenia
|
.zoom-factor = Współczynnik powiększenia
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = O ile powiększa jedno kliknięcie kółka (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = O ile powiększa jedno kliknięcie kółka (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Tekst i wymiary
|
.text-and-dimensions = Tekst i wymiary
|
||||||
|
|||||||
@@ -1439,6 +1439,7 @@ common =
|
|||||||
.zoom = Zoom
|
.zoom = Zoom
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Inverter o zoom da roda do mouse (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Inverter o zoom da roda do mouse (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: rolar move a vista, Ctrl+rolar ou pinçar aplica zoom
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Touchpad: rolar move a vista, Ctrl+rolar ou pinçar aplica zoom
|
||||||
|
.interface-scale = Escala da interface
|
||||||
.zoom-factor = Fator de zoom
|
.zoom-factor = Fator de zoom
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Quanto um passo da roda amplia (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Quanto um passo da roda amplia (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Texto e cotas
|
.text-and-dimensions = Texto e cotas
|
||||||
|
|||||||
@@ -1442,6 +1442,7 @@ common =
|
|||||||
.zoom = Зумирование
|
.zoom = Зумирование
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Обратить зумирование колесом мыши (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Обратить зумирование колесом мыши (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Тачпад: прокрутка панорамирует, Ctrl+прокрутка или щипок масштабирует
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Тачпад: прокрутка панорамирует, Ctrl+прокрутка или щипок масштабирует
|
||||||
|
.interface-scale = Масштаб интерфейса
|
||||||
.zoom-factor = Коэффициент зумирования
|
.zoom-factor = Коэффициент зумирования
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Насколько приближает один щелчок колеса (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Насколько приближает один щелчок колеса (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Текст и размеры
|
.text-and-dimensions = Текст и размеры
|
||||||
|
|||||||
@@ -1425,6 +1425,7 @@ common =
|
|||||||
.zoom = Yakınlaştırma
|
.zoom = Yakınlaştırma
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = Fare tekerleği yakınlaştırmasını ters çevir (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = Fare tekerleği yakınlaştırmasını ters çevir (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Dokunmatik yüzey: kaydırma kaydırır, Ctrl+kaydırma veya sıkıştırma yakınlaştırır
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = Dokunmatik yüzey: kaydırma kaydırır, Ctrl+kaydırma veya sıkıştırma yakınlaştırır
|
||||||
|
.interface-scale = Arayüz ölçeği
|
||||||
.zoom-factor = Yakınlaştırma çarpanı
|
.zoom-factor = Yakınlaştırma çarpanı
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = Bir tekerlek adımının ne kadar yakınlaştıracağı (ZOOMFACTOR).
|
.how-far-one-wheel-notch-zooms-zoomfactor = Bir tekerlek adımının ne kadar yakınlaştıracağı (ZOOMFACTOR).
|
||||||
.text-and-dimensions = Metin ve ölçülendirme
|
.text-and-dimensions = Metin ve ölçülendirme
|
||||||
|
|||||||
@@ -1426,6 +1426,7 @@ common =
|
|||||||
.zoom = 缩放
|
.zoom = 缩放
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = 反转鼠标滚轮缩放方向 (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = 反转鼠标滚轮缩放方向 (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = 触控板:滚动平移,Ctrl+滚动或捏合缩放
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = 触控板:滚动平移,Ctrl+滚动或捏合缩放
|
||||||
|
.interface-scale = 界面缩放
|
||||||
.zoom-factor = 缩放系数
|
.zoom-factor = 缩放系数
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = 滚轮每格的缩放幅度 (ZOOMFACTOR)。
|
.how-far-one-wheel-notch-zooms-zoomfactor = 滚轮每格的缩放幅度 (ZOOMFACTOR)。
|
||||||
.text-and-dimensions = 文字和标注
|
.text-and-dimensions = 文字和标注
|
||||||
|
|||||||
@@ -1438,6 +1438,7 @@ common =
|
|||||||
.zoom = 縮放
|
.zoom = 縮放
|
||||||
.reverse-mouse-wheel-zoom-zoomwheel = 反轉滑鼠滾輪縮放方向 (ZOOMWHEEL)
|
.reverse-mouse-wheel-zoom-zoomwheel = 反轉滑鼠滾輪縮放方向 (ZOOMWHEEL)
|
||||||
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = 觸控板:捲動平移,Ctrl+捲動或捏合縮放
|
.touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms = 觸控板:捲動平移,Ctrl+捲動或捏合縮放
|
||||||
|
.interface-scale = 介面縮放
|
||||||
.zoom-factor = 縮放係數
|
.zoom-factor = 縮放係數
|
||||||
.how-far-one-wheel-notch-zooms-zoomfactor = 滾輪每一格的縮放幅度 (ZOOMFACTOR)。
|
.how-far-one-wheel-notch-zooms-zoomfactor = 滾輪每一格的縮放幅度 (ZOOMFACTOR)。
|
||||||
.text-and-dimensions = 文字與標註
|
.text-and-dimensions = 文字與標註
|
||||||
|
|||||||
Generated
+1
-1
@@ -1264,7 +1264,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "opencadcodec"
|
name = "opencadcodec"
|
||||||
version = "0.6.0"
|
version = "0.6.0"
|
||||||
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=ab2eecd#ab2eecdbffc31120b5ad6d899f6fc67cf21ede39"
|
source = "git+https://github.com/HakanSeven12/opencadcodec.git?rev=c9ffcac#c9ffcac5ecdc7a504cc4a7e3ecb52ac9d587c8bf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bitflags",
|
"bitflags",
|
||||||
|
|||||||
@@ -1801,7 +1801,7 @@ mod tests {
|
|||||||
viewport.id = 2;
|
viewport.id = 2;
|
||||||
viewport.width = 100.0;
|
viewport.width = 100.0;
|
||||||
viewport.height = 50.0;
|
viewport.height = 50.0;
|
||||||
viewport.status.is_on = true;
|
viewport.turn_on();
|
||||||
scene.add_entity(codec::EntityType::Viewport(viewport));
|
scene.add_entity(codec::EntityType::Viewport(viewport));
|
||||||
for entity in scene.document.entities_mut() {
|
for entity in scene.document.entities_mut() {
|
||||||
if let codec::EntityType::Viewport(viewport) = entity {
|
if let codec::EntityType::Viewport(viewport) = entity {
|
||||||
|
|||||||
@@ -489,6 +489,8 @@ impl OpenCADStudio {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
// Group 294: a base point the user picked.
|
// Group 294: a base point the user picked.
|
||||||
let picked_base = options.base_point.is_some();
|
let picked_base = options.base_point.is_some();
|
||||||
|
let several = profiles.len() > 1;
|
||||||
|
let picked = options;
|
||||||
let options = sweep_model::sweep_selection_options(&selection, options);
|
let options = sweep_model::sweep_selection_options(&selection, options);
|
||||||
let pending = if profiles.is_empty() {
|
let pending = if profiles.is_empty() {
|
||||||
None
|
None
|
||||||
@@ -532,8 +534,10 @@ impl OpenCADStudio {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// An associative surface keeps the profile and path it reads.
|
||||||
|
let associative = surface && sweep_model::surface_associativity();
|
||||||
let deletes_path =
|
let deletes_path =
|
||||||
crate::app::delobj_deletes_auxiliary(delete_objects, surface);
|
!associative && crate::app::delobj_deletes_auxiliary(delete_objects, surface);
|
||||||
if deletes_path && self.tabs[i].scene.is_layer_locked(path_handle) {
|
if deletes_path && self.tabs[i].scene.is_layer_locked(path_handle) {
|
||||||
failed += 1;
|
failed += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -546,6 +550,14 @@ impl OpenCADStudio {
|
|||||||
created,
|
created,
|
||||||
&expressions,
|
&expressions,
|
||||||
);
|
);
|
||||||
|
if associative {
|
||||||
|
// A picked base point, or the one several profiles share.
|
||||||
|
let linked = crate::command::SweepOptions {
|
||||||
|
base_point: if picked_base || several { options.and_then(|o| o.base_point) } else { None },
|
||||||
|
..picked
|
||||||
|
};
|
||||||
|
sweep_model::link_sweep_sources(&mut self.tabs[i].scene.document, created, handle, path_handle, &linked);
|
||||||
|
}
|
||||||
created
|
created
|
||||||
} else {
|
} else {
|
||||||
self.add_solid_model(
|
self.add_solid_model(
|
||||||
@@ -558,7 +570,7 @@ impl OpenCADStudio {
|
|||||||
failed += 1;
|
failed += 1;
|
||||||
} else {
|
} else {
|
||||||
created_handles.push(created);
|
created_handles.push(created);
|
||||||
if delete_profiles {
|
if delete_profiles && !associative {
|
||||||
consumed.push(handle);
|
consumed.push(handle);
|
||||||
}
|
}
|
||||||
if deletes_path {
|
if deletes_path {
|
||||||
|
|||||||
@@ -935,14 +935,17 @@ mod delobj_tests {
|
|||||||
expected,
|
expected,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
assert_eq!(
|
// A swept surface is associative by default (SURFACEASSOCIATIVITY 1)
|
||||||
sweep_source_presence(2, crate::command::ExtrudeMode::Surface),
|
// and keeps the profile and path it follows, whatever DELOBJ says;
|
||||||
(false, false),
|
// DELOBJ reaches surfaces only with associativity off, a profile-wide
|
||||||
);
|
// switch this parallel test leaves alone.
|
||||||
assert_eq!(
|
assert!(crate::scene::model::sweep_model::surface_associativity());
|
||||||
sweep_source_presence(3, crate::command::ExtrudeMode::Surface),
|
for value in [2, 3] {
|
||||||
(false, true),
|
assert_eq!(
|
||||||
);
|
sweep_source_presence(value, crate::command::ExtrudeMode::Surface),
|
||||||
|
(true, true),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -697,6 +697,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
|||||||
"PDFOSNAP",
|
"PDFOSNAP",
|
||||||
"UOSNAP",
|
"UOSNAP",
|
||||||
"FIELDDISPLAY",
|
"FIELDDISPLAY",
|
||||||
|
"SURFACEASSOCIATIVITY",
|
||||||
"FIELDEVAL",
|
"FIELDEVAL",
|
||||||
"PDFIMPORTMODE",
|
"PDFIMPORTMODE",
|
||||||
"PDFIMPORTFILTER",
|
"PDFIMPORTFILTER",
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ impl OpenCADStudio {
|
|||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
Some(name.clone())
|
Some(name.to_uppercase())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -310,6 +310,17 @@ impl OpenCADStudio {
|
|||||||
.map(|ds| ds.dimtxsty.clone()),
|
.map(|ds| ds.dimtxsty.clone()),
|
||||||
)
|
)
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| s.to_uppercase())
|
||||||
|
.collect();
|
||||||
|
// Complex linetypes reach their text and shape styles by handle.
|
||||||
|
let linetype_styles: rustc_hash::FxHashSet<codec::Handle> = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.line_types
|
||||||
|
.iter()
|
||||||
|
.flat_map(|lt| lt.elements.iter())
|
||||||
|
.filter_map(|element| element.complex.as_ref())
|
||||||
|
.map(|complex| complex.style_handle)
|
||||||
.collect();
|
.collect();
|
||||||
// Linetypes: entity references plus every layer's own
|
// Linetypes: entity references plus every layer's own
|
||||||
// linetype — a ByLayer entity reaches it through the layer.
|
// linetype — a ByLayer entity reaches it through the layer.
|
||||||
@@ -334,6 +345,7 @@ impl OpenCADStudio {
|
|||||||
.map(|l| l.line_type.clone()),
|
.map(|l| l.line_type.clone()),
|
||||||
)
|
)
|
||||||
.filter(|s| !s.is_empty())
|
.filter(|s| !s.is_empty())
|
||||||
|
.map(|s| s.to_uppercase())
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
// Live blocks are reachable from layouts and style roots
|
// Live blocks are reachable from layouts and style roots
|
||||||
@@ -403,7 +415,9 @@ impl OpenCADStudio {
|
|||||||
.document
|
.document
|
||||||
.layers
|
.layers
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|l| l.name != "0" && !used_layers.contains(&l.name))
|
.filter(|l| {
|
||||||
|
l.name != "0" && !used_layers.contains(&l.name.to_uppercase())
|
||||||
|
})
|
||||||
.map(|l| l.name.clone())
|
.map(|l| l.name.clone())
|
||||||
.collect()
|
.collect()
|
||||||
} else {
|
} else {
|
||||||
@@ -415,8 +429,14 @@ impl OpenCADStudio {
|
|||||||
.document
|
.document
|
||||||
.text_styles
|
.text_styles
|
||||||
.iter()
|
.iter()
|
||||||
|
// Unnamed shape-file records serve complex linetypes
|
||||||
|
// and are never listed for purging.
|
||||||
.filter(|s| {
|
.filter(|s| {
|
||||||
s.name != "Standard" && !used_text_styles.contains(&s.name)
|
!s.name.is_empty()
|
||||||
|
&& !s.is_shape_file
|
||||||
|
&& !s.name.eq_ignore_ascii_case("Standard")
|
||||||
|
&& !used_text_styles.contains(&s.name.to_uppercase())
|
||||||
|
&& !linetype_styles.contains(&s.handle)
|
||||||
})
|
})
|
||||||
.map(|s| s.name.clone())
|
.map(|s| s.name.clone())
|
||||||
.collect()
|
.collect()
|
||||||
@@ -432,7 +452,7 @@ impl OpenCADStudio {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter(|lt| {
|
.filter(|lt| {
|
||||||
!standard.iter().any(|s| s.eq_ignore_ascii_case(<.name))
|
!standard.iter().any(|s| s.eq_ignore_ascii_case(<.name))
|
||||||
&& !used_linetypes.contains(<.name)
|
&& !used_linetypes.contains(<.name.to_uppercase())
|
||||||
})
|
})
|
||||||
.map(|lt| lt.name.clone())
|
.map(|lt| lt.name.clone())
|
||||||
.collect()
|
.collect()
|
||||||
@@ -479,16 +499,17 @@ impl OpenCADStudio {
|
|||||||
snapshot_pushed = true;
|
snapshot_pushed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply removals (mutable)
|
// Apply removals (mutable). Count what actually went: an
|
||||||
for name in &layer_remove {
|
// entry the table cannot remove by name would otherwise be
|
||||||
self.tabs[i].scene.document.layers.remove(name);
|
// listed again on every pass and never let the loop end.
|
||||||
}
|
let doc = &mut self.tabs[i].scene.document;
|
||||||
for name in &style_remove {
|
let layers_gone =
|
||||||
self.tabs[i].scene.document.text_styles.remove(name);
|
layer_remove.iter().filter(|n| doc.layers.remove(n).is_some()).count();
|
||||||
}
|
let styles_gone =
|
||||||
for name in <_remove {
|
style_remove.iter().filter(|n| doc.text_styles.remove(n).is_some()).count();
|
||||||
self.tabs[i].scene.document.line_types.remove(name);
|
let lts_gone =
|
||||||
}
|
lt_remove.iter().filter(|n| doc.line_types.remove(n).is_some()).count();
|
||||||
|
let mut blocks_gone = 0usize;
|
||||||
for name in &block_remove {
|
for name in &block_remove {
|
||||||
// Drop the block definition's member entities (and the
|
// Drop the block definition's member entities (and the
|
||||||
// BLOCK/ENDBLK delimiters) before the record so no orphaned
|
// BLOCK/ENDBLK delimiters) before the record so no orphaned
|
||||||
@@ -506,12 +527,17 @@ impl OpenCADStudio {
|
|||||||
for h in handles {
|
for h in handles {
|
||||||
self.tabs[i].scene.document.remove_entity(h);
|
self.tabs[i].scene.document.remove_entity(h);
|
||||||
}
|
}
|
||||||
self.tabs[i].scene.document.block_records.remove(name);
|
if self.tabs[i].scene.document.block_records.remove(name).is_some() {
|
||||||
|
blocks_gone += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n_layers += layers_gone;
|
||||||
|
n_styles += styles_gone;
|
||||||
|
n_lts += lts_gone;
|
||||||
|
n_blocks += blocks_gone;
|
||||||
|
if layers_gone + styles_gone + lts_gone + blocks_gone == 0 {
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
n_layers += layer_remove.len();
|
|
||||||
n_styles += style_remove.len();
|
|
||||||
n_lts += lt_remove.len();
|
|
||||||
n_blocks += block_remove.len();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove draw-order tables whose owning blocks are gone.
|
// Remove draw-order tables whose owning blocks are gone.
|
||||||
@@ -1093,6 +1119,7 @@ impl OpenCADStudio {
|
|||||||
| "DGNOSNAP"
|
| "DGNOSNAP"
|
||||||
| "UOSNAP"
|
| "UOSNAP"
|
||||||
| "FIELDDISPLAY"
|
| "FIELDDISPLAY"
|
||||||
|
| "SURFACEASSOCIATIVITY"
|
||||||
| "PDFIMPORTMODE"
|
| "PDFIMPORTMODE"
|
||||||
| "PDFIMPORTFILTER"
|
| "PDFIMPORTFILTER"
|
||||||
| "PDFIMPORTLAYERS"
|
| "PDFIMPORTLAYERS"
|
||||||
@@ -1301,6 +1328,28 @@ impl OpenCADStudio {
|
|||||||
}
|
}
|
||||||
return Some(self.finish_dispatch(cmd));
|
return Some(self.finish_dispatch(cmd));
|
||||||
}
|
}
|
||||||
|
if name == "SURFACEASSOCIATIVITY" {
|
||||||
|
use crate::scene::model::sweep_model;
|
||||||
|
let current = i16::from(sweep_model::surface_associativity());
|
||||||
|
match value.as_deref().map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v))) {
|
||||||
|
Some(Some(mode)) => {
|
||||||
|
if current != mode {
|
||||||
|
sweep_model::set_surface_associativity(mode == 1);
|
||||||
|
self.save_config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(None) => {
|
||||||
|
self.command_line.push_error(crate::t!("Requires 0 or 1 only.").as_ref());
|
||||||
|
self.command_line.push_output(&format!("Enter new value for SURFACEASSOCIATIVITY <{current}>:"));
|
||||||
|
self.pending_setvar = Some(name.clone());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
self.command_line.push_output(&format!("Enter new value for SURFACEASSOCIATIVITY <{current}>:"));
|
||||||
|
self.pending_setvar = Some(name.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Some(self.finish_dispatch(cmd));
|
||||||
|
}
|
||||||
if name == "FIELDDISPLAY" {
|
if name == "FIELDDISPLAY" {
|
||||||
let current = i16::from(crate::entities::field::display());
|
let current = i16::from(crate::entities::field::display());
|
||||||
match value.as_deref().map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v))) {
|
match value.as_deref().map(|v| v.trim().parse::<i16>().ok().filter(|v| (0..=1).contains(v))) {
|
||||||
|
|||||||
@@ -719,7 +719,7 @@ impl OpenCADStudio {
|
|||||||
vp.view_height,
|
vp.view_height,
|
||||||
vp.height,
|
vp.height,
|
||||||
),
|
),
|
||||||
vp.status.is_on,
|
vp.is_on(),
|
||||||
vp.status.locked,
|
vp.status.locked,
|
||||||
))
|
))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -62,10 +62,25 @@ impl OpenCADStudio {
|
|||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
let doc_layers = self.tabs[i].scene.document.layers.clone();
|
||||||
let vp_info = self.tabs[i].scene.viewport_list();
|
let vp_info = self.tabs[i].scene.viewport_list();
|
||||||
|
let names_before: Vec<String> =
|
||||||
|
self.tabs[i].layers.layers.iter().map(|l| l.name.clone()).collect();
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.layers
|
.layers
|
||||||
.sync_with_viewports(&doc_layers, vp_info);
|
.sync_with_viewports(&doc_layers, vp_info);
|
||||||
self.sync_ribbon_layers();
|
self.sync_ribbon_layers();
|
||||||
|
// The Properties palette lists layer names it read when it was built;
|
||||||
|
// a layer added, removed or renamed must reach that list too (#1664).
|
||||||
|
// Only then: the rebuild is costly for a large selection.
|
||||||
|
let names_changed = self.tabs[i].layers.layers.len() != names_before.len()
|
||||||
|
|| self.tabs[i]
|
||||||
|
.layers
|
||||||
|
.layers
|
||||||
|
.iter()
|
||||||
|
.zip(&names_before)
|
||||||
|
.any(|(layer, before)| layer.name != *before);
|
||||||
|
if names_changed {
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pick up layers an entity edit registered on the fly (`ensure_layer`).
|
/// Pick up layers an entity edit registered on the fly (`ensure_layer`).
|
||||||
|
|||||||
@@ -561,6 +561,8 @@ pub(super) struct OpenCADStudio {
|
|||||||
zoom_wheel_reversed: bool,
|
zoom_wheel_reversed: bool,
|
||||||
/// Scroll pans and Ctrl+scroll zooms (touchpads); see `scroll_intent`.
|
/// Scroll pans and Ctrl+scroll zooms (touchpads); see `scroll_intent`.
|
||||||
touchpad_pan: bool,
|
touchpad_pan: bool,
|
||||||
|
/// Interface scale in percent, applied through the window scale factor.
|
||||||
|
ui_scale_percent: u16,
|
||||||
/// Mouse-wheel zoom sensitivity (ZOOMFACTOR), held inside
|
/// Mouse-wheel zoom sensitivity (ZOOMFACTOR), held inside
|
||||||
/// `settings::ZOOM_FACTOR_MIN..=settings::ZOOM_FACTOR_MAX`. `SETVAR`
|
/// `settings::ZOOM_FACTOR_MIN..=settings::ZOOM_FACTOR_MAX`. `SETVAR`
|
||||||
/// only sets the range the system variable itself has; the Options
|
/// only sets the range the system variable itself has; the Options
|
||||||
@@ -2378,6 +2380,8 @@ pub enum Message {
|
|||||||
/// Toggle reversing the mouse-wheel zoom direction (ZOOMWHEEL).
|
/// Toggle reversing the mouse-wheel zoom direction (ZOOMWHEEL).
|
||||||
ZoomWheelReversedChanged(bool),
|
ZoomWheelReversedChanged(bool),
|
||||||
TouchpadPanChanged(bool),
|
TouchpadPanChanged(bool),
|
||||||
|
/// Interface scale in percent (Options > Display).
|
||||||
|
UiScaleChanged(u16),
|
||||||
/// Change how far one wheel notch zooms, from the Options slider
|
/// Change how far one wheel notch zooms, from the Options slider
|
||||||
/// (ZOOMFACTOR).
|
/// (ZOOMFACTOR).
|
||||||
ZoomFactorChanged(i32),
|
ZoomFactorChanged(i32),
|
||||||
@@ -4161,6 +4165,7 @@ impl OpenCADStudio {
|
|||||||
polar_increment_deg: 45.0,
|
polar_increment_deg: 45.0,
|
||||||
zoom_wheel_reversed: false,
|
zoom_wheel_reversed: false,
|
||||||
touchpad_pan: false,
|
touchpad_pan: false,
|
||||||
|
ui_scale_percent: 100,
|
||||||
zoom_factor: 60,
|
zoom_factor: 60,
|
||||||
zoom_factor_input: 60.to_string(),
|
zoom_factor_input: 60.to_string(),
|
||||||
cursor_size: 5,
|
cursor_size: 5,
|
||||||
@@ -4904,6 +4909,7 @@ pub fn run() -> iced::Result {
|
|||||||
..iced::Settings::default()
|
..iced::Settings::default()
|
||||||
})
|
})
|
||||||
.subscription(OpenCADStudio::subscription)
|
.subscription(OpenCADStudio::subscription)
|
||||||
|
.scale_factor(|state: &OpenCADStudio, _| f32::from(state.ui_scale_percent) / 100.0)
|
||||||
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
.title(|state: &OpenCADStudio, window_id: window::Id| {
|
||||||
let _ = window_id; // all dialogs are in-canvas modals now
|
let _ = window_id; // all dialogs are in-canvas modals now
|
||||||
if let Some(tab) = state.tabs.get(state.active_tab) {
|
if let Some(tab) = state.tabs.get(state.active_tab) {
|
||||||
|
|||||||
@@ -183,6 +183,11 @@ pub const ZOOM_FACTOR_MAX: i32 = 500;
|
|||||||
/// Hold a ZOOMFACTOR inside the range the application can store, wherever it
|
/// Hold a ZOOMFACTOR inside the range the application can store, wherever it
|
||||||
/// arrives from — the slider, the Options field, or a configuration file
|
/// arrives from — the slider, the Options field, or a configuration file
|
||||||
/// written by hand or by an older build.
|
/// written by hand or by an older build.
|
||||||
|
/// Interface scale in percent, kept to the range the Options slider offers.
|
||||||
|
pub fn clamp_ui_scale(percent: u16) -> u16 {
|
||||||
|
percent.clamp(75, 200)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clamp_zoom_factor(v: i32) -> i32 {
|
pub fn clamp_zoom_factor(v: i32) -> i32 {
|
||||||
v.clamp(ZOOM_FACTOR_MIN, ZOOM_FACTOR_MAX)
|
v.clamp(ZOOM_FACTOR_MIN, ZOOM_FACTOR_MAX)
|
||||||
}
|
}
|
||||||
@@ -391,6 +396,8 @@ pub struct UserSettings {
|
|||||||
/// Scrolling pans and Ctrl+scroll (a touchpad pinch) zooms, for a
|
/// Scrolling pans and Ctrl+scroll (a touchpad pinch) zooms, for a
|
||||||
/// touchpad that reports two-finger drags as wheel scrolls.
|
/// touchpad that reports two-finger drags as wheel scrolls.
|
||||||
pub touchpad_pan: bool,
|
pub touchpad_pan: bool,
|
||||||
|
/// Interface scale in percent, on top of the display's own scale.
|
||||||
|
pub ui_scale_percent: u16,
|
||||||
pub zoom_factor: i32,
|
pub zoom_factor: i32,
|
||||||
/// CURSORSIZE: normalized crosshair reach; 5 retains the original 60 px arms.
|
/// CURSORSIZE: normalized crosshair reach; 5 retains the original 60 px arms.
|
||||||
pub cursor_size: i32,
|
pub cursor_size: i32,
|
||||||
@@ -415,6 +422,8 @@ pub struct UserSettings {
|
|||||||
pub attdef_on_screen: bool,
|
pub attdef_on_screen: bool,
|
||||||
/// FIELDDISPLAY: fields show on a gray background.
|
/// FIELDDISPLAY: fields show on a gray background.
|
||||||
pub field_display: bool,
|
pub field_display: bool,
|
||||||
|
/// SURFACEASSOCIATIVITY: new surfaces stay associative to their sources.
|
||||||
|
pub surface_associativity: bool,
|
||||||
/// Nested-copy symbol handling: false inserts, true binds.
|
/// Nested-copy symbol handling: false inserts, true binds.
|
||||||
pub ncopy_bind: bool,
|
pub ncopy_bind: bool,
|
||||||
/// Last Options page; unknown saved names fall back without rejecting the config.
|
/// Last Options page; unknown saved names fall back without rejecting the config.
|
||||||
@@ -811,6 +820,7 @@ impl Default for UserSettings {
|
|||||||
polar_increment_deg: 45.0,
|
polar_increment_deg: 45.0,
|
||||||
zoom_wheel_reversed: false,
|
zoom_wheel_reversed: false,
|
||||||
touchpad_pan: false,
|
touchpad_pan: false,
|
||||||
|
ui_scale_percent: 100,
|
||||||
zoom_factor: 60,
|
zoom_factor: 60,
|
||||||
cursor_size: 5,
|
cursor_size: 5,
|
||||||
pick_box: 3,
|
pick_box: 3,
|
||||||
@@ -827,6 +837,7 @@ impl Default for UserSettings {
|
|||||||
image_frame: 1,
|
image_frame: 1,
|
||||||
attdef_on_screen: true,
|
attdef_on_screen: true,
|
||||||
field_display: true,
|
field_display: true,
|
||||||
|
surface_associativity: true,
|
||||||
ncopy_bind: false,
|
ncopy_bind: false,
|
||||||
cursor_type: CursorType::Crosshair,
|
cursor_type: CursorType::Crosshair,
|
||||||
crosshair_color: None,
|
crosshair_color: None,
|
||||||
|
|||||||
@@ -663,6 +663,7 @@ impl OpenCADStudio {
|
|||||||
polar_increment_deg: self.polar_increment_deg,
|
polar_increment_deg: self.polar_increment_deg,
|
||||||
zoom_wheel_reversed: self.zoom_wheel_reversed,
|
zoom_wheel_reversed: self.zoom_wheel_reversed,
|
||||||
touchpad_pan: self.touchpad_pan,
|
touchpad_pan: self.touchpad_pan,
|
||||||
|
ui_scale_percent: self.ui_scale_percent,
|
||||||
zoom_factor: self.zoom_factor,
|
zoom_factor: self.zoom_factor,
|
||||||
cursor_size: self.cursor_size,
|
cursor_size: self.cursor_size,
|
||||||
pick_box: self.pick_box,
|
pick_box: self.pick_box,
|
||||||
@@ -679,6 +680,7 @@ impl OpenCADStudio {
|
|||||||
image_frame: crate::scene::frame::profile_image_mode(),
|
image_frame: crate::scene::frame::profile_image_mode(),
|
||||||
attdef_on_screen: crate::modules::draw::draw::attdef::session().on_screen,
|
attdef_on_screen: crate::modules::draw::draw::attdef::session().on_screen,
|
||||||
field_display: crate::entities::field::display(),
|
field_display: crate::entities::field::display(),
|
||||||
|
surface_associativity: crate::scene::model::sweep_model::surface_associativity(),
|
||||||
ncopy_bind: self.ncopy_bind,
|
ncopy_bind: self.ncopy_bind,
|
||||||
cursor_type: self.cursor_type,
|
cursor_type: self.cursor_type,
|
||||||
crosshair_color: self.crosshair_color,
|
crosshair_color: self.crosshair_color,
|
||||||
@@ -764,6 +766,7 @@ impl OpenCADStudio {
|
|||||||
self.polar_increment_deg = s.polar_increment_deg;
|
self.polar_increment_deg = s.polar_increment_deg;
|
||||||
self.zoom_wheel_reversed = s.zoom_wheel_reversed;
|
self.zoom_wheel_reversed = s.zoom_wheel_reversed;
|
||||||
self.touchpad_pan = s.touchpad_pan;
|
self.touchpad_pan = s.touchpad_pan;
|
||||||
|
self.ui_scale_percent = crate::app::settings::clamp_ui_scale(s.ui_scale_percent);
|
||||||
self.zoom_factor = crate::app::settings::clamp_zoom_factor(s.zoom_factor);
|
self.zoom_factor = crate::app::settings::clamp_zoom_factor(s.zoom_factor);
|
||||||
// Like the drafting-rotation field below, the Options field edits a
|
// Like the drafting-rotation field below, the Options field edits a
|
||||||
// buffer rather than the value, so it is reseeded whenever the value
|
// buffer rather than the value, so it is reseeded whenever the value
|
||||||
@@ -789,6 +792,7 @@ impl OpenCADStudio {
|
|||||||
crate::scene::frame::set_profile_image_mode(s.image_frame);
|
crate::scene::frame::set_profile_image_mode(s.image_frame);
|
||||||
crate::modules::draw::draw::attdef::session().on_screen = s.attdef_on_screen;
|
crate::modules::draw::draw::attdef::session().on_screen = s.attdef_on_screen;
|
||||||
crate::entities::field::set_display(s.field_display);
|
crate::entities::field::set_display(s.field_display);
|
||||||
|
crate::scene::model::sweep_model::set_surface_associativity(s.surface_associativity);
|
||||||
self.ncopy_bind = s.ncopy_bind;
|
self.ncopy_bind = s.ncopy_bind;
|
||||||
self.cursor_type = s.cursor_type;
|
self.cursor_type = s.cursor_type;
|
||||||
self.crosshair_color = s.crosshair_color;
|
self.crosshair_color = s.crosshair_color;
|
||||||
|
|||||||
@@ -8391,6 +8391,12 @@ impl OpenCADStudio {
|
|||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Message::UiScaleChanged(percent) => {
|
||||||
|
self.ui_scale_percent = crate::app::settings::clamp_ui_scale(percent);
|
||||||
|
self.persist_settings_if_changed();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
Message::ZoomFactorChanged(factor) => {
|
Message::ZoomFactorChanged(factor) => {
|
||||||
self.zoom_factor = crate::app::settings::clamp_zoom_factor(factor);
|
self.zoom_factor = crate::app::settings::clamp_zoom_factor(factor);
|
||||||
// The field beside the slider reads the same value, so the
|
// The field beside the slider reads the same value, so the
|
||||||
|
|||||||
+8
-5
@@ -927,11 +927,14 @@ bg={bg_ms:.1}ms n={view_count}"
|
|||||||
snap_angle_deg: self.snap_angle_deg,
|
snap_angle_deg: self.snap_angle_deg,
|
||||||
pick_pending: self.pending_pick_label().is_some(),
|
pick_pending: self.pending_pick_label().is_some(),
|
||||||
// An infinite-line preview runs along the arms; drop them
|
// An infinite-line preview runs along the arms; drop them
|
||||||
// so the preview stays visible (#1437).
|
// so the preview stays visible (#1437). A command asking
|
||||||
hide_arms: tab
|
// for objects shows the pickbox alone, so picking reads
|
||||||
.active_cmd
|
// apart from both idle and point input (#1661).
|
||||||
.as_ref()
|
hide_arms: tab.active_cmd.as_ref().is_some_and(|c| {
|
||||||
.is_some_and(|c| matches!(c.name(), "XLINE" | "RAY")),
|
matches!(c.name(), "XLINE" | "RAY")
|
||||||
|
|| c.needs_entity_pick()
|
||||||
|
|| c.is_selection_gathering()
|
||||||
|
}),
|
||||||
snap_color: self.snap_marker_color,
|
snap_color: self.snap_marker_color,
|
||||||
},
|
},
|
||||||
crate::ui::overlay::SelectionVisualOptions {
|
crate::ui::overlay::SelectionVisualOptions {
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ impl OpenCADStudio {
|
|||||||
commandline_fade_ms: self.commandline_fade_ms,
|
commandline_fade_ms: self.commandline_fade_ms,
|
||||||
zoom_wheel_reversed: self.zoom_wheel_reversed,
|
zoom_wheel_reversed: self.zoom_wheel_reversed,
|
||||||
touchpad_pan: self.touchpad_pan,
|
touchpad_pan: self.touchpad_pan,
|
||||||
|
ui_scale_percent: self.ui_scale_percent,
|
||||||
zoom_factor: self.zoom_factor,
|
zoom_factor: self.zoom_factor,
|
||||||
texteditmode: self.texteditmode,
|
texteditmode: self.texteditmode,
|
||||||
dimension_continue_mode: self.dimension_continue_mode,
|
dimension_continue_mode: self.dimension_continue_mode,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ fn fixture() -> (OpenCADStudio, Handle, ViewportFrame) {
|
|||||||
viewport.view_height = 1000.0;
|
viewport.view_height = 1000.0;
|
||||||
viewport.custom_scale = 0.1;
|
viewport.custom_scale = 0.1;
|
||||||
viewport.view_direction = Vector3::UNIT_Z;
|
viewport.view_direction = Vector3::UNIT_Z;
|
||||||
viewport.status.is_on = true;
|
viewport.turn_on();
|
||||||
let viewport = scene.add_entity(EntityType::Viewport(viewport));
|
let viewport = scene.add_entity(EntityType::Viewport(viewport));
|
||||||
let frame = scene.viewport_frame(viewport).unwrap();
|
let frame = scene.viewport_frame(viewport).unwrap();
|
||||||
scene.document.header.dimension_associativity = 2;
|
scene.document.header.dimension_associativity = 2;
|
||||||
@@ -484,7 +484,7 @@ fn viewport_dimension_eligibility_is_shared_and_uses_clipping() {
|
|||||||
first.viewport
|
first.viewport
|
||||||
);
|
);
|
||||||
if let Some(EntityType::Viewport(vp)) = scene.document.get_entity_mut(first.viewport) {
|
if let Some(EntityType::Viewport(vp)) = scene.document.get_entity_mut(first.viewport) {
|
||||||
vp.status.is_on = false;
|
vp.turn_off();
|
||||||
}
|
}
|
||||||
assert!(scene.viewport_frames_at_paper_point(paper).is_empty());
|
assert!(scene.viewport_frames_at_paper_point(paper).is_empty());
|
||||||
assert!(scene.dimension_pick_through_viewport(paper, 0.2).is_none());
|
assert!(scene.dimension_pick_through_viewport(paper, 0.2).is_none());
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ fn properties(vp: &Viewport) -> Vec<PropSection> {
|
|||||||
field: "vp_on",
|
field: "vp_on",
|
||||||
value: PropValue::BoolToggle {
|
value: PropValue::BoolToggle {
|
||||||
field: "vp_on",
|
field: "vp_on",
|
||||||
value: vp.status.is_on,
|
value: vp.is_on(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
ro(
|
ro(
|
||||||
@@ -193,11 +193,14 @@ fn apply_geom_prop(vp: &mut Viewport, field: &str, value: &str) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
"vp_on" => {
|
"vp_on" => {
|
||||||
vp.status.is_on = if value == "toggle" {
|
// turn_on/turn_off keep the explicit-off bit in step, so a
|
||||||
!vp.status.is_on
|
// viewport the file marked off can be turned back on.
|
||||||
|
let on = if value == "toggle" { !vp.is_on() } else { value == "true" };
|
||||||
|
if on {
|
||||||
|
vp.turn_on();
|
||||||
} else {
|
} else {
|
||||||
value == "true"
|
vp.turn_off();
|
||||||
};
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
"vp_perspective" => {
|
"vp_perspective" => {
|
||||||
|
|||||||
+1
-1
@@ -2539,7 +2539,7 @@ fn fix_viewport_status_flags(doc: &mut CadDocument) {
|
|||||||
// If bit 0 is not set but bit 15 is, this is an external-format viewport:
|
// If bit 0 is not set but bit 15 is, this is an external-format viewport:
|
||||||
// treat bit 15 as "on" and bit 14 as "locked".
|
// treat bit 15 as "on" and bit 14 as "locked".
|
||||||
if (bits & 0x0001) == 0 && (bits & 0x8000) != 0 {
|
if (bits & 0x0001) == 0 && (bits & 0x8000) != 0 {
|
||||||
vp.status.is_on = true;
|
vp.turn_on();
|
||||||
vp.status.locked = (bits & 0x4000) != 0;
|
vp.status.locked = (bits & 0x4000) != 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4798,6 +4798,7 @@ pub(super) fn message_attribute(source: &str) -> Option<(&'static str, &'static
|
|||||||
"Zoom" => Some(("common", "zoom")),
|
"Zoom" => Some(("common", "zoom")),
|
||||||
"Reverse mouse wheel zoom (ZOOMWHEEL)" => Some(("common", "reverse-mouse-wheel-zoom-zoomwheel")),
|
"Reverse mouse wheel zoom (ZOOMWHEEL)" => Some(("common", "reverse-mouse-wheel-zoom-zoomwheel")),
|
||||||
"Touchpad: scroll pans, Ctrl+scroll or pinch zooms" => Some(("common", "touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms")),
|
"Touchpad: scroll pans, Ctrl+scroll or pinch zooms" => Some(("common", "touchpad-scroll-pans-ctrl-scroll-or-pinch-zooms")),
|
||||||
|
"Interface scale" => Some(("common", "interface-scale")),
|
||||||
"Zoom factor" => Some(("common", "zoom-factor")),
|
"Zoom factor" => Some(("common", "zoom-factor")),
|
||||||
"How far one wheel notch zooms (ZOOMFACTOR)." => Some(("common", "how-far-one-wheel-notch-zooms-zoomfactor")),
|
"How far one wheel notch zooms (ZOOMFACTOR)." => Some(("common", "how-far-one-wheel-notch-zooms-zoomfactor")),
|
||||||
"Text and Dimensions" => Some(("common", "text-and-dimensions")),
|
"Text and Dimensions" => Some(("common", "text-and-dimensions")),
|
||||||
|
|||||||
@@ -2482,6 +2482,9 @@ fn detach_preserved_scope(
|
|||||||
Some(ObjectType::Associative(object)) => match &object.data {
|
Some(ObjectType::Associative(object)) => match &object.data {
|
||||||
AssociativeData::ConstraintGroup(_) | AssociativeData::Network(_) => false,
|
AssociativeData::ConstraintGroup(_) | AssociativeData::Network(_) => false,
|
||||||
AssociativeData::Variable(variable) => variable.name.trim().is_empty(),
|
AssociativeData::Variable(variable) => variable.name.trim().is_empty(),
|
||||||
|
// A surface action whose surface was erased goes with it.
|
||||||
|
AssociativeData::Action(_) => action_surface(document, *handle)
|
||||||
|
.is_none_or(|surface| document.get_entity(surface).is_some()),
|
||||||
_ => true,
|
_ => true,
|
||||||
},
|
},
|
||||||
_ => false,
|
_ => false,
|
||||||
@@ -2489,6 +2492,35 @@ fn detach_preserved_scope(
|
|||||||
.collect(),
|
.collect(),
|
||||||
_ => Vec::new(),
|
_ => Vec::new(),
|
||||||
};
|
};
|
||||||
|
// Unnamed variables stay only while a kept action reads them.
|
||||||
|
let read_variables: Vec<Handle> = foreign_actions
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| match document.objects.get(handle) {
|
||||||
|
Some(ObjectType::Associative(AssociativeObject {
|
||||||
|
data: AssociativeData::Action(action),
|
||||||
|
..
|
||||||
|
})) => Some(action),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.flat_map(|action| action.values.iter().flat_map(|value| value.variables.iter().map(|variable| variable.handle)))
|
||||||
|
.filter_map(|handle| match document.objects.get(&handle) {
|
||||||
|
Some(ObjectType::Associative(AssociativeObject {
|
||||||
|
data: AssociativeData::ValueDependency(value),
|
||||||
|
..
|
||||||
|
})) => Some(value.dependency.dependent_on),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let foreign_actions: Vec<Handle> = foreign_actions
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| match document.objects.get(handle) {
|
||||||
|
Some(ObjectType::Associative(AssociativeObject {
|
||||||
|
data: AssociativeData::Variable(_),
|
||||||
|
..
|
||||||
|
})) => read_variables.contains(handle),
|
||||||
|
_ => true,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
let preserved_groups: Vec<_> = native_group_handles(document, owner)
|
let preserved_groups: Vec<_> = native_group_handles(document, owner)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|handle| {
|
.filter(|handle| {
|
||||||
@@ -2574,6 +2606,265 @@ fn detach_preserved_scope(
|
|||||||
(variables, preserved_groups, foreign_actions)
|
(variables, preserved_groups, foreign_actions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The surface an associative surface action writes.
|
||||||
|
fn action_surface(document: &CadDocument, action: Handle) -> Option<Handle> {
|
||||||
|
let Some(ObjectType::Associative(AssociativeObject {
|
||||||
|
data: AssociativeData::Action(action),
|
||||||
|
..
|
||||||
|
})) = document.objects.get(&action)
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
let Some(ObjectType::Associative(AssociativeObject {
|
||||||
|
data: AssociativeData::SurfaceActionBody(body),
|
||||||
|
..
|
||||||
|
})) = document.objects.get(&action.action_body)
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
match document.objects.get(&body.surface_body.dependency) {
|
||||||
|
Some(ObjectType::Associative(AssociativeObject {
|
||||||
|
data: AssociativeData::Dependency(dependency),
|
||||||
|
..
|
||||||
|
})) => Some(dependency.dependent_on),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Associative swept surfaces made here that the drawing's network does
|
||||||
|
/// not carry yet: (surface, profile, path).
|
||||||
|
fn pending_swept_surfaces(document: &CadDocument) -> Vec<(Handle, Handle, Handle)> {
|
||||||
|
use crate::scene::model::sweep_model;
|
||||||
|
let carried: Vec<Handle> = document
|
||||||
|
.objects
|
||||||
|
.keys()
|
||||||
|
.filter_map(|handle| action_surface(document, *handle))
|
||||||
|
.collect();
|
||||||
|
let mut pending = document
|
||||||
|
.entities()
|
||||||
|
.filter(|entity| matches!(entity, codec::EntityType::Surface(surface)
|
||||||
|
if matches!(surface.surface_data, codec::entities::SurfaceData::Swept { .. })))
|
||||||
|
.filter_map(|entity| {
|
||||||
|
let (profile, path) = sweep_model::sweep_sources(entity)?;
|
||||||
|
let surface = entity.common().handle;
|
||||||
|
(!carried.contains(&surface)
|
||||||
|
&& document.get_entity(profile).is_some_and(|e| sweep_model::assoc_edge_curve(e).is_some())
|
||||||
|
&& document.get_entity(path).is_some_and(|e| sweep_model::assoc_edge_curve(e).is_some()))
|
||||||
|
.then_some((surface, profile, path))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
pending.sort_by_key(|(surface, _, _)| surface.value());
|
||||||
|
pending
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The names an expression reads.
|
||||||
|
fn expression_names(expression: &str) -> Vec<String> {
|
||||||
|
let mut names = Vec::new();
|
||||||
|
let mut current = String::new();
|
||||||
|
for character in expression.chars().chain(std::iter::once(' ')) {
|
||||||
|
if character.is_alphanumeric() || character == '_' {
|
||||||
|
current.push(character);
|
||||||
|
} else if !current.is_empty() {
|
||||||
|
if current.chars().next().is_some_and(|c| c.is_alphabetic()) && !names.contains(¤t) {
|
||||||
|
names.push(current.clone());
|
||||||
|
}
|
||||||
|
current.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
names
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The reference's associative network for each pending swept surface: an
|
||||||
|
/// action with a swept-surface body that writes the surface and reads its
|
||||||
|
/// profile and path, with the rotation, scale and twist as value
|
||||||
|
/// parameters; a scale or twist expression is an unnamed variable reading
|
||||||
|
/// the named ones. Returns the network's new actions in order.
|
||||||
|
fn materialize_swept_surface_actions(
|
||||||
|
allocator: &mut Allocator,
|
||||||
|
network: Handle,
|
||||||
|
parameters: &ParameterTable,
|
||||||
|
pending: &[(Handle, Handle, Handle)],
|
||||||
|
first_index: i32,
|
||||||
|
) -> Vec<Handle> {
|
||||||
|
use crate::scene::model::sweep_model;
|
||||||
|
use codec::objects::*;
|
||||||
|
let mut added = Vec::new();
|
||||||
|
for (surface, profile, path) in pending.iter().copied() {
|
||||||
|
let document = &*allocator.document;
|
||||||
|
let Some(surface_entity) = document.get_entity(surface) else { continue };
|
||||||
|
let codec::EntityType::Surface(surface_value) = surface_entity else { continue };
|
||||||
|
let codec::entities::SurfaceData::Swept { options, .. } = &surface_value.surface_data else { continue };
|
||||||
|
let (rotation, scale, twist) = (options.align_angle, options.scale_factor, options.twist_angle);
|
||||||
|
let expressions = sweep_model::sweep_expressions(surface_entity).unwrap_or([None, None]);
|
||||||
|
let (Some(profile_curve), Some(path_curve)) = (
|
||||||
|
document.get_entity(profile).and_then(sweep_model::assoc_edge_curve),
|
||||||
|
document.get_entity(path).and_then(sweep_model::assoc_edge_curve),
|
||||||
|
) else { continue };
|
||||||
|
let evaluate = |expression: &str| {
|
||||||
|
let mut table = parameters.clone();
|
||||||
|
table.set("sweepExpression", expression).ok()?;
|
||||||
|
table.resolve("sweepExpression").ok().filter(|value| value.is_finite())
|
||||||
|
};
|
||||||
|
let document = &mut *allocator.document;
|
||||||
|
let mut next = || document.allocate_handle();
|
||||||
|
let (action, body, written, profile_param, profile_edge, profile_read, path_param, path_edge, path_read) =
|
||||||
|
(next(), next(), next(), next(), next(), next(), next(), next(), next());
|
||||||
|
let dependency = |dependent_on: Handle, read: bool, id: i32, order: i32| AssocDependency {
|
||||||
|
class_version: 2,
|
||||||
|
status: 0,
|
||||||
|
is_read_dependency: read,
|
||||||
|
is_write_dependency: !read,
|
||||||
|
is_attached_to_object: true,
|
||||||
|
is_delegating_to_owning_action: true,
|
||||||
|
order,
|
||||||
|
dependent_on,
|
||||||
|
name: None,
|
||||||
|
read_dependency: Handle::NULL,
|
||||||
|
node: Handle::NULL,
|
||||||
|
dependency_body: Handle::NULL,
|
||||||
|
dependency_body_id: id,
|
||||||
|
};
|
||||||
|
let mut objects: Vec<(Handle, Handle, &str, &str, AssociativeData)> = Vec::new();
|
||||||
|
let mut reactors: Vec<(Handle, Handle)> = vec![(surface, written), (profile, profile_read), (path, path_read)];
|
||||||
|
let mut dependencies = vec![written, profile_read, path_read];
|
||||||
|
let mut value_handles = [Handle::NULL, Handle::NULL];
|
||||||
|
let mut network_actions = vec![action];
|
||||||
|
// Scale (slot 0) and twist (slot 1) expressions.
|
||||||
|
for (slot, expression) in expressions.iter().enumerate() {
|
||||||
|
let Some(expression) = expression else { continue };
|
||||||
|
let Some(value) = evaluate(expression) else { continue };
|
||||||
|
let (variable, read_by_action) = (document.allocate_handle(), document.allocate_handle());
|
||||||
|
let mut variable_dependencies = Vec::new();
|
||||||
|
for (index, name) in expression_names(expression).iter().enumerate() {
|
||||||
|
let Some(named) = allocator.variables.get(name).copied() else { continue };
|
||||||
|
let reads = document.allocate_handle();
|
||||||
|
let named_value = parameters.resolve(name).unwrap_or(0.0);
|
||||||
|
objects.push((reads, variable, "ACDBASSOCVALUEDEPENDENCY", "AcDbAssocValueDependency",
|
||||||
|
AssociativeData::ValueDependency(AssocValueDependency {
|
||||||
|
dependency: dependency(named, true, index as i32 + 1, 0),
|
||||||
|
class_version: 0,
|
||||||
|
name: String::new(),
|
||||||
|
value: Allocator::numeric_eval(named_value),
|
||||||
|
})));
|
||||||
|
reactors.push((named, reads));
|
||||||
|
variable_dependencies.push(AssocVariableDependency { dependency: reads, flags: 0 });
|
||||||
|
}
|
||||||
|
objects.push((variable, network, "ACDBASSOCVARIABLE", "AcDbAssocVariable", AssociativeData::Variable(AssocVariable {
|
||||||
|
action: AssocAction {
|
||||||
|
class_version: 2,
|
||||||
|
owning_network: network,
|
||||||
|
max_dependency_index: variable_dependencies.len() as i32,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
class_version: 2,
|
||||||
|
name: String::new(),
|
||||||
|
expression: expression.clone(),
|
||||||
|
evaluator: "AcDbCalc:1.0".to_string(),
|
||||||
|
description: String::new(),
|
||||||
|
value: Allocator::numeric_eval(value),
|
||||||
|
has_cached_value: false,
|
||||||
|
cached_value: String::new(),
|
||||||
|
flag: false,
|
||||||
|
dependencies: variable_dependencies,
|
||||||
|
})));
|
||||||
|
objects.push((read_by_action, action, "ACDBASSOCVALUEDEPENDENCY", "AcDbAssocValueDependency",
|
||||||
|
AssociativeData::ValueDependency(AssocValueDependency {
|
||||||
|
dependency: dependency(variable, true, dependencies.len() as i32 + 1, 0),
|
||||||
|
class_version: 0,
|
||||||
|
name: String::new(),
|
||||||
|
value: Allocator::numeric_eval(value),
|
||||||
|
})));
|
||||||
|
reactors.push((variable, read_by_action));
|
||||||
|
dependencies.push(read_by_action);
|
||||||
|
value_handles[slot] = read_by_action;
|
||||||
|
network_actions.push(variable);
|
||||||
|
}
|
||||||
|
let real = |value: f64| AssocEvalVariant { code: 40, value: AssocEvalValue::Real(value) };
|
||||||
|
let value = |name: &str, unit_type: i32, amount: f64, handle: Handle| AssocValueParam {
|
||||||
|
class_version: 0,
|
||||||
|
name: name.to_string(),
|
||||||
|
unit_type,
|
||||||
|
variables: vec![AssocValueParamVariable { value: real(amount), handle }],
|
||||||
|
controlled_object_dependency: written,
|
||||||
|
};
|
||||||
|
objects.push((action, network, "ACDBASSOCACTION", "AcDbAssocAction", AssociativeData::Action(AssocAction {
|
||||||
|
class_version: 2,
|
||||||
|
geometry_status: 0,
|
||||||
|
owning_network: network,
|
||||||
|
action_body: body,
|
||||||
|
action_index: 0,
|
||||||
|
max_dependency_index: dependencies.len() as i32,
|
||||||
|
dependencies: dependencies.iter().map(|handle| AssocActionDependency { is_owned: true, dependency: *handle }).collect(),
|
||||||
|
owned_parameters: vec![profile_param, path_param],
|
||||||
|
values: vec![
|
||||||
|
value("RotationAngle", 2, rotation, Handle::NULL),
|
||||||
|
value("ScaleFactor", 0, scale, value_handles[0]),
|
||||||
|
value("TwistAngle", 2, twist, value_handles[1]),
|
||||||
|
],
|
||||||
|
})));
|
||||||
|
objects.push((body, action, "ACDBASSOCSWEPTSURFACEACTIONBODY", "AcDbAssocSweptSurfaceActionBody",
|
||||||
|
AssociativeData::SurfaceActionBody(AssocSurfaceActionBody {
|
||||||
|
kind: AssocSurfaceActionKind::Swept,
|
||||||
|
action_body: AssocActionBody { version: 2 },
|
||||||
|
surface_body: AssocSurfaceBody { version: 0, dependency: written, is_semi_associative: false, marker: 1, is_semi_override: false, grip_status: 0 },
|
||||||
|
..Default::default()
|
||||||
|
})));
|
||||||
|
objects.push((written, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(dependency(surface, false, 1, i32::MIN))));
|
||||||
|
let edge_param = |read: Handle, (kind, subcurve, curve): (i32, AssocSubcurveKind, Vec<AssocCurveValue>)| AssociativeData::EdgeActionParam(AssocEdgeActionParam {
|
||||||
|
single_dependency: AssocSingleDependencyActionParam {
|
||||||
|
action_param: AssocActionParam { is_r2013: 1, version: 0, name: String::new() },
|
||||||
|
dependency_class_version: 0,
|
||||||
|
dependency: read,
|
||||||
|
class_version: 0,
|
||||||
|
},
|
||||||
|
parameter: Handle::NULL,
|
||||||
|
has_action: true,
|
||||||
|
action_type: kind,
|
||||||
|
subcurve_kind: subcurve,
|
||||||
|
curve,
|
||||||
|
});
|
||||||
|
let path_parameter = |name: &str, child: Handle| AssociativeData::PathActionParam(AssocPathActionParam {
|
||||||
|
compound: AssocCompoundActionParam {
|
||||||
|
action_param: AssocActionParam { is_r2013: 1, version: 0, name: name.to_string() },
|
||||||
|
class_version: 0,
|
||||||
|
status: 0,
|
||||||
|
parameters: vec![child],
|
||||||
|
child_parameter: None,
|
||||||
|
},
|
||||||
|
version: 0,
|
||||||
|
});
|
||||||
|
objects.push((profile_param, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam", path_parameter("SweepProfile", profile_edge)));
|
||||||
|
objects.push((profile_edge, profile_param, "ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", edge_param(profile_read, profile_curve)));
|
||||||
|
objects.push((profile_read, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(dependency(profile, true, 2, 0))));
|
||||||
|
objects.push((path_param, action, "ACDBASSOCPATHACTIONPARAM", "AcDbAssocPathActionParam", path_parameter("SweepPath", path_edge)));
|
||||||
|
objects.push((path_edge, path_param, "ACDBASSOCEDGEACTIONPARAM", "AcDbAssocEdgeActionParam", edge_param(path_read, path_curve)));
|
||||||
|
objects.push((path_read, action, "ACDBASSOCDEPENDENCY", "AcDbAssocDependency", AssociativeData::Dependency(dependency(path, true, 3, 0))));
|
||||||
|
for (handle, owner, dxf, class, data) in objects {
|
||||||
|
allocator.insert_associative_at(handle, owner, dxf, class, data);
|
||||||
|
}
|
||||||
|
for (target, reactor) in reactors {
|
||||||
|
let document = &mut *allocator.document;
|
||||||
|
if let Some(entity) = document.get_entity_mut(target) {
|
||||||
|
entity.common_mut().reactors.push(reactor);
|
||||||
|
} else if let Some(ObjectType::Associative(object)) = document.objects.get_mut(&target) {
|
||||||
|
object.reactors.push(reactor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
added.extend(network_actions);
|
||||||
|
}
|
||||||
|
for (offset, handle) in added.iter().copied().enumerate() {
|
||||||
|
if let Some(ObjectType::Associative(object)) = allocator.document.objects.get_mut(&handle) {
|
||||||
|
let action = match &mut object.data {
|
||||||
|
AssociativeData::Action(action) => action,
|
||||||
|
AssociativeData::Variable(variable) => &mut variable.action,
|
||||||
|
_ => continue,
|
||||||
|
};
|
||||||
|
action.action_index = first_index + offset as i32 + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
added
|
||||||
|
}
|
||||||
|
|
||||||
/// Re-evaluates the unnamed variables of kept actions (a swept surface's
|
/// Re-evaluates the unnamed variables of kept actions (a swept surface's
|
||||||
/// scale or twist expression) and the values their actions cache, so the
|
/// scale or twist expression) and the values their actions cache, so the
|
||||||
/// file carries the values the named parameters give.
|
/// file carries the values the named parameters give.
|
||||||
@@ -2714,6 +3005,7 @@ fn materialize_scope(
|
|||||||
&& existing_variables.is_empty()
|
&& existing_variables.is_empty()
|
||||||
&& preserved_groups.is_empty()
|
&& preserved_groups.is_empty()
|
||||||
&& foreign_actions.is_empty()
|
&& foreign_actions.is_empty()
|
||||||
|
&& !(materialize_all_parameters && !pending_swept_surfaces(document).is_empty())
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -2898,6 +3190,9 @@ fn materialize_scope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
refresh_foreign_values(allocator.document, &foreign_actions, parameters);
|
refresh_foreign_values(allocator.document, &foreign_actions, parameters);
|
||||||
|
let pending = if materialize_all_parameters { pending_swept_surfaces(allocator.document) } else { Vec::new() };
|
||||||
|
let surface_actions = materialize_swept_surface_actions(
|
||||||
|
&mut allocator, network_handle, parameters, &pending, first_foreign + foreign_actions.len() as i32);
|
||||||
|
|
||||||
let network = AssocNetwork {
|
let network = AssocNetwork {
|
||||||
action: AssocAction {
|
action: AssocAction {
|
||||||
@@ -2907,7 +3202,7 @@ fn materialize_scope(
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
network_version: 0,
|
network_version: 0,
|
||||||
network_action_index: first_foreign + foreign_actions.len() as i32,
|
network_action_index: first_foreign + (foreign_actions.len() + surface_actions.len()) as i32,
|
||||||
actions: allocator
|
actions: allocator
|
||||||
.variable_actions
|
.variable_actions
|
||||||
.iter()
|
.iter()
|
||||||
@@ -2915,6 +3210,7 @@ fn materialize_scope(
|
|||||||
.chain(group_handle)
|
.chain(group_handle)
|
||||||
.chain(preserved_groups)
|
.chain(preserved_groups)
|
||||||
.chain(foreign_actions)
|
.chain(foreign_actions)
|
||||||
|
.chain(surface_actions)
|
||||||
.map(|dependency| AssocActionDependency {
|
.map(|dependency| AssocActionDependency {
|
||||||
is_owned: true,
|
is_owned: true,
|
||||||
dependency,
|
dependency,
|
||||||
@@ -3055,7 +3351,8 @@ impl Scene {
|
|||||||
};
|
};
|
||||||
if set.is_none()
|
if set.is_none()
|
||||||
&& !existing
|
&& !existing
|
||||||
&& (owner != model_owner || self.named_parameters.is_empty())
|
&& (owner != model_owner
|
||||||
|
|| (self.named_parameters.is_empty() && pending_swept_surfaces(&self.document).is_empty()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -3696,6 +3696,11 @@ impl Scene {
|
|||||||
changes.push(change);
|
changes.push(change);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for change in self.refresh_associative_sweeps(&changes) {
|
||||||
|
if !changes.iter().any(|(handle, _)| *handle == change.0) {
|
||||||
|
changes.push(change);
|
||||||
|
}
|
||||||
|
}
|
||||||
if solve_parametric && !self.parametric_constraints.is_empty() {
|
if solve_parametric && !self.parametric_constraints.is_empty() {
|
||||||
for change in self.refresh_parametric_constraints_with_initial_policy(
|
for change in self.refresh_parametric_constraints_with_initial_policy(
|
||||||
&changes,
|
&changes,
|
||||||
@@ -7499,7 +7504,7 @@ impl Scene {
|
|||||||
let Some(EntityType::Viewport(viewport)) = self.document.get_entity(handle) else {
|
let Some(EntityType::Viewport(viewport)) = self.document.get_entity(handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if viewport.common.owner_handle != paper_block || !viewport.status.is_on {
|
if viewport.common.owner_handle != paper_block || !viewport.is_on() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let mode = render_mode_override.unwrap_or(viewport.render_mode);
|
let mode = render_mode_override.unwrap_or(viewport.render_mode);
|
||||||
|
|||||||
@@ -167,7 +167,11 @@ pub fn sweep_selection_options(profiles: &[EntityType], mut options: SweepOption
|
|||||||
}
|
}
|
||||||
// Spatial profiles keep their own anchor.
|
// Spatial profiles keep their own anchor.
|
||||||
let profiles = profiles.iter().filter(|profile| spatial_profile(profile).is_none()).collect::<Vec<_>>();
|
let profiles = profiles.iter().filter(|profile| spatial_profile(profile).is_none()).collect::<Vec<_>>();
|
||||||
if profiles.is_empty() {
|
// A single profile takes its base from the path it is swept along
|
||||||
|
// (`sweep_record`).
|
||||||
|
// ponytail: several profiles share the group anchor even when the path
|
||||||
|
// starts inside one of them; the reference rule for that case is unmeasured.
|
||||||
|
if profiles.len() < 2 {
|
||||||
return Some(options);
|
return Some(options);
|
||||||
}
|
}
|
||||||
let geometry = profiles.iter().map(|profile| {
|
let geometry = profiles.iter().map(|profile| {
|
||||||
@@ -184,23 +188,7 @@ pub fn sweep_selection_options(profiles: &[EntityType], mut options: SweepOption
|
|||||||
/// The path traversed the other way when it was picked nearer its end
|
/// The path traversed the other way when it was picked nearer its end
|
||||||
/// (measured in plan, as the reference measures the pick), else `None`.
|
/// (measured in plan, as the reference measures the pick), else `None`.
|
||||||
fn reversed_toward_pick(path: &EntityType, pick: glam::DVec3) -> Option<EntityType> {
|
fn reversed_toward_pick(path: &EntityType, pick: glam::DVec3) -> Option<EntityType> {
|
||||||
let (start, end) = match path {
|
let (start, end) = path_ends(path)?;
|
||||||
EntityType::Polyline3D(value) if !value.is_closed() => {
|
|
||||||
let p = |v: &codec::entities::Vertex3DPolyline| glam::DVec3::new(v.position.x, v.position.y, v.position.z);
|
|
||||||
(p(value.vertices.first()?), p(value.vertices.last()?))
|
|
||||||
}
|
|
||||||
EntityType::Spline(value) if !value.flags.closed => {
|
|
||||||
let points = if value.control_points.is_empty() { &value.fit_points } else { &value.control_points };
|
|
||||||
let p = |v: &codec::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
|
|
||||||
(p(points.first()?), p(points.last()?))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let planar = crate::entities::curve::entity_curve(path)?;
|
|
||||||
if planar.curve.is_closed() { return None; }
|
|
||||||
let at = |t: f64| glam::DVec3::from_array(planar.plane.point_at(planar.curve.point_at(t)));
|
|
||||||
(at(0.0), at(1.0))
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let flat = |p: glam::DVec3| p.truncate().distance(pick.truncate());
|
let flat = |p: glam::DVec3| p.truncate().distance(pick.truncate());
|
||||||
if flat(end) >= flat(start) { return None; }
|
if flat(end) >= flat(start) { return None; }
|
||||||
match path {
|
match path {
|
||||||
@@ -220,6 +208,27 @@ fn reversed_toward_pick(path: &EntityType, pick: glam::DVec3) -> Option<EntityTy
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The start and end of an open path.
|
||||||
|
fn path_ends(path: &EntityType) -> Option<(glam::DVec3, glam::DVec3)> {
|
||||||
|
Some(match path {
|
||||||
|
EntityType::Polyline3D(value) if !value.is_closed() => {
|
||||||
|
let p = |v: &codec::entities::Vertex3DPolyline| glam::DVec3::new(v.position.x, v.position.y, v.position.z);
|
||||||
|
(p(value.vertices.first()?), p(value.vertices.last()?))
|
||||||
|
}
|
||||||
|
EntityType::Spline(value) if !value.flags.closed => {
|
||||||
|
let points = if value.control_points.is_empty() { &value.fit_points } else { &value.control_points };
|
||||||
|
let p = |v: &codec::types::Vector3| glam::DVec3::new(v.x, v.y, v.z);
|
||||||
|
(p(points.first()?), p(points.last()?))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let planar = crate::entities::curve::entity_curve(path)?;
|
||||||
|
if planar.curve.is_closed() { return None; }
|
||||||
|
let at = |t: f64| glam::DVec3::from_array(planar.plane.point_at(planar.curve.point_at(t)));
|
||||||
|
(at(0.0), at(1.0))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptions) -> Option<SolidHistorySweep> {
|
pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptions) -> Option<SolidHistorySweep> {
|
||||||
// Placed from the picked end; the record keeps the original path, and
|
// Placed from the picked end; the record keeps the original path, and
|
||||||
// the placed frame tells which end the sweep starts from.
|
// the placed frame tells which end the sweep starts from.
|
||||||
@@ -229,10 +238,10 @@ pub fn sweep_record(profile: &EntityType, path: &EntityType, options: SweepOptio
|
|||||||
return Some(record);
|
return Some(record);
|
||||||
}
|
}
|
||||||
let (sweep_entity, sweep_entity_transform) = embedded_sweep_profile(profile)?;
|
let (sweep_entity, sweep_entity_transform) = embedded_sweep_profile(profile)?;
|
||||||
let (plane, wires, _) = kernel::acis::sweep_profile_geometry(&sweep_entity, sweep_entity_transform).ok()?;
|
kernel::acis::sweep_profile_geometry(&sweep_entity, sweep_entity_transform).ok()?;
|
||||||
let base_point = match options.base_point {
|
let base_point = match options.base_point {
|
||||||
Some(point) => point.to_array(),
|
Some(point) => point.to_array(),
|
||||||
None => kernel::brep::sweep_profile_base(plane, &wires)?,
|
None => kernel::acis::sweep_default_base(&sweep_entity, sweep_entity_transform, &embedded_sweep_path(path)?).ok()?,
|
||||||
};
|
};
|
||||||
let mut base = SolidHistoryNodeBase::new(1);
|
let mut base = SolidHistoryNodeBase::new(1);
|
||||||
base.transform = glam::DMat4::IDENTITY.to_cols_array();
|
base.transform = glam::DMat4::IDENTITY.to_cols_array();
|
||||||
@@ -337,6 +346,195 @@ pub fn link_sweep_expressions(
|
|||||||
crate::scene::view::dispatch::set_entity_xdata(document, handle, SWEEP_EXPRESSION_APP, Some(values));
|
crate::scene::view::dispatch::set_entity_xdata(document, handle, SWEEP_EXPRESSION_APP, Some(values));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// SURFACEASSOCIATIVITY: whether new surfaces stay associative to the
|
||||||
|
/// objects they were made from. A profile setting, so one value for every
|
||||||
|
/// drawing.
|
||||||
|
static SURFACE_ASSOCIATIVITY: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);
|
||||||
|
|
||||||
|
pub fn surface_associativity() -> bool {
|
||||||
|
SURFACE_ASSOCIATIVITY.load(std::sync::atomic::Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_surface_associativity(on: bool) {
|
||||||
|
SURFACE_ASSOCIATIVITY.store(on, std::sync::atomic::Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The application name under which an associative swept surface keeps
|
||||||
|
/// the profile and path it was swept from.
|
||||||
|
pub const SWEEP_SOURCES_APP: &str = "OCS_SWEEP_SOURCES";
|
||||||
|
|
||||||
|
/// Marks a swept surface associative to the profile and path it was swept
|
||||||
|
/// from, with the options it is swept again with when they change; the
|
||||||
|
/// drawing's associative network for it is written on save.
|
||||||
|
pub fn link_sweep_sources(
|
||||||
|
document: &mut codec::CadDocument,
|
||||||
|
surface: codec::Handle,
|
||||||
|
profile: codec::Handle,
|
||||||
|
path: codec::Handle,
|
||||||
|
options: &SweepOptions,
|
||||||
|
) {
|
||||||
|
use codec::xdata::XDataValue;
|
||||||
|
if surface.is_null() { return; }
|
||||||
|
let point = |p: glam::DVec3| XDataValue::Point3D(Vector3::new(p.x, p.y, p.z));
|
||||||
|
// The path keeps the direction it was swept in when it is edited.
|
||||||
|
let reversed = options.path_pick
|
||||||
|
.zip(document.get_entity(path))
|
||||||
|
.is_some_and(|(pick, path)| reversed_toward_pick(path, pick).is_some());
|
||||||
|
let mut values = vec![
|
||||||
|
XDataValue::Handle(profile),
|
||||||
|
XDataValue::Handle(path),
|
||||||
|
XDataValue::Integer16(i16::from(options.align) | (i16::from(options.bank) << 1) | (i16::from(reversed) << 2)),
|
||||||
|
];
|
||||||
|
if let Some(base) = options.base_point {
|
||||||
|
values.push(XDataValue::String("BASE".to_string()));
|
||||||
|
values.push(point(base));
|
||||||
|
}
|
||||||
|
crate::scene::view::dispatch::set_entity_xdata(document, surface, SWEEP_SOURCES_APP, Some(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The profile and path an associative swept surface was swept from.
|
||||||
|
pub fn sweep_sources(entity: &EntityType) -> Option<(codec::Handle, codec::Handle)> {
|
||||||
|
let record = entity.common().extended_data.records().iter()
|
||||||
|
.find(|record| record.application_name == SWEEP_SOURCES_APP)?;
|
||||||
|
let mut handles = record.values.iter().filter_map(|value| match value {
|
||||||
|
codec::xdata::XDataValue::Handle(handle) => Some(*handle),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
Some((handles.next()?, handles.next()?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The surface swept again from its current profile and path, with the
|
||||||
|
/// options it was made with: (surface record, body).
|
||||||
|
pub fn resweep_from_sources(
|
||||||
|
surface: &EntityType,
|
||||||
|
profile: &EntityType,
|
||||||
|
path: &EntityType,
|
||||||
|
) -> Option<(SolidHistorySweep, Body)> {
|
||||||
|
use codec::xdata::XDataValue;
|
||||||
|
let record = surface.common().extended_data.records().iter()
|
||||||
|
.find(|record| record.application_name == SWEEP_SOURCES_APP)?;
|
||||||
|
let EntityType::Surface(value) = surface else { return None };
|
||||||
|
let SurfaceData::Swept { options: stored, .. } = &value.surface_data else { return None };
|
||||||
|
let mut options = SweepOptions {
|
||||||
|
scale: stored.scale_factor,
|
||||||
|
twist_angle: stored.twist_angle,
|
||||||
|
..SweepOptions::default()
|
||||||
|
};
|
||||||
|
let mut values = record.values.iter();
|
||||||
|
while let Some(value) = values.next() {
|
||||||
|
match value {
|
||||||
|
XDataValue::Integer16(flags) => {
|
||||||
|
options.align = flags & 1 != 0;
|
||||||
|
options.bank = flags & 2 != 0;
|
||||||
|
// Swept from the end: picked there.
|
||||||
|
if flags & 4 != 0 {
|
||||||
|
options.path_pick = path_ends(path).map(|(_, end)| end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
XDataValue::String(key) if key == "BASE" => {
|
||||||
|
if let Some(XDataValue::Point3D(p)) = values.next() {
|
||||||
|
options.base_point = Some(glam::DVec3::new(p.x, p.y, p.z));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let record = if spatial_profile(profile).is_some() {
|
||||||
|
spatial_sweep_record(profile, path, options).ok()?
|
||||||
|
} else {
|
||||||
|
sweep_record(profile, path, options)?
|
||||||
|
};
|
||||||
|
let body = kernel::acis::rebuild_sweep_with_mode(&record, true).ok()?;
|
||||||
|
Some((record, body))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An entity's curve as the reference stores it in an edge action
|
||||||
|
/// parameter: line segment (23: start, vector to the end), arc (11:
|
||||||
|
/// centre, normal, reference axis, radius, start and end angle, 0) or a
|
||||||
|
/// composite of those (47: count, then each part's type and values).
|
||||||
|
pub fn assoc_edge_curve(entity: &EntityType) -> Option<(i32, codec::objects::AssocSubcurveKind, Vec<codec::objects::AssocCurveValue>)> {
|
||||||
|
use codec::objects::{AssocCurveValue as V, AssocSubcurveKind as K};
|
||||||
|
use codec::types::Vector3 as P;
|
||||||
|
let point = |p: glam::DVec3| V::Point(P::new(p.x, p.y, p.z));
|
||||||
|
let segment = |a: glam::DVec3, b: glam::DVec3| vec![point(a), point(b - a)];
|
||||||
|
// An arc about `normal` from `start` through `sweep` (signed, about the
|
||||||
|
// normal) on a circle of `radius`; measured from the reference axis.
|
||||||
|
let arc = |centre: glam::DVec3, normal: glam::DVec3, axis: glam::DVec3, radius: f64, start: f64, end: f64| vec![
|
||||||
|
point(centre), point(normal), point(axis), V::Real(radius), V::Real(start), V::Real(end), V::Real(0.0),
|
||||||
|
];
|
||||||
|
match entity {
|
||||||
|
EntityType::Line(line) => {
|
||||||
|
let (a, b) = (glam::DVec3::new(line.start.x, line.start.y, line.start.z), glam::DVec3::new(line.end.x, line.end.y, line.end.z));
|
||||||
|
Some((23, K::LineSegment3d, segment(a, b)))
|
||||||
|
}
|
||||||
|
EntityType::Circle(circle) => {
|
||||||
|
let normal = glam::DVec3::new(circle.normal.x, circle.normal.y, circle.normal.z).try_normalize()?;
|
||||||
|
let axis = arbitrary_x(normal);
|
||||||
|
Some((11, K::Arc, arc(glam::DVec3::new(circle.center.x, circle.center.y, circle.center.z), normal, axis, circle.radius, 0.0, std::f64::consts::TAU)))
|
||||||
|
}
|
||||||
|
EntityType::Arc(value) => {
|
||||||
|
let normal = glam::DVec3::new(value.normal.x, value.normal.y, value.normal.z).try_normalize()?;
|
||||||
|
let axis = arbitrary_x(normal);
|
||||||
|
// The end past the start by the arc's sweep; an equal pair is a
|
||||||
|
// full turn. (A loop adding TAU never ends on a huge angle.)
|
||||||
|
let sweep = (value.end_angle - value.start_angle).rem_euclid(std::f64::consts::TAU);
|
||||||
|
let end = value.start_angle + if sweep > 0.0 { sweep } else { std::f64::consts::TAU };
|
||||||
|
Some((11, K::Arc, arc(glam::DVec3::new(value.center.x, value.center.y, value.center.z), normal, axis, value.radius, value.start_angle, end)))
|
||||||
|
}
|
||||||
|
EntityType::LwPolyline(polyline) => {
|
||||||
|
let normal = glam::DVec3::new(polyline.normal.x, polyline.normal.y, polyline.normal.z).try_normalize()?;
|
||||||
|
let axis_x = arbitrary_x(normal);
|
||||||
|
let axis_y = normal.cross(axis_x);
|
||||||
|
let at = |v: &codec::entities::LwVertex| axis_x * v.location.x + axis_y * v.location.y + normal * polyline.elevation;
|
||||||
|
let count = polyline.vertices.len();
|
||||||
|
let spans = if polyline.is_closed { count } else { count.saturating_sub(1) };
|
||||||
|
if spans == 0 { return None; }
|
||||||
|
let mut values = vec![V::Int(spans as i32)];
|
||||||
|
for index in 0..spans {
|
||||||
|
let (from, to) = (&polyline.vertices[index], &polyline.vertices[(index + 1) % count]);
|
||||||
|
let (a, b) = (at(from), at(to));
|
||||||
|
if from.bulge.abs() <= 1e-12 {
|
||||||
|
values.push(V::Int(23));
|
||||||
|
values.extend(segment(a, b));
|
||||||
|
} else {
|
||||||
|
// The arc in the polyline's plane; a clockwise one turns about
|
||||||
|
// the opposite normal, from the same reference axis.
|
||||||
|
let bulge = from.bulge;
|
||||||
|
let chord = b - a;
|
||||||
|
let centre = a + chord * 0.5 + normal.cross(chord) * ((1.0 - bulge * bulge) / (4.0 * bulge));
|
||||||
|
let radius = chord.length() * (1.0 + bulge * bulge) / (4.0 * bulge.abs());
|
||||||
|
let turn = normal * bulge.signum();
|
||||||
|
let across = turn.cross(axis_x);
|
||||||
|
let angle = |p: glam::DVec3| (p - centre).dot(across).atan2((p - centre).dot(axis_x));
|
||||||
|
let start = angle(a);
|
||||||
|
let end = start + 4.0 * bulge.atan().abs();
|
||||||
|
values.push(V::Int(11));
|
||||||
|
values.extend(arc(centre, turn, axis_x, radius, start, end));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some((47, K::None, values))
|
||||||
|
}
|
||||||
|
EntityType::Polyline3D(polyline) => {
|
||||||
|
let points = polyline.vertices.iter().map(|v| glam::DVec3::new(v.position.x, v.position.y, v.position.z)).collect::<Vec<_>>();
|
||||||
|
let spans = if polyline.is_closed() { points.len() } else { points.len().saturating_sub(1) };
|
||||||
|
if spans == 0 { return None; }
|
||||||
|
let mut values = vec![V::Int(spans as i32)];
|
||||||
|
for index in 0..spans {
|
||||||
|
values.push(V::Int(23));
|
||||||
|
values.extend(segment(points[index], points[(index + 1) % points.len()]));
|
||||||
|
}
|
||||||
|
Some((47, K::None, values))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The arbitrary-axis X direction of an entity normal.
|
||||||
|
fn arbitrary_x(normal: glam::DVec3) -> glam::DVec3 {
|
||||||
|
let world = if normal.x.abs() < 1.0 / 64.0 && normal.y.abs() < 1.0 / 64.0 { glam::DVec3::Y } else { glam::DVec3::Z };
|
||||||
|
world.cross(normal).normalize()
|
||||||
|
}
|
||||||
|
|
||||||
/// The scale and twist expressions a swept surface is linked to.
|
/// The scale and twist expressions a swept surface is linked to.
|
||||||
pub fn sweep_expressions(entity: &EntityType) -> Option<[Option<String>; 2]> {
|
pub fn sweep_expressions(entity: &EntityType) -> Option<[Option<String>; 2]> {
|
||||||
let record = entity.common().extended_data.records().iter()
|
let record = entity.common().extended_data.records().iter()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ use codec::EntityType;
|
|||||||
|
|
||||||
use crate::entities::curve::entity_curve;
|
use crate::entities::curve::entity_curve;
|
||||||
pub use super::sweep_command_model::{
|
pub use super::sweep_command_model::{
|
||||||
is_sweep_path, is_sweep_profile, scaled_sweep_path_refusal, spatial_profile, spatial_sweep_record, surface_sweep_record, sweep_expressions, link_sweep_expressions, sweep_record, sweep_refusal_code, sweep_selection_options, swept_surface_entity, swept_with_options,
|
is_sweep_path, is_sweep_profile, scaled_sweep_path_refusal, spatial_profile, spatial_sweep_record, surface_sweep_record, sweep_expressions, link_sweep_expressions, link_sweep_sources, sweep_sources, SWEEP_SOURCES_APP, resweep_from_sources, surface_associativity, set_surface_associativity, assoc_edge_curve, sweep_record, sweep_refusal_code, sweep_selection_options, swept_surface_entity, swept_with_options,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// A drawn profile, as the kernel wants it: the plane it lies in and the
|
/// A drawn profile, as the kernel wants it: the plane it lies in and the
|
||||||
|
|||||||
@@ -1130,6 +1130,54 @@ impl Scene {
|
|||||||
rebuilt
|
rebuilt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Associative swept surfaces follow edits of their profile or path:
|
||||||
|
/// swept again from them with the options they were made with. A
|
||||||
|
/// surface edited along with its sources keeps its own edit. Returns the
|
||||||
|
/// surfaces rebuilt.
|
||||||
|
pub(crate) fn refresh_associative_sweeps(&mut self, changes: &[(Handle, crate::scene::ChangeKind)]) -> Vec<(Handle, crate::scene::ChangeKind)> {
|
||||||
|
use crate::scene::model::sweep_model;
|
||||||
|
let changed = changes.iter()
|
||||||
|
.filter(|(_, kind)| !matches!(kind, crate::scene::ChangeKind::Removed))
|
||||||
|
.map(|(handle, _)| *handle)
|
||||||
|
.collect::<rustc_hash::FxHashSet<_>>();
|
||||||
|
// Every edit passes through here: a drawing with no associative
|
||||||
|
// swept surface (no application registered for them) skips the scan.
|
||||||
|
if changed.is_empty() || !self.document.app_ids.contains(sweep_model::SWEEP_SOURCES_APP) {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let linked = self.document.entities()
|
||||||
|
.filter(|entity| matches!(entity, EntityType::Surface(_)))
|
||||||
|
.filter_map(|entity| {
|
||||||
|
let handle = entity.common().handle;
|
||||||
|
let (profile, path) = sweep_model::sweep_sources(entity)?;
|
||||||
|
(!changed.contains(&handle) && (changed.contains(&profile) || changed.contains(&path)))
|
||||||
|
.then_some((handle, profile, path))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let mut rebuilt = Vec::new();
|
||||||
|
for (handle, profile, path) in linked {
|
||||||
|
let (Some(surface), Some(profile), Some(path)) = (
|
||||||
|
self.document.get_entity(handle),
|
||||||
|
self.document.get_entity(profile),
|
||||||
|
self.document.get_entity(path),
|
||||||
|
) else { continue };
|
||||||
|
let Some((record, body)) = sweep_model::resweep_from_sources(surface, profile, path) else { continue };
|
||||||
|
let EntityType::Surface(fresh) = sweep_model::swept_surface_entity(&record) else { continue };
|
||||||
|
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&body) else { continue };
|
||||||
|
let Some(display) = self.prepare_solid_model_display(handle, &body) else { continue };
|
||||||
|
let before = self.document.get_entity(handle).cloned().map(std::sync::Arc::new);
|
||||||
|
self.record_undo_before(handle, before);
|
||||||
|
if let Some(EntityType::Surface(surface)) = self.document.get_entity_mut(handle) {
|
||||||
|
surface.acis_data = codec::entities::AcisData::from_sat(&document.to_sat_string());
|
||||||
|
surface.surface_data = fresh.surface_data;
|
||||||
|
surface.point_of_reference = fresh.point_of_reference;
|
||||||
|
}
|
||||||
|
self.register_prepared_solid_model(handle, body, display);
|
||||||
|
rebuilt.push((handle, crate::scene::ChangeKind::Modified));
|
||||||
|
}
|
||||||
|
rebuilt
|
||||||
|
}
|
||||||
|
|
||||||
/// Swept surfaces the reference keeps associative to a scale or twist
|
/// Swept surfaces the reference keeps associative to a scale or twist
|
||||||
/// expression (an associative swept surface action whose value parameter
|
/// expression (an associative swept surface action whose value parameter
|
||||||
/// depends on an expression variable) are linked the same way here.
|
/// depends on an expression variable) are linked the same way here.
|
||||||
@@ -1140,6 +1188,7 @@ impl Scene {
|
|||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
let mut links = Vec::new();
|
let mut links = Vec::new();
|
||||||
|
let mut sources = Vec::new();
|
||||||
for item in self.document.objects.values() {
|
for item in self.document.objects.values() {
|
||||||
let ObjectType::Associative(action) = item else { continue };
|
let ObjectType::Associative(action) = item else { continue };
|
||||||
let AssociativeData::Action(action) = &action.data else { continue };
|
let AssociativeData::Action(action) = &action.data else { continue };
|
||||||
@@ -1160,6 +1209,32 @@ impl Scene {
|
|||||||
if expressions.iter().any(Option::is_some) {
|
if expressions.iter().any(Option::is_some) {
|
||||||
links.push((written.dependent_on, expressions));
|
links.push((written.dependent_on, expressions));
|
||||||
}
|
}
|
||||||
|
// The profile and path the action reads.
|
||||||
|
let source = |name: &str| {
|
||||||
|
action.owned_parameters.iter().find_map(|parameter| {
|
||||||
|
let Some(AssociativeData::PathActionParam(path)) = object(*parameter) else { return None };
|
||||||
|
if path.compound.action_param.name != name { return None; }
|
||||||
|
let Some(AssociativeData::EdgeActionParam(edge)) = object(*path.compound.parameters.first()?) else { return None };
|
||||||
|
match object(edge.single_dependency.dependency) {
|
||||||
|
Some(AssociativeData::Dependency(dependency)) => Some(dependency.dependent_on),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
if let (Some(profile), Some(path)) = (source("SweepProfile"), source("SweepPath")) {
|
||||||
|
sources.push((written.dependent_on, profile, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (surface, profile, path) in sources {
|
||||||
|
let Some(entity @ EntityType::Surface(value)) = self.document.get_entity(surface) else { continue };
|
||||||
|
if crate::scene::model::sweep_model::sweep_sources(entity).is_some() { continue; }
|
||||||
|
let codec::entities::SurfaceData::Swept { options, .. } = &value.surface_data else { continue };
|
||||||
|
let linked = crate::command::SweepOptions {
|
||||||
|
align: options.sweep_alignment_flags == 1,
|
||||||
|
bank: options.bank,
|
||||||
|
..crate::command::SweepOptions::default()
|
||||||
|
};
|
||||||
|
crate::scene::model::sweep_model::link_sweep_sources(&mut self.document, surface, profile, path, &linked);
|
||||||
}
|
}
|
||||||
for (handle, expressions) in links {
|
for (handle, expressions) in links {
|
||||||
let Some(entity) = self.document.get_entity(handle) else { continue };
|
let Some(entity) = self.document.get_entity(handle) else { continue };
|
||||||
|
|||||||
+3
-3
@@ -801,7 +801,7 @@ impl Scene {
|
|||||||
let Some(EntityType::Viewport(vp)) = self.document.get_entity(*handle) else {
|
let Some(EntityType::Viewport(vp)) = self.document.get_entity(*handle) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if !vp.status.is_on {
|
if !vp.is_on() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let rect = self.viewport_screen_rect(vp.common.handle, canvas)?;
|
let rect = self.viewport_screen_rect(vp.common.handle, canvas)?;
|
||||||
@@ -838,7 +838,7 @@ impl Scene {
|
|||||||
pub fn viewport_displays_content(&self, vp_handle: Handle) -> bool {
|
pub fn viewport_displays_content(&self, vp_handle: Handle) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
self.document.get_entity(vp_handle),
|
self.document.get_entity(vp_handle),
|
||||||
Some(EntityType::Viewport(vp)) if vp.status.is_on
|
Some(EntityType::Viewport(vp)) if vp.is_on()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -850,7 +850,7 @@ impl Scene {
|
|||||||
let Some(EntityType::Viewport(vp)) = self.document.get_entity(*handle) else {
|
let Some(EntityType::Viewport(vp)) = self.document.get_entity(*handle) else {
|
||||||
return None;
|
return None;
|
||||||
};
|
};
|
||||||
if vp.status.is_on {
|
if vp.is_on() {
|
||||||
Some(vp.common.handle)
|
Some(vp.common.handle)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
+1
-1
@@ -194,7 +194,7 @@ impl Scene {
|
|||||||
let Some(EntityType::Viewport(vp)) = self.document.get_entity(handle) else {
|
let Some(EntityType::Viewport(vp)) = self.document.get_entity(handle) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
if !vp.status.is_on
|
if !vp.is_on()
|
||||||
|| self.entity_temporarily_hidden(handle)
|
|| self.entity_temporarily_hidden(handle)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ impl Scene {
|
|||||||
})
|
})
|
||||||
.filter(|vp| {
|
.filter(|vp| {
|
||||||
vp.common.owner_handle == paper_block
|
vp.common.owner_handle == paper_block
|
||||||
&& vp.status.is_on
|
&& vp.is_on()
|
||||||
&& only_vp.map_or(true, |h| vp.common.handle == h)
|
&& only_vp.map_or(true, |h| vp.common.handle == h)
|
||||||
&& exclude_vp.map_or(true, |h| vp.common.handle != h)
|
&& exclude_vp.map_or(true, |h| vp.common.handle != h)
|
||||||
})
|
})
|
||||||
@@ -439,7 +439,7 @@ impl Scene {
|
|||||||
.iter()
|
.iter()
|
||||||
.filter_map(|handle| match self.document.get_entity(*handle) {
|
.filter_map(|handle| match self.document.get_entity(*handle) {
|
||||||
Some(EntityType::Viewport(viewport))
|
Some(EntityType::Viewport(viewport))
|
||||||
if viewport.common.owner_handle == paper_block && viewport.status.is_on =>
|
if viewport.common.owner_handle == paper_block && viewport.is_on() =>
|
||||||
{
|
{
|
||||||
Some(viewport)
|
Some(viewport)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ pub struct AppPrefs {
|
|||||||
pub zoom_wheel_reversed: bool,
|
pub zoom_wheel_reversed: bool,
|
||||||
/// Scroll pans and Ctrl+scroll zooms (touchpads).
|
/// Scroll pans and Ctrl+scroll zooms (touchpads).
|
||||||
pub touchpad_pan: bool,
|
pub touchpad_pan: bool,
|
||||||
|
/// Interface scale in percent.
|
||||||
|
pub ui_scale_percent: u16,
|
||||||
/// ZOOMFACTOR. The slider sets the range the system variable has; the
|
/// ZOOMFACTOR. The slider sets the range the system variable has; the
|
||||||
/// field beside it reaches `settings::ZOOM_FACTOR_MAX`.
|
/// field beside it reaches `settings::ZOOM_FACTOR_MAX`.
|
||||||
pub zoom_factor: i32,
|
pub zoom_factor: i32,
|
||||||
@@ -679,6 +681,23 @@ pub fn view_window<'a>(
|
|||||||
.width(sizing.width),
|
.width(sizing.width),
|
||||||
Space::new().height(12),
|
Space::new().height(12),
|
||||||
color_controls,
|
color_controls,
|
||||||
|
Space::new().height(16),
|
||||||
|
// A list, not a slider: the dialog rescales under the cursor as the
|
||||||
|
// value changes, which would drag a slider along with it.
|
||||||
|
row![
|
||||||
|
text(crate::t!("Interface scale")).size(12).width(150),
|
||||||
|
iced::widget::pick_list(
|
||||||
|
Some(format!("{}%", prefs.ui_scale_percent)),
|
||||||
|
[75u16, 100, 125, 150, 175, 200].map(|p| format!("{p}%")).to_vec(),
|
||||||
|
|label: &String| label.clone(),
|
||||||
|
)
|
||||||
|
.on_select(|label: String| {
|
||||||
|
Message::UiScaleChanged(label.trim_end_matches('%').parse().unwrap_or(100))
|
||||||
|
})
|
||||||
|
.width(sizing.width),
|
||||||
|
]
|
||||||
|
.spacing(12)
|
||||||
|
.align_y(iced::Center),
|
||||||
Space::new().height(24),
|
Space::new().height(24),
|
||||||
row![
|
row![
|
||||||
text(crate::t!("Model Space Appearance")).size(15),
|
text(crate::t!("Model Space Appearance")).size(15),
|
||||||
|
|||||||
Reference in new issue
Block a user