Merge remote-tracking branch 'upstream/main'
# Conflicts: # src/app/update/file.rs # src/scene/convert/solid3d_tess.rs
4
Cargo.lock
generated
|
|
@ -72,7 +72,7 @@ checksum = "366ffbaa4442f4684d91e2cd7c5ea7c4ed8add41959a31447066e279e432b618"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "acadrust"
|
name = "acadrust"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
source = "git+https://github.com/HakanSeven12/cadcodec.git?rev=db73dab#db73dab540dbfb690c93f4416014262e6cf7bf42"
|
source = "git+https://git@github.com/HakanSeven12/cadcodec.git?rev=788eea0161ba9f3eb7ee6569fd8bb52a8207a2ed#788eea0161ba9f3eb7ee6569fd8bb52a8207a2ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ahash 0.8.12",
|
"ahash 0.8.12",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
|
@ -878,7 +878,7 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cadkernel"
|
name = "cadkernel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=895f8b7#895f8b7b4d53845608689ead46c1a122e352b79b"
|
source = "git+https://github.com/HakanSeven12/cadkernel.git?rev=b2b1d4b#b2b1d4b0cd7def2d26a0d519991fc89a89f916d3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"acadrust",
|
"acadrust",
|
||||||
"cavalier_contours",
|
"cavalier_contours",
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,8 @@ glam = { version = "0.33", features = ["bytemuck"] }
|
||||||
rfd = "0.17"
|
rfd = "0.17"
|
||||||
clap = { version = "4", features = ["derive"] }
|
clap = { version = "4", features = ["derive"] }
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "db73dab", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "788eea0161ba9f3eb7ee6569fd8bb52a8207a2ed", features = ["serde"] }
|
||||||
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "895f8b7", features = ["acis", "offset"] }
|
cadkernel = { git = "https://github.com/HakanSeven12/cadkernel.git", rev = "b2b1d4b", features = ["acis", "offset"] }
|
||||||
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
dwg-thumbnailer = { path = "crates/dwg-thumbnailer" }
|
||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg", "bmp", "tiff"] }
|
||||||
|
|
@ -60,6 +60,9 @@ ashpd = { version = "0.13.13", default-features = false, features = ["async-io",
|
||||||
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_core = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
iced_widget = { git = "https://github.com/iced-rs/iced.git", rev = "23604ff22ab0aad9e00b9327cb7b8546ed84db39" }
|
||||||
|
|
||||||
|
[patch."https://github.com/HakanSeven12/cadcodec.git"]
|
||||||
|
acadrust = { git = "https://git@github.com/HakanSeven12/cadcodec.git", rev = "788eea0161ba9f3eb7ee6569fd8bb52a8207a2ed" }
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
|
ocs_plugin_api = { path = "crates/ocs_plugin_api", features = ["host"] }
|
||||||
meshopt = "0.6.2"
|
meshopt = "0.6.2"
|
||||||
|
|
|
||||||
4
assets/icons/extrude.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path d="M4 16 10 13 16 16 10 19Z" fill="none" stroke="#e0e0e0" stroke-width="1.7" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 13V5M7 8l3-3 3 3" fill="none" stroke="#4cc9f0" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 332 B |
4
assets/icons/loft.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path d="M5 18 12 21 19 18 16 6 12 3 8 6Z" fill="none" stroke="#e0e0e0" stroke-width="1.7" stroke-linejoin="round"/>
|
||||||
|
<path d="M8 6h8M5 18h14" stroke="#4cc9f0" stroke-width="1.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 275 B |
4
assets/icons/polysolid.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path d="M3 17 8 12 13 16 21 8V14L13 22 8 18 3 23Z" fill="none" stroke="#e0e0e0" stroke-width="1.7" stroke-linejoin="round"/>
|
||||||
|
<path d="M3 17V23M8 12V18M13 16V22M21 8V14" stroke="#4cc9f0" stroke-width="1.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 303 B |
4
assets/icons/presspull.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path d="M4 17 10 14 16 17 10 20Z" fill="none" stroke="#e0e0e0" stroke-width="1.7" stroke-linejoin="round"/>
|
||||||
|
<path d="M10 14V5M7 8l3-3 3 3M18 5v9m-2-3 2 3 2-3" fill="none" stroke="#4cc9f0" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 352 B |
4
assets/icons/pyramid3d.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path d="M12 3 3 18 12 22 21 18Z" fill="none" stroke="#e0e0e0" stroke-width="1.7" stroke-linejoin="round"/>
|
||||||
|
<path d="M12 3V22M3 18H21" fill="none" stroke="#4cc9f0" stroke-width="1.4"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 280 B |
4
assets/icons/revolve.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<path d="M8 18V6M8 7c7-2 11 1 11 5s-4 7-11 5" fill="none" stroke="#e0e0e0" stroke-width="1.7" stroke-linecap="round"/>
|
||||||
|
<path d="m16 8 3 4-4 2" fill="none" stroke="#4cc9f0" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 335 B |
4
assets/icons/sweep.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="64" height="64">
|
||||||
|
<circle cx="6" cy="17" r="3" fill="none" stroke="#e0e0e0" stroke-width="1.7"/>
|
||||||
|
<path d="M8 15c3-6 6-8 11-8M16 5l3 2-2 3" fill="none" stroke="#4cc9f0" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 313 B |
169
assets/logo.svg
|
|
@ -1,150 +1,21 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg width="920" height="920" viewBox="0 0 920 920" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<svg
|
<g clip-path="url(#clip0_12_11)">
|
||||||
viewBox="0 0 48 48"
|
<rect width="920" height="920" fill="#1C2430"/>
|
||||||
version="1.1"
|
<path d="M166.001 472.8C149.001 468.7 133.301 455.5 125.001 438.4C118.801 425.7 119.001 428.6 119.001 336.7V252.7L121.801 244.4C129.001 223.7 147.401 207 168.001 202.7C173.301 201.6 182.601 201.2 205.801 201.2H236.501L236.301 228L236.001 254.7L211.001 255.2C183.801 255.7 182.901 255.9 177.301 261.9C171.301 268.1 171.501 266.1 171.501 337.3C171.501 383.2 171.801 403.6 172.601 406.3C174.201 411.5 179.001 417.1 183.901 419.3C187.501 420.9 190.801 421.2 212.201 421.2C225.501 421.2 236.501 421.5 236.701 422C236.901 422.4 236.901 434.3 236.701 448.5L236.301 474.2L203.701 474.1C181.601 474.1 169.401 473.6 166.001 472.8Z" fill="#327DE8"/>
|
||||||
id="svg8"
|
<path d="M250.501 473.8C250.101 473.4 250.001 458.8 250.401 428.5L250.501 421.2H274.501C300.701 421.2 303.201 420.8 308.801 415.4C310.401 413.9 312.701 410.9 313.801 408.7C316.001 404.7 316.001 404.4 316.001 338.2V271.7L313.801 267.5C312.501 265.2 310.301 262.1 308.801 260.6C303.401 255.5 301.501 255.2 275.001 255.2H250.501V228.1V201L284.301 201.4C315.601 201.8 318.501 202 324.501 204C343.501 210.3 359.801 226.8 365.801 245.7C368.001 252.7 368.001 252.8 368.001 338.2V423.7L365.301 431C358.301 450 345.101 463.7 327.001 470.9L320.001 473.7L285.501 474C266.501 474.2 250.701 474.1 250.501 473.8Z" fill="#327DE8"/>
|
||||||
sodipodi:docname="logo.svg"
|
<path d="M631.575 473C631.275 472.2 631.175 460.9 631.275 447.7L631.575 423.7L693.075 423.2C762.275 422.6 758.575 423.1 765.675 414.9C770.475 409.2 771.275 405.6 770.875 391.1C770.575 379.4 770.375 378.3 767.875 374.4C766.375 372.2 762.975 369 760.375 367.5L755.575 364.7L719.075 364.1C690.575 363.6 681.075 363.1 675.775 361.8C652.475 356.3 632.975 337 627.575 314C625.475 305 625.475 261.1 627.575 252.7C632.875 231.7 647.675 215.5 668.875 207.4L675.575 204.8L741.575 204.2C789.775 203.8 807.875 204 808.575 204.8C809.175 205.5 809.575 215.3 809.475 230.3L809.375 254.7L751.475 255.2L693.575 255.7L689.875 258C684.775 261.2 680.375 266.5 679.175 270.9C678.575 273 678.075 279.2 678.075 284.7C678.075 296.1 679.675 300.6 685.175 305.9C691.675 312.1 692.575 312.2 729.275 312.2C753.275 312.3 764.775 312.7 770.375 313.7C790.575 317.3 810.275 332.7 818.475 351.3C823.075 361.8 824.075 369.3 824.075 393.4C824.075 419.2 823.075 425.6 817.275 437.7C809.475 453.9 796.175 465.4 778.075 471.5C771.675 473.7 770.775 473.7 701.775 474C645.075 474.2 631.975 474 631.575 473Z" fill="#F7F7F8"/>
|
||||||
inkscape:version="1.4.4 (dcaf3e7d9e, 2026-05-05)"
|
<path d="M450.3 472.1C439 469.1 429.7 463.7 421 455.1C412 446.2 407.2 437.9 403.6 425.6L401 416.5L401.3 336.1C401.7 256.8 401.7 255.5 403.8 249.3C410.3 230 425.1 214.9 444.9 207.2L451.6 204.6L522.4 204.3L593.1 204V229.6V255.1H533.4C497 255.1 472.2 255.5 470 256.1C465.2 257.4 458.2 263.6 456 268.5C454.3 272.4 454.1 276.6 454.1 339.1C454.1 401.6 454.3 405.8 456 409.7C458.3 414.6 464.8 420.4 470 421.9C472.7 422.8 491.1 423.1 533.5 423.1H593.1V448.6V474.1L525.4 474C462 474 457.1 473.9 450.3 472.1Z" fill="#F7F7F8"/>
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
<rect x="672" y="517" width="12" height="81" fill="white"/>
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
<rect x="672" y="691" width="12" height="81" fill="white"/>
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
<rect x="725.5" y="650" width="12" height="93" transform="rotate(-90 725.5 650)" fill="white"/>
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
<rect x="537.5" y="650" width="12" height="93" transform="rotate(-90 537.5 650)" fill="white"/>
|
||||||
<defs
|
<rect x="649" y="616" width="58" height="57" stroke="#337CE5" stroke-width="18"/>
|
||||||
id="defs8" />
|
<rect x="-193" y="1079.79" width="377.615" height="972" transform="rotate(-77.5907 -193 1079.79)" fill="#141D26"/>
|
||||||
<sodipodi:namedview
|
<rect x="-450" y="699.014" width="377.615" height="972" transform="rotate(-45 -450 699.014)" fill="#327DE8"/>
|
||||||
id="namedview8"
|
</g>
|
||||||
pagecolor="#505050"
|
<defs>
|
||||||
bordercolor="#eeeeee"
|
<clipPath id="clip0_12_11">
|
||||||
borderopacity="1"
|
<rect width="920" height="920" fill="white"/>
|
||||||
inkscape:showpageshadow="0"
|
</clipPath>
|
||||||
inkscape:pageopacity="0"
|
</defs>
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#505050"
|
|
||||||
inkscape:zoom="14.27082"
|
|
||||||
inkscape:cx="10.370813"
|
|
||||||
inkscape:cy="24.630681"
|
|
||||||
inkscape:window-width="1920"
|
|
||||||
inkscape:window-height="939"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg8" />
|
|
||||||
<!-- Background -->
|
|
||||||
<rect
|
|
||||||
width="48"
|
|
||||||
height="48"
|
|
||||||
rx="6"
|
|
||||||
fill="#B03020"
|
|
||||||
id="rect1" />
|
|
||||||
<!-- O -->
|
|
||||||
<line
|
|
||||||
x1="6.8919725"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="14.891973"
|
|
||||||
y2="9.7197075"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="o-top" />
|
|
||||||
<line
|
|
||||||
x1="6.8919725"
|
|
||||||
y1="37.719707"
|
|
||||||
x2="14.891973"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="o-bot" />
|
|
||||||
<line
|
|
||||||
x1="6.8919725"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="6.8919725"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="o-left" />
|
|
||||||
<line
|
|
||||||
x1="14.891973"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="14.891973"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="o-right" />
|
|
||||||
<!-- C -->
|
|
||||||
<line
|
|
||||||
x1="19.891972"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="27.891972"
|
|
||||||
y2="9.7197075"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="c-top" />
|
|
||||||
<line
|
|
||||||
x1="19.891972"
|
|
||||||
y1="37.719707"
|
|
||||||
x2="27.891972"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="c-bot" />
|
|
||||||
<line
|
|
||||||
x1="19.891972"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="19.891972"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="c-left" />
|
|
||||||
<!-- S -->
|
|
||||||
<line
|
|
||||||
x1="32.891972"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="40.891972"
|
|
||||||
y2="9.7197075"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="s-top" />
|
|
||||||
<line
|
|
||||||
x1="32.891972"
|
|
||||||
y1="9.7197075"
|
|
||||||
x2="32.891972"
|
|
||||||
y2="23.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="s-tl" />
|
|
||||||
<line
|
|
||||||
x1="32.891972"
|
|
||||||
y1="23.719707"
|
|
||||||
x2="40.891972"
|
|
||||||
y2="23.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="s-mid" />
|
|
||||||
<line
|
|
||||||
x1="40.891972"
|
|
||||||
y1="23.719707"
|
|
||||||
x2="40.891972"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="s-br" />
|
|
||||||
<line
|
|
||||||
x1="32.891972"
|
|
||||||
y1="37.719707"
|
|
||||||
x2="40.891972"
|
|
||||||
y2="37.719707"
|
|
||||||
stroke="#ffffff"
|
|
||||||
stroke-width="3"
|
|
||||||
stroke-linecap="round"
|
|
||||||
id="s-bot" />
|
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.7 KiB |
|
|
@ -21,6 +21,7 @@ SPL, *SPLINE
|
||||||
H, *HATCH
|
H, *HATCH
|
||||||
BH, *HATCH
|
BH, *HATCH
|
||||||
HE, *HATCHEDIT
|
HE, *HATCHEDIT
|
||||||
|
HB, *HATCHTOBACK
|
||||||
BO, *BOUNDARY
|
BO, *BOUNDARY
|
||||||
GD, *GRADIENT
|
GD, *GRADIENT
|
||||||
XL, *XLINE
|
XL, *XLINE
|
||||||
|
|
|
||||||
264
crates/ocs_plugin_api/ARCHITECTURE.md
Normal file
|
|
@ -0,0 +1,264 @@
|
||||||
|
# `ocs_plugin_api` — Internal Architecture
|
||||||
|
|
||||||
|
Internal-facing architecture documentation for `crates/ocs_plugin_api`. For plugin-author tutorials see [`README.md`](./README.md); for host/plugin integration design goals see [`../../docs/plugin-architecture.md`](../../docs/plugin-architecture.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`ocs_plugin_api` is the stable, semver-versioned contract between the Open CAD Studio host and add-on plugins. The crate is intentionally split into two tiers:
|
||||||
|
|
||||||
|
| Tier | Feature | Dependencies | Purpose |
|
||||||
|
|------|---------|--------------|---------|
|
||||||
|
| **Core** | default | none | `PluginManifest`, `ApiVersion`, ribbon vocabulary, type registry, version info. Engine crates and external tooling can depend on this cheaply. |
|
||||||
|
| **Runtime host surface** | `host` | `acadrust`, `interprocess`, `memmap2`, `rkyv`, etc. | `HostApi`, `BuiltinPlugin`, out-of-process spawn, IPC, shared-memory snapshots, runner. |
|
||||||
|
|
||||||
|
The host and each plugin run in separate OS processes. The host re-executes itself in `--ocs-plugin-runner` mode, the runner dynamically loads the plugin `cdylib`, and the two sides talk over a local socket. Process isolation keeps plugin crashes from affecting the host or other plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module map
|
||||||
|
|
||||||
|
| Module | Responsibility |
|
||||||
|
|--------|----------------|
|
||||||
|
| [`src/manifest.rs`](src/manifest.rs) | `API_VERSION`, `API_VERSION_MIN_SUPPORTED`, `ApiVersion` compatibility, runtime `OCS_PLUGIN_MAX_API_VERSION` gate. |
|
||||||
|
| [`src/ribbon`](src/ribbon) | `CadModule` trait and plain-data ribbon types (`RibbonGroup`, `ToolDef`, `IconKind`, `ModuleEvent`). |
|
||||||
|
| [`src/host.rs`](src/host.rs) | `HostApi` plugin-facing runtime trait, `BuiltinPlugin` entry-point trait, `export_plugin!` macro, `HostNotification`, `PluginNotification`. |
|
||||||
|
| [`src/ipc/mod.rs`](src/ipc/mod.rs) | IPC layer public exports. |
|
||||||
|
| [`src/ipc/protocol.rs`](src/ipc/protocol.rs) | V2/V3 request/response enums (`HostRequest`, `HostResponse`, `PluginRequest`, `PluginResponse`), `RunnerHandshake`, `PLUGIN_TOKEN_ENV`. |
|
||||||
|
| [`src/ipc/transport.rs`](src/ipc/transport.rs) | Length-framed bincode send/recv over `interprocess::local_socket::Stream`. |
|
||||||
|
| [`src/ipc/client.rs`](src/ipc/client.rs) | Plugin-side V2/V3 `IpcClient` and `PluginHostApi` proxy. |
|
||||||
|
| [`src/ipc/server.rs`](src/ipc/server.rs) | Host-side handler that applies a `PluginRequest` to a `HostApi` implementation. |
|
||||||
|
| [`src/ipc/v4`](src/ipc/v4) | Multiplexed V4 protocol: frames, notifications, correlation ids. |
|
||||||
|
| [`src/ipc/proxy.rs`](src/ipc/proxy.rs) | Optional TCP request proxy for plugin child processes. |
|
||||||
|
| [`src/process.rs`](src/process.rs) | `PluginProcess` lifecycle: spawn, handshake, call, timeouts, `NullHost`, io draining. |
|
||||||
|
| [`src/process/manager.rs`](src/process/manager.rs) | `PluginManager`: owns loaded plugins, dispatch routing, notification broadcast, V4 gating. |
|
||||||
|
| [`src/process/v4.rs`](src/process/v4.rs) | Host-side V4 connection and reader thread. |
|
||||||
|
| [`src/runner.rs`](src/runner.rs) | Runner entry point and V2/V3 vs V4 request loops; loads cdylib via `libloading`. |
|
||||||
|
| [`src/shm.rs`](src/shm.rs) | Shared-memory document snapshot layout, `DocumentSnapshotStore`, `SharedDocumentReader`, V3/V4 view data. |
|
||||||
|
| [`src/host_v4.rs`](src/host_v4.rs) | Host-side per-tab V4 snapshot manager. |
|
||||||
|
| [`src/type_registry.rs`](src/type_registry.rs) / [`src/type_registry_types.rs`](src/type_registry_types.rs) | Build-time `serde-reflection` registry, embedded JSON, schema types. |
|
||||||
|
| [`src/version_info.rs`](src/version_info.rs) | Embedded version metadata and acadrust source-compatibility helper. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Versioning and ABI stability
|
||||||
|
|
||||||
|
- `API_VERSION` (currently `4`) is the host's advertised major.
|
||||||
|
- `API_VERSION_MIN_SUPPORTED` (currently `2`) is the oldest plugin major the host loads.
|
||||||
|
- `OCS_PLUGIN_MAX_API_VERSION` can cap the accepted major at runtime (e.g. `3` to disable V4).
|
||||||
|
- A plugin built against major `N` runs on a host whose major is `>= N` because new vtable entries and enum variants are appended at the end.
|
||||||
|
- V4 introduces the **acadrust gate**: plugins targeting API v4 or later must resolve the same `acadrust` source as the host (see [`src/version_info.rs`](src/version_info.rs)).
|
||||||
|
|
||||||
|
The runtime enforces two gates:
|
||||||
|
|
||||||
|
1. `ocs_plugin_api_version()` exported by the cdylib must be within `[API_VERSION_MIN_SUPPORTED, effective_max_api_version()]`.
|
||||||
|
2. For v4+, `acadrust_sources_compatible(host_acadrust_source(), plugin_acadrust_source)` must be true.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Process model
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant PM as PluginManager (host)
|
||||||
|
participant PP as PluginProcess (host)
|
||||||
|
participant R as Runner child
|
||||||
|
participant L as Plugin cdylib
|
||||||
|
|
||||||
|
PM->>PP: spawn(cdylib_path, host)
|
||||||
|
PP->>PP: create local socket listener
|
||||||
|
PP->>R: spawn --ocs-plugin-runner <socket> <cdylib>
|
||||||
|
R->>L: unsafe { load(cdylib_path) }
|
||||||
|
L-->>R: Box<dyn BuiltinPlugin>
|
||||||
|
alt API v4
|
||||||
|
R->>PP: connect + RunnerHandshake::TokenV4
|
||||||
|
PP->>PP: verify token + V4 protocol/API gate
|
||||||
|
else API v2/v3
|
||||||
|
R->>PP: connect + RunnerHandshake::Token
|
||||||
|
PP->>PP: verify token
|
||||||
|
end
|
||||||
|
PP->>R: HostToPlugin::Request(GetManifest)
|
||||||
|
R->>L: manifest()
|
||||||
|
L-->>R: PluginManifest { api_version }
|
||||||
|
R-->>PP: HostResponse::Manifest
|
||||||
|
PP->>PP: verify api_version + acadrust gate
|
||||||
|
alt accepted
|
||||||
|
PP-->>PM: success, keep alive
|
||||||
|
else rejected
|
||||||
|
PP->>R: Shutdown
|
||||||
|
PP-->>PM: error
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Key invariants:
|
||||||
|
|
||||||
|
- The runner process is the host binary re-executed with special CLI args, so runner and host share the same `ocs_plugin_api` build.
|
||||||
|
- The plugin cdylib is `dlopen`-ed inside the runner, not the host.
|
||||||
|
- A pre-shared token (`OCS_PLUGIN_TOKEN`) authenticates the runner to the host.
|
||||||
|
- `PluginProcess` owns the `Child` handle and a reader thread; `PluginManager` owns the loaded plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Wire protocols
|
||||||
|
|
||||||
|
### V2/V3: request/response over a single socket
|
||||||
|
|
||||||
|
A single bidirectional socket carries both directions. While the host waits for a response to a `HostRequest`, it may receive nested `PluginRequest`s from the runner and handles them inline.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant H as Host
|
||||||
|
participant R as Runner
|
||||||
|
participant P as BuiltinPlugin
|
||||||
|
|
||||||
|
H->>R: HostToPlugin::Request(Dispatch { cmd: "LINE" })
|
||||||
|
R->>P: dispatch(host, "LINE")
|
||||||
|
P->>R: PluginToHost::Request(AddEntity(...))
|
||||||
|
R->>H: HostToPlugin::Request(AddEntity(...))
|
||||||
|
H-->>R: PluginToHost::Response(Handle)
|
||||||
|
R-->>P: PluginResponse::Handle
|
||||||
|
P-->>R: true
|
||||||
|
R-->>H: PluginToHost::Response(Bool(true))
|
||||||
|
```
|
||||||
|
|
||||||
|
Compatibility rule: new variants are appended at the end of `HostRequest`, `HostResponse`, `PluginRequest`, and `PluginResponse`, preserving bincode discriminant indices for old plugins.
|
||||||
|
|
||||||
|
### V4: multiplexed frames
|
||||||
|
|
||||||
|
V4 keeps the V3 request vocabulary but multiplexes requests, responses, and best-effort notifications over one local socket. Every request/response carries a correlation `id`; notifications carry an optional `command_id`.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant H as Host
|
||||||
|
participant R as V4 Runner
|
||||||
|
participant P as BuiltinPlugin
|
||||||
|
|
||||||
|
H->>R: HostToPluginV4::Notification(DocumentChangedV4 { tab_id })
|
||||||
|
H->>R: HostToPluginV4::Request { id: 7, ExecuteCode }
|
||||||
|
R->>P: start_execute_code(host, 7, ...)
|
||||||
|
P-->>R: true
|
||||||
|
P->>R: Notification(Output { text: "..." })
|
||||||
|
R->>H: PluginToHostV4::Notification(Output)
|
||||||
|
P->>R: Response { id: 7, result }
|
||||||
|
R->>H: PluginToHostV4::Response { id: 7, CodeExecutionResult }
|
||||||
|
```
|
||||||
|
|
||||||
|
Key types in [`src/ipc/v4/protocol.rs`](src/ipc/v4/protocol.rs):
|
||||||
|
|
||||||
|
- `HostToPluginV4`: `Request { id, HostRequest }`, `Response { id, PluginResponse }`, `Notification<HostNotification>`.
|
||||||
|
- `PluginToHostV4`: `Request { id, tab_id, PluginRequest }`, `Response { id, HostResponse }`, `Notification<PluginNotification>`.
|
||||||
|
- `NotificationEnvelope<T>`: `{ command_id: Option<u64>, payload: T }`.
|
||||||
|
|
||||||
|
Notifications are best-effort: per-process errors are logged, not propagated. Unknown host-notification discriminants deserialize to `HostNotification::Unknown(raw)` so a newer host does not crash an older plugin.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Shared-memory document views
|
||||||
|
|
||||||
|
V3 and V4 avoid cloning the full `CadDocument` over IPC for large reads. The host owns a memory-mapped file; the plugin maps it read-only.
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant P as Plugin
|
||||||
|
participant R as Runner
|
||||||
|
participant H as Host
|
||||||
|
participant S as DocumentSnapshotStore
|
||||||
|
|
||||||
|
P->>R: request(OpenDocumentView)
|
||||||
|
R->>H: PluginRequest::OpenDocumentView
|
||||||
|
H->>S: DocumentSnapshotStore::new / publish
|
||||||
|
S-->>H: DocumentViewInfo { path, version }
|
||||||
|
H-->>R: PluginResponse::DocumentView { path, version }
|
||||||
|
R-->>P: DocumentViewInfo
|
||||||
|
P->>S: SharedDocumentReader::open(path)
|
||||||
|
S-->>P: read-only mapping
|
||||||
|
loop host publishes new version
|
||||||
|
H->>S: publish(data)
|
||||||
|
S->>S: swap active segment, increment version
|
||||||
|
P->>S: read() + check version
|
||||||
|
end
|
||||||
|
P->>P: close reader
|
||||||
|
H->>S: close(tab_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [`src/shm.rs`](src/shm.rs) defines the generic double-buffered control page and the `SnapshotData` trait. Implementations live in the same file for V3 (`DocumentViewData`) and V4 (`DocumentViewDataV4`).
|
||||||
|
- [`src/host_v4.rs`](src/host_v4.rs) keeps a global `HostV4SnapshotManager` keyed by `tab_id`, so each document tab has its own snapshot.
|
||||||
|
- Segment size defaults to 16 MiB and is configurable via `OCS_V4_SNAPSHOT_SEGMENT_SIZE` (minimum 1 MiB).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notifications
|
||||||
|
|
||||||
|
- **Host → plugin:** `HostNotification` in [`src/host.rs`](src/host.rs). V4-only delivery via `PluginManager::broadcast_notification`. Currently emitted examples: `DocumentChangedV4`, `SelectionChangedV4`, `DocumentTabClosed`.
|
||||||
|
- **Plugin → host:** `PluginNotification` in [`src/host.rs`](src/host.rs). Examples: `Output`, `Progress`, `Log`. Handler installed via `PluginManager::set_notification_handler`.
|
||||||
|
|
||||||
|
Broadcasting is V4-gated inside `PluginManager::broadcast_notification`: V2/V3 processes are skipped. The handler runs on the V4 reader thread and must not block.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error handling and timeouts
|
||||||
|
|
||||||
|
| Kind | Default | Env var | Behavior |
|
||||||
|
|------|---------|---------|----------|
|
||||||
|
| Spawn / connect timeout | 30 s | `OCS_PLUGIN_SPAWN_TIMEOUT_SECS` | Host aborts spawn, plugin is not loaded. |
|
||||||
|
| Per-call timeout | 30 s | `OCS_PLUGIN_CALL_TIMEOUT_SECS` | Host kills runner, plugin marked dead. Floor timeouts apply to `GetManifest`/`GetRibbon`, `Dispatch`, and interactive events. |
|
||||||
|
| Oversized message | — | — | Transport rejects messages > 64 MiB with `TransportError::TooLarge`. |
|
||||||
|
| Malformed message | — | — | `bincode` deserialize error; V4 reader logs and continues if possible. |
|
||||||
|
| Panic in plugin code | — | — | Caught by `catch_unwind` in the runner; converted to `HostResponse::Error`. |
|
||||||
|
| Dead runner | — | — | Detected via `try_wait` on next dispatch or ribbon rebuild; plugin dropped. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Embedded metadata
|
||||||
|
|
||||||
|
The crate embeds two JSON blobs at build time:
|
||||||
|
|
||||||
|
- **Type registry** (`OUT_DIR/type_registry.json`) — a language-binding-friendly schema for a curated allow-list of `acadrust` types, generated by `serde-reflection` in `build.rs`. See [`src/type_registry.rs`](src/type_registry.rs).
|
||||||
|
- **Version info** (`OUT_DIR/version_info.json`) — host version, `ocs_plugin_api` version, `acadrust` version and source, API versions, build timestamp. See [`src/version_info.rs`](src/version_info.rs).
|
||||||
|
|
||||||
|
Both are accessible without enabling the `host` feature.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security / isolation boundaries
|
||||||
|
|
||||||
|
- **Process isolation:** plugin code runs in a child process.
|
||||||
|
- **Token handshake:** `OCS_PLUGIN_TOKEN` is generated per spawn and checked before accepting the runner connection.
|
||||||
|
- **C-ABI export:** plugins expose only two symbols, `ocs_plugin_api_version` and `ocs_plugin_register`, defined by `export_plugin!`.
|
||||||
|
- **Message size cap:** 64 MiB per length-framed message.
|
||||||
|
- **Shared memory:** plugin maps snapshots read-only; the host controls publish/close.
|
||||||
|
|
||||||
|
This is defense-in-depth, not a sandbox: plugins execute native code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Failure modes
|
||||||
|
|
||||||
|
| Scenario | Result |
|
||||||
|
|----------|--------|
|
||||||
|
| Plugin panics during dispatch | Runner catches, returns error response, process stays alive. |
|
||||||
|
| Call timeout | Host kills runner, plugin marked dead. |
|
||||||
|
| Spawn timeout or crash | Spawn fails; plugin not loaded; error surfaced in UI/logs. |
|
||||||
|
| Version mismatch | Host refuses plugin before running plugin code. |
|
||||||
|
| Acadrust source mismatch (v4+) | Host refuses plugin to avoid binary incompatibilities. |
|
||||||
|
| Unknown notification discriminant | Deserializes to `HostNotification::Unknown`; plugin can ignore. |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Open tasks
|
||||||
|
|
||||||
|
- **Reduce broadcast notification clone/serialize overhead.** `PluginManager::broadcast_notification` clones the `HostNotification` (including its `Vec<Handle>`) once per loaded V4 plugin, and `transport::send` re-serializes the payload into a new `Vec<u8>` per plugin. Fixing this properly requires either a public API change (`Arc<Vec<Handle>>` in `HostNotification`) or a new bytes-oriented broadcast path in the IPC layer. It only matters with many plugins plus very large selections.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cross-references
|
||||||
|
|
||||||
|
- Plugin-author quick start: [`README.md`](./README.md)
|
||||||
|
- Host/plugin integration spec: [`../../docs/plugin-architecture.md`](../../docs/plugin-architecture.md)
|
||||||
|
- Plugin template: [`../../docs/plugin-template/`](../../docs/plugin-template/)
|
||||||
|
- Process spawn: [`src/process.rs`](src/process.rs)
|
||||||
|
- Process manager: [`src/process/manager.rs`](src/process/manager.rs)
|
||||||
|
- Runner loop: [`src/runner.rs`](src/runner.rs)
|
||||||
|
- V4 protocol: [`src/ipc/v4/protocol.rs`](src/ipc/v4/protocol.rs)
|
||||||
|
- Shared memory: [`src/shm.rs`](src/shm.rs)
|
||||||
|
|
@ -14,7 +14,7 @@ serde = { version = "1", features = ["derive"] }
|
||||||
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
# Pulled in only by the `host` feature, which adds the `acadrust`-typed
|
||||||
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
# `HostApi` runtime surface. The default crate stays dependency-free so engine
|
||||||
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
# crates and external tooling can depend on the manifest/ribbon contract cheaply.
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "db73dab", optional = true, features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66", optional = true, features = ["serde"] }
|
||||||
|
|
||||||
# Runtime IPC and serialization (host feature only).
|
# Runtime IPC and serialization (host feature only).
|
||||||
interprocess = { version = "2", optional = true }
|
interprocess = { version = "2", optional = true }
|
||||||
|
|
@ -37,7 +37,7 @@ serde_json = "1"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
cargo-lock = "11"
|
cargo-lock = "11"
|
||||||
# acadrust is scanned at build time to generate the embedded type registry.
|
# acadrust is scanned at build time to generate the embedded type registry.
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "db73dab", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66", features = ["serde"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
|
|
||||||
|
|
@ -120,9 +120,10 @@ The frame format supports:
|
||||||
- Bi-directional, best-effort notifications (`NotificationEnvelope`)
|
- Bi-directional, best-effort notifications (`NotificationEnvelope`)
|
||||||
|
|
||||||
Because the socket is full-duplex, the host can push
|
Because the socket is full-duplex, the host can push
|
||||||
`HostNotification::DocumentChanged` or `InputLine` to a plugin at any time, and
|
`HostNotification::DocumentChangedV4`, `SelectionChangedV4`, or
|
||||||
the plugin can stream `PluginNotification::Output`/`Progress`/`Log` back while a
|
`DocumentTabClosed` to a plugin at any time, and the plugin can stream
|
||||||
long command is still running.
|
`PluginNotification::Output`/`Progress`/`Log` back while a long command is still
|
||||||
|
running.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
sequenceDiagram
|
sequenceDiagram
|
||||||
|
|
@ -420,7 +421,8 @@ std::thread::spawn(move || {
|
||||||
|
|
||||||
### Further reading
|
### Further reading
|
||||||
|
|
||||||
|
- Internal architecture & invariants: [`ARCHITECTURE.md`](./ARCHITECTURE.md)
|
||||||
|
- Host/plugin integration spec: [`../../docs/plugin-architecture.md`](../../docs/plugin-architecture.md)
|
||||||
- Plugin template: [`../../docs/plugin-template`](../../docs/plugin-template)
|
- Plugin template: [`../../docs/plugin-template`](../../docs/plugin-template)
|
||||||
- Plugin marketplace registry: [`../../plugins/README.md`](../../plugins/README.md)
|
- Plugin marketplace registry: [`../../plugins/README.md`](../../plugins/README.md)
|
||||||
- REPL design notes: [`../DESIGN_REPL.md`](../DESIGN_REPL.md)
|
- REPL design notes: [`../DESIGN_REPL.md`](../DESIGN_REPL.md)
|
||||||
- Python REPL plugin: [`../ocs_python_repl/README.md`](../ocs_python_repl/README.md)
|
|
||||||
|
|
|
||||||
|
|
@ -1,193 +0,0 @@
|
||||||
# Out-of-Process Plugin Architecture — `ocs_plugin_api`
|
|
||||||
|
|
||||||
**Status:** Design proposal
|
|
||||||
**Scope:** `crates/ocs_plugin_api` only (minimal host wiring, no plugin changes, no new crate)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Problem
|
|
||||||
|
|
||||||
The host currently loads external add-ons as `cdylib` libraries into its own process via `libloading`. `panic::catch_unwind` catches Rust panics, but a plugin can still corrupt host memory, segfault, or deadlock the UI thread. The fix is to run each plugin as a separate OS process and mediate all interaction through IPC.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Design
|
|
||||||
|
|
||||||
`ocs_plugin_api` becomes a dual-use library:
|
|
||||||
|
|
||||||
- **Plugin side:** unchanged API surface (`BuiltinPlugin`, `HostApi`, `CadModule`, `export_plugin!`).
|
|
||||||
- **Host side:** runtime that spawns plugin processes and handles their IPC requests.
|
|
||||||
|
|
||||||
Plugins remain `cdylib`s. The host spawns **itself** in runner mode (`--ocs-plugin-runner <socket> <cdylib>`) to load each cdylib in a child process and bridge to the host over `interprocess::local_socket`. The runner implementation lives inside `ocs_plugin_api` as a library module; no separate helper binary is needed, so the runner and host are always the same build and cannot get out of sync at deployment time.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Constraints
|
|
||||||
|
|
||||||
| Constraint | Handling |
|
|
||||||
|---|---|
|
|
||||||
| Plugin API unchanged | Trait/type signatures preserved. `document()` / `document_mut()` keep their signatures but return a local cached copy, so `API_VERSION` bumps to 3. |
|
|
||||||
| Only `ocs_plugin_api` modified | All new code lives here. The host needs only minimal call-site wiring in `src/plugin/external.rs`, `src/plugin/registry.rs`, and `src/app/plugin_host.rs`. |
|
|
||||||
| No new crate | Runner code lives inside `ocs_plugin_api`; the host executable serves as the runner process. |
|
|
||||||
| Platform-independent | `interprocess::local_socket` uses named pipes on Windows and Unix domain sockets elsewhere; self-spawning works on every host target. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Architecture
|
|
||||||
|
|
||||||
```text
|
|
||||||
Host process Plugin process
|
|
||||||
┌─────────────────┐ local socket ┌─────────────────┐
|
|
||||||
│ HostSession │◄───────────────►│ HostApi proxy │
|
|
||||||
│ (document, UI) │ bincode frames │ (sends RPCs) │
|
|
||||||
└─────────────────┘ └─────────────────┘
|
|
||||||
▲ │
|
|
||||||
│ ▼
|
|
||||||
PluginManager cdylib loaded
|
|
||||||
(spawn / kill / by host in
|
|
||||||
supervise) runner mode
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.1 IPC protocol
|
|
||||||
|
|
||||||
All messages are length-framed and serialized with `bincode`.
|
|
||||||
|
|
||||||
**Host → plugin:**
|
|
||||||
|
|
||||||
- `GetManifest`, `GetRibbon`
|
|
||||||
- `Dispatch { cmd: String }`
|
|
||||||
- `InteractiveEvent { command_id, event }`
|
|
||||||
- `Shutdown`
|
|
||||||
|
|
||||||
**Plugin → host:**
|
|
||||||
|
|
||||||
- `PushInfo` / `PushOutput` / `PushError`
|
|
||||||
- `AddEntity(SerializedEntity)` → `Handle`
|
|
||||||
- `BumpGeometry`, `PushUndo`, `SetDirty`
|
|
||||||
- `ReadRecord`, `WriteRecord`, `RemoveRecord`
|
|
||||||
- `StartInteractive`, `PollInteractive`
|
|
||||||
- `DocumentSnapshot` → `SerializedDocument`
|
|
||||||
|
|
||||||
`SerializedEntity`, `SerializedRecord`, and `SerializedDocument` are `acadrust` types with `Serialize` / `Deserialize` derived.
|
|
||||||
|
|
||||||
### 4.2 Plugin-side runtime
|
|
||||||
|
|
||||||
`OpenCADStudio --ocs-plugin-runner <socket_name> <cdylib_path>`:
|
|
||||||
|
|
||||||
1. Loads the cdylib, validates `ocs_plugin_api_version`, calls `ocs_plugin_register`.
|
|
||||||
2. Connects to the host socket and answers `GetManifest` / `GetRibbon`.
|
|
||||||
3. Runs a request loop: dispatch commands, forward interactive events.
|
|
||||||
|
|
||||||
`PluginHostApi` implements `HostApi` by sending RPCs. `document()` / `document_mut()` return a local copy fetched from `DocumentSnapshot`; mutations are **not** automatically synced back. Plugins use `add_entity`, `write_record`, etc. for host-visible changes.
|
|
||||||
|
|
||||||
### 4.3 Host-side runtime
|
|
||||||
|
|
||||||
`ocs_plugin_api::process` provides:
|
|
||||||
|
|
||||||
- `PluginProcess::spawn(cdylib_path)` — creates a socket, launches the host executable in runner mode, accepts its connection.
|
|
||||||
- `PluginManager` — spawn all discovered plugins, supervise, kill.
|
|
||||||
- `serve_plugin_connection(stream, &mut dyn HostApi)` — host-side request handler.
|
|
||||||
|
|
||||||
The host creates a `HostSession` and passes it to `serve_plugin_connection`; existing document/undo logic is reused.
|
|
||||||
|
|
||||||
### 4.4 Ribbon
|
|
||||||
|
|
||||||
Ribbon types use `&'static str`, which cannot cross a socket. Define owned equivalents in `ocs_plugin_api::ribbon::owned` and convert for IPC. The host reconstructs `RibbonGroup` once per load (e.g., by leaking the owned strings), avoiding changes to ribbon rendering code.
|
|
||||||
|
|
||||||
### 4.5 Failure handling
|
|
||||||
|
|
||||||
| Failure | Behavior |
|
|
||||||
|---|---|
|
|
||||||
| Plugin crash / hang / malformed message | Host marks plugin dead, drops its ribbon tab, logs the error, and continues running. |
|
|
||||||
| Plugin panics | Caught inside the runner; an error response is returned to the host. |
|
|
||||||
| Spawn failure | Reported through `PluginManager` and shown in the Plugin Manager. |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Host Integration Points
|
|
||||||
|
|
||||||
1. **`src/plugin/external.rs`** — replace `libloading`-based `LoadedPlugin` with `PluginProcess::spawn`.
|
|
||||||
2. **`src/plugin/registry.rs`** — use `PluginProcess` for ribbon collection and command dispatch.
|
|
||||||
3. **`src/app/plugin_host.rs`** — add an IPC request bridge that maps incoming messages to `HostSession` calls.
|
|
||||||
|
|
||||||
No changes to `docs/plugin-template` or any other plugin.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Crate Changes
|
|
||||||
|
|
||||||
### 6.1 New files inside `crates/ocs_plugin_api`
|
|
||||||
|
|
||||||
```text
|
|
||||||
src/
|
|
||||||
ipc/
|
|
||||||
protocol.rs # HostRequest / PluginRequest / PluginResponse
|
|
||||||
transport.rs # framed read/write over local_socket
|
|
||||||
client.rs # plugin-side IpcClient + PluginHostApi
|
|
||||||
server.rs # host-side serve_plugin_connection
|
|
||||||
process.rs # PluginProcess / PluginManager
|
|
||||||
runner.rs # plugin runner logic invoked by host in runner mode
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 Dependencies
|
|
||||||
|
|
||||||
Add under the existing `host` feature:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[dependencies]
|
|
||||||
interprocess = { version = "2", optional = true }
|
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
|
||||||
bincode = { version = "1", optional = true }
|
|
||||||
thiserror = { version = "1", optional = true }
|
|
||||||
libloading = { version = "0.8", optional = true }
|
|
||||||
|
|
||||||
[features]
|
|
||||||
host = ["dep:acadrust", "dep:interprocess", "dep:serde", "dep:bincode", "dep:thiserror", "dep:libloading"]
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. API Version
|
|
||||||
|
|
||||||
Bump `API_VERSION` to `3` because `document()` / `document_mut()` semantics change from direct host references to local cached copies. v2 plugins are refused as usual; plugin authors recompile with `ApiVersion::CURRENT`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 8. Implementation Plan
|
|
||||||
|
|
||||||
1. Add dependencies to `Cargo.toml`.
|
|
||||||
2. Implement framed transport and protocol messages.
|
|
||||||
3. Implement runner logic in `runner.rs`.
|
|
||||||
4. Implement `PluginHostApi` proxy.
|
|
||||||
5. Implement host-side server and `PluginManager`.
|
|
||||||
6. Add owned ribbon conversions.
|
|
||||||
7. Wire the three host call sites and add `--ocs-plugin-runner` dispatch in `src/main.rs`.
|
|
||||||
8. Bump `API_VERSION` and update `docs/plugin-architecture.md`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Testing
|
|
||||||
|
|
||||||
- **Unit:** protocol round-trip, ribbon conversion, proxy request emission.
|
|
||||||
- **Integration:** spawn a test plugin, verify dispatch and interactive command round-trip, kill the process and confirm the host survives.
|
|
||||||
- **Host:** update registry tests once `LoadedPlugin` is replaced by `PluginProcess`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Compliance with `AGENT.md`
|
|
||||||
|
|
||||||
| Requirement | Status |
|
|
||||||
|---|---|
|
|
||||||
| `ocs_plugin_api` is a library, not a plugin | Yes |
|
|
||||||
| Plugin API source-compatible | Yes; signatures are unchanged. `document()` semantics change is gated by v3. |
|
|
||||||
| Separate processes + failure management | Yes |
|
|
||||||
| Platform-independent `interprocess` IPC | Yes |
|
|
||||||
| Only `ocs_plugin_api` modified | Code: yes. Host needs minimal wiring; unavoidable because `OpenCADStudio` / `HostSession` are host-private. |
|
|
||||||
| No new crate | Yes |
|
|
||||||
| Memory / process isolation | Yes |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 11. Summary
|
|
||||||
|
|
||||||
`ocs_plugin_api` absorbs the plugin runtime: the host spawns itself in runner mode to load each cdylib in its own process, and all host/plugin interaction is serialized over `interprocess` local sockets. Plugin API signatures stay intact, host changes are limited to a few call sites, and no new crate is introduced.
|
|
||||||
|
|
@ -69,6 +69,8 @@ pub enum HostNotification {
|
||||||
DocumentChangedV4 { tab_id: u64, version: u64 },
|
DocumentChangedV4 { tab_id: u64, version: u64 },
|
||||||
/// V4 tab closed notification. Discriminant 6.
|
/// V4 tab closed notification. Discriminant 6.
|
||||||
DocumentTabClosed { tab_id: u64 },
|
DocumentTabClosed { tab_id: u64 },
|
||||||
|
/// V4 selection changed for a specific tab. Discriminant 7.
|
||||||
|
SelectionChangedV4 { tab_id: u64, handles: Vec<Handle> },
|
||||||
/// Fallback for notification variants added in future minor revisions.
|
/// Fallback for notification variants added in future minor revisions.
|
||||||
/// Carries the raw bincode payload so an older peer can ignore it without
|
/// Carries the raw bincode payload so an older peer can ignore it without
|
||||||
/// failing deserialization.
|
/// failing deserialization.
|
||||||
|
|
@ -112,6 +114,13 @@ impl Serialize for HostNotification {
|
||||||
bincode::serialize_into(&mut bytes, tab_id)
|
bincode::serialize_into(&mut bytes, tab_id)
|
||||||
.map_err(serde::ser::Error::custom)?;
|
.map_err(serde::ser::Error::custom)?;
|
||||||
}
|
}
|
||||||
|
HostNotification::SelectionChangedV4 { tab_id, handles } => {
|
||||||
|
bytes.push(7);
|
||||||
|
bincode::serialize_into(&mut bytes, tab_id)
|
||||||
|
.map_err(serde::ser::Error::custom)?;
|
||||||
|
bincode::serialize_into(&mut bytes, handles)
|
||||||
|
.map_err(serde::ser::Error::custom)?;
|
||||||
|
}
|
||||||
HostNotification::Unknown(raw) => bytes.extend_from_slice(raw),
|
HostNotification::Unknown(raw) => bytes.extend_from_slice(raw),
|
||||||
}
|
}
|
||||||
bytes.serialize(serializer)
|
bytes.serialize(serializer)
|
||||||
|
|
@ -146,6 +155,9 @@ impl<'de> Deserialize<'de> for HostNotification {
|
||||||
6 => bincode::deserialize(rest)
|
6 => bincode::deserialize(rest)
|
||||||
.map(|tab_id| HostNotification::DocumentTabClosed { tab_id })
|
.map(|tab_id| HostNotification::DocumentTabClosed { tab_id })
|
||||||
.map_err(serde::de::Error::custom),
|
.map_err(serde::de::Error::custom),
|
||||||
|
7 => bincode::deserialize(rest)
|
||||||
|
.map(|(tab_id, handles)| HostNotification::SelectionChangedV4 { tab_id, handles })
|
||||||
|
.map_err(serde::de::Error::custom),
|
||||||
_ => Ok(HostNotification::Unknown(bytes)),
|
_ => Ok(HostNotification::Unknown(bytes)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,12 @@
|
||||||
//! Maintains a per-tab shared-memory document view keyed by the stable
|
//! Maintains a per-tab shared-memory document view keyed by the stable
|
||||||
//! `DocumentTab.id`. The manager is used by `src/plugin/v4_support.rs` to
|
//! `DocumentTab.id`. The manager is used by `src/plugin/v4_support.rs` to
|
||||||
//! open, publish, and close V4 snapshots independently of the V3 reader path.
|
//! open, publish, and close V4 snapshots independently of the V3 reader path.
|
||||||
|
//!
|
||||||
|
//! [`manager`] returns a global `Mutex`-guarded [`HostV4SnapshotManager`].
|
||||||
|
//! [`HostV4SnapshotManager::open`] lazily creates a [`DocumentSnapshotStore`] for
|
||||||
|
//! a tab; [`HostV4SnapshotManager::publish`] updates an existing store;
|
||||||
|
//! [`HostV4SnapshotManager::close`] drops it. Segment size defaults to 16 MiB
|
||||||
|
//! and can be overridden with `OCS_V4_SNAPSHOT_SEGMENT_SIZE`.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,21 @@
|
||||||
//!
|
//!
|
||||||
//! Built only with the `host` feature because it needs `acadrust`-typed
|
//! Built only with the `host` feature because it needs `acadrust`-typed
|
||||||
//! messages and the plugin runner binary.
|
//! messages and the plugin runner binary.
|
||||||
|
//!
|
||||||
|
//! Submodules:
|
||||||
|
//!
|
||||||
|
//! - [`protocol`](crate::ipc::protocol) — V2/V3 request/response enums and the
|
||||||
|
//! initial runner handshake.
|
||||||
|
//! - [`transport`](crate::ipc::transport) — length-framed bincode over a local
|
||||||
|
//! socket; enforces `MAX_MESSAGE_SIZE`.
|
||||||
|
//! - [`client`](crate::ipc::client) — plugin-side V2/V3 client and `HostApi`
|
||||||
|
//! proxy.
|
||||||
|
//! - [`server`](crate::ipc::server) — host-side handler that applies a
|
||||||
|
//! `PluginRequest` to a `HostApi` implementation.
|
||||||
|
//! - [`v4`](crate::ipc::v4) — multiplexed V4 frames, notifications, and
|
||||||
|
//! correlation ids.
|
||||||
|
//! - [`proxy`](crate::ipc::proxy) — optional TCP proxy so plugin child
|
||||||
|
//! processes can forward requests to the host.
|
||||||
|
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,16 @@
|
||||||
//! (expecting a response) or a response (to a previous request). This lets the
|
//! (expecting a response) or a response (to a previous request). This lets the
|
||||||
//! host handle plugin RPCs inline while it waits for the result of a host→plugin
|
//! host handle plugin RPCs inline while it waits for the result of a host→plugin
|
||||||
//! request such as `Dispatch`, avoiding the need for two sockets or threads.
|
//! request such as `Dispatch`, avoiding the need for two sockets or threads.
|
||||||
|
//!
|
||||||
|
//! Compatibility rule: new variants are appended at the end of every public enum
|
||||||
|
//! (`HostRequest`, `HostResponse`, `PluginRequest`, `PluginResponse`,
|
||||||
|
//! `RunnerHandshake`) so older plugins keep their bincode discriminant indices.
|
||||||
|
//! The V4 additions are a separate frame layer in [`crate::ipc::v4`] and do not
|
||||||
|
//! alter these enums.
|
||||||
|
//!
|
||||||
|
//! The pre-shared runner token is delivered through [`PLUGIN_TOKEN_ENV`]
|
||||||
|
//! (`OCS_PLUGIN_TOKEN`). The runner must present the same token immediately
|
||||||
|
//! after connecting or the host closes the connection.
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,78 @@ pub fn recv_framed<R: Read, T: serde::de::DeserializeOwned>(reader: &mut R) -> R
|
||||||
Ok(msg)
|
Ok(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error during a polled read: either an IO error on the stream, or an
|
||||||
|
/// interrupt reported by the poll callback (e.g. the user pressed Ctrl+C).
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum PollError {
|
||||||
|
Io(ProxyError),
|
||||||
|
Interrupted(PluginRequestError),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ProxyError> for PollError {
|
||||||
|
fn from(e: ProxyError) -> Self {
|
||||||
|
PollError::Io(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<PollError> for PluginRequestError {
|
||||||
|
fn from(e: PollError) -> Self {
|
||||||
|
match e {
|
||||||
|
PollError::Io(err) => err.into(),
|
||||||
|
PollError::Interrupted(err) => err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read exactly `buf.len()` bytes from `reader`, calling `poll` whenever no
|
||||||
|
/// data is currently available. Unlike `read_exact`, this preserves partial
|
||||||
|
/// progress across short read timeouts so the stream can never get out of sync.
|
||||||
|
fn read_with_poll<R: Read>(
|
||||||
|
reader: &mut R,
|
||||||
|
buf: &mut [u8],
|
||||||
|
poll: &mut dyn FnMut() -> Result<(), PluginRequestError>,
|
||||||
|
) -> Result<(), PollError> {
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
let mut off = 0;
|
||||||
|
while off < buf.len() {
|
||||||
|
match reader.read(&mut buf[off..]) {
|
||||||
|
Ok(0) => return Err(PollError::Io(ProxyError::Io(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::UnexpectedEof,
|
||||||
|
"proxy disconnected",
|
||||||
|
)))),
|
||||||
|
Ok(n) => off += n,
|
||||||
|
Err(e) if e.kind() == ErrorKind::WouldBlock || e.kind() == ErrorKind::TimedOut => {
|
||||||
|
if let Err(err) = poll() {
|
||||||
|
return Err(PollError::Interrupted(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => return Err(PollError::Io(ProxyError::Io(e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Receive a length-framed bincode message with polling. The length prefix and
|
||||||
|
/// payload are read incrementally so a timeout never splits a message.
|
||||||
|
fn recv_framed_with_poll<R: Read>(
|
||||||
|
reader: &mut R,
|
||||||
|
poll: &mut dyn FnMut() -> Result<(), PluginRequestError>,
|
||||||
|
) -> Result<PluginResponse, PollError> {
|
||||||
|
let mut len_buf = [0u8; 8];
|
||||||
|
read_with_poll(reader, &mut len_buf, poll)?;
|
||||||
|
let len = u64::from_le_bytes(len_buf) as usize;
|
||||||
|
if len == 0 {
|
||||||
|
return Err(ProxyError::Empty.into());
|
||||||
|
}
|
||||||
|
if len > MAX_MESSAGE_SIZE {
|
||||||
|
return Err(ProxyError::TooLarge(len).into());
|
||||||
|
}
|
||||||
|
let mut buf = vec![0u8; len];
|
||||||
|
read_with_poll(reader, &mut buf, poll)?;
|
||||||
|
let msg = bincode::deserialize(&buf).map_err(ProxyError::Encode)?;
|
||||||
|
Ok(msg)
|
||||||
|
}
|
||||||
|
|
||||||
/// Thread-safe [`PluginRequestSender`] implementation that forwards requests
|
/// Thread-safe [`PluginRequestSender`] implementation that forwards requests
|
||||||
/// over a single TCP stream to a request proxy server.
|
/// over a single TCP stream to a request proxy server.
|
||||||
pub struct ProxyPluginRequestSender {
|
pub struct ProxyPluginRequestSender {
|
||||||
|
|
@ -113,12 +185,12 @@ impl ProxyPluginRequestSender {
|
||||||
/// [`POLL_INTERVAL`] while waiting. This lets Python callers run
|
/// [`POLL_INTERVAL`] while waiting. This lets Python callers run
|
||||||
/// `py.check_signals()` so that `Ctrl+C`/`KeyboardInterrupt` is raised
|
/// `py.check_signals()` so that `Ctrl+C`/`KeyboardInterrupt` is raised
|
||||||
/// promptly instead of waiting for the full socket timeout.
|
/// promptly instead of waiting for the full socket timeout.
|
||||||
|
/// Partial reads are preserved across timeouts to keep framing synchronized.
|
||||||
pub fn request_with_poll(
|
pub fn request_with_poll(
|
||||||
&self,
|
&self,
|
||||||
req: PluginRequest,
|
req: PluginRequest,
|
||||||
poll: &mut dyn FnMut() -> Result<(), PluginRequestError>,
|
poll: &mut dyn FnMut() -> Result<(), PluginRequestError>,
|
||||||
) -> Result<PluginResponse, PluginRequestError> {
|
) -> Result<PluginResponse, PluginRequestError> {
|
||||||
use std::io::ErrorKind;
|
|
||||||
let mut stream = self
|
let mut stream = self
|
||||||
.stream
|
.stream
|
||||||
.lock()
|
.lock()
|
||||||
|
|
@ -130,21 +202,17 @@ impl ProxyPluginRequestSender {
|
||||||
stream
|
stream
|
||||||
.set_read_timeout(Some(POLL_INTERVAL))
|
.set_read_timeout(Some(POLL_INTERVAL))
|
||||||
.map_err(|e| PluginRequestError(e.to_string()))?;
|
.map_err(|e| PluginRequestError(e.to_string()))?;
|
||||||
let result = loop {
|
|
||||||
match recv_framed(&mut *stream) {
|
let result = match recv_framed_with_poll(&mut *stream, poll) {
|
||||||
Ok(resp) => break Ok(resp),
|
Ok(resp) => Ok(resp),
|
||||||
Err(ProxyError::Io(e))
|
Err(PollError::Interrupted(e)) => {
|
||||||
if e.kind() == ErrorKind::WouldBlock
|
// A partial response cannot be reused after interruption.
|
||||||
|| e.kind() == ErrorKind::TimedOut =>
|
let _ = stream.shutdown(Shutdown::Both);
|
||||||
{
|
Err(e)
|
||||||
if let Err(e) = poll() {
|
|
||||||
let _ = stream.shutdown(Shutdown::Both);
|
|
||||||
break Err(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => break Err(e.into()),
|
|
||||||
}
|
}
|
||||||
|
Err(PollError::Io(e)) => Err(e.into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _ = stream.set_read_timeout(original_timeout);
|
let _ = stream.set_read_timeout(original_timeout);
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
@ -331,4 +399,55 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(matches!(resp, PluginResponse::Ok));
|
assert!(matches!(resp, PluginResponse::Ok));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A reader that returns one byte per call and then `WouldBlock` on the
|
||||||
|
/// next call, exercising both the incremental read and the poll path.
|
||||||
|
struct OneByteThenBlock<'a> {
|
||||||
|
data: &'a [u8],
|
||||||
|
block_next: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Read for OneByteThenBlock<'a> {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
|
||||||
|
if self.block_next {
|
||||||
|
self.block_next = false;
|
||||||
|
return Err(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::WouldBlock,
|
||||||
|
"simulated block",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if self.data.is_empty() {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
let n = buf.len().min(1);
|
||||||
|
buf[..n].copy_from_slice(&self.data[..n]);
|
||||||
|
self.data = &self.data[n..];
|
||||||
|
self.block_next = true;
|
||||||
|
Ok(n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn recv_framed_with_poll_reads_one_byte_at_a_time() {
|
||||||
|
let msg = PluginResponse::Bool(true);
|
||||||
|
let bytes = bincode::serialize(&msg).unwrap();
|
||||||
|
let len = bytes.len() as u64;
|
||||||
|
let mut framed = Vec::with_capacity(8 + bytes.len());
|
||||||
|
framed.extend_from_slice(&len.to_le_bytes());
|
||||||
|
framed.extend_from_slice(&bytes);
|
||||||
|
|
||||||
|
let mut reader = OneByteThenBlock {
|
||||||
|
data: &framed,
|
||||||
|
block_next: false,
|
||||||
|
};
|
||||||
|
let mut polls = 0;
|
||||||
|
let resp = recv_framed_with_poll(&mut reader, &mut || {
|
||||||
|
polls += 1;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
assert!(matches!(resp, PluginResponse::Bool(true)));
|
||||||
|
// One byte, one block for every byte except the last (no poll after EOF).
|
||||||
|
assert_eq!(polls, framed.len() - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,12 @@
|
||||||
//! Length-framed transport over `interprocess::local_socket` streams.
|
//! Length-framed transport over `interprocess::local_socket` streams.
|
||||||
|
//!
|
||||||
|
//! Messages are serialized with `bincode`, prefixed by a little-endian `u64`
|
||||||
|
//! length, and sent over the stream. The receiver parses the length, bounds it
|
||||||
|
//! against [`MAX_MESSAGE_SIZE`], then deserializes the payload.
|
||||||
|
//!
|
||||||
|
//! [`send`] and [`recv`] are synchronous and block until the full frame is read
|
||||||
|
//! or the peer disconnects. `Disconnected` is returned on clean EOF; `TooLarge`
|
||||||
|
//! rejects messages that exceed 64 MiB to protect host/runner memory.
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -761,7 +761,6 @@ impl DocumentReader for EmptyDocumentReader {
|
||||||
#[cfg(all(test, feature = "host"))]
|
#[cfg(all(test, feature = "host"))]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::atomic::{AtomicU64, Ordering};
|
use std::sync::atomic::{AtomicU64, Ordering};
|
||||||
use std::sync::Mutex as StdMutex;
|
|
||||||
use std::thread;
|
use std::thread;
|
||||||
|
|
||||||
use interprocess::local_socket::{
|
use interprocess::local_socket::{
|
||||||
|
|
@ -772,10 +771,9 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ipc::transport::recv;
|
use crate::ipc::transport::recv;
|
||||||
use crate::ipc::v4::protocol::{HostToPluginV4, PluginToHostV4};
|
use crate::ipc::v4::protocol::{HostToPluginV4, PluginToHostV4};
|
||||||
|
use crate::test_lock::ENV_LOCK;
|
||||||
use acadrust::entities::Point;
|
use acadrust::entities::Point;
|
||||||
|
|
||||||
static ENV_LOCK: StdMutex<()> = StdMutex::new(());
|
|
||||||
|
|
||||||
fn unique_socket_name() -> String {
|
fn unique_socket_name() -> String {
|
||||||
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
||||||
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
|
let n = COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,15 @@
|
||||||
//! requests, responses, and best-effort notifications over a single local
|
//! requests, responses, and best-effort notifications over a single local
|
||||||
//! socket. The V3 files (`src/ipc/{protocol,client,server}.rs`) are left
|
//! socket. The V3 files (`src/ipc/{protocol,client,server}.rs`) are left
|
||||||
//! untouched; all V4 logic lives in this module.
|
//! untouched; all V4 logic lives in this module.
|
||||||
|
//!
|
||||||
|
//! Submodules:
|
||||||
|
//!
|
||||||
|
//! - [`protocol`](crate::ipc::v4::protocol) — V4 wire frames, notification
|
||||||
|
//! envelopes, and correlation ids.
|
||||||
|
//! - [`client`](crate::ipc::v4::client) — plugin-side V4 client loop and
|
||||||
|
//! notification dispatch.
|
||||||
|
//! - [`server`](crate::ipc::v4::server) — host-side V4 connection and reader
|
||||||
|
//! thread.
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,23 @@
|
||||||
//! V4 wire frames and envelope types.
|
//! V4 wire frames and envelope types.
|
||||||
|
//!
|
||||||
|
//! V4 replaces the simple request/response pipe with a multiplexed frame layer.
|
||||||
|
//! Every request and response carries a monotonically increasing correlation
|
||||||
|
//! `id` so a plugin can send/receive out of order. Notifications carry an
|
||||||
|
//! optional `command_id` so a plugin can associate a host notification with the
|
||||||
|
//! long-running command that caused it.
|
||||||
|
//!
|
||||||
|
//! The two frame enums are:
|
||||||
|
//!
|
||||||
|
//! - [`HostToPluginV4`] — host → runner requests, responses to runner requests,
|
||||||
|
//! and host notifications.
|
||||||
|
//! - [`PluginToHostV4`] — runner → host requests, responses to host requests,
|
||||||
|
//! and plugin notifications.
|
||||||
|
//!
|
||||||
|
//! Notifications are wrapped in [`NotificationEnvelope`] and are best-effort:
|
||||||
|
//! a process that fails to accept a notification is logged and skipped, but the
|
||||||
|
//! connection stays alive. Unknown host-notification discriminants deserialize
|
||||||
|
//! to [`crate::host::HostNotification::Unknown`] so newer host notifications do
|
||||||
|
//! not break older plugins.
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,17 @@
|
||||||
//! For binary compatibility, the host and every plugin must resolve the same
|
//! For binary compatibility, the host and every plugin must resolve the same
|
||||||
//! `acadrust` source. The host does this via a `[patch.crates-io]` entry in
|
//! `acadrust` source. The host does this via a `[patch.crates-io]` entry in
|
||||||
//! `Cargo.toml`; out-of-tree plugins should copy that exact patch.
|
//! `Cargo.toml`; out-of-tree plugins should copy that exact patch.
|
||||||
|
//!
|
||||||
|
//! For internal architecture (process model, wire protocols, versioning policy,
|
||||||
|
//! failure modes), see `ARCHITECTURE.md` in the crate root. The modules enabled
|
||||||
|
//! by the `host` feature are:
|
||||||
|
//!
|
||||||
|
//! - `host` — plugin/runtime traits and notification types.
|
||||||
|
//! - `host_v4` — per-tab shared-memory snapshot manager.
|
||||||
|
//! - `ipc` — transport, V2/V3 protocol, and V4 multiplexed protocol.
|
||||||
|
//! - `process` — `PluginProcess` and `PluginManager`.
|
||||||
|
//! - `runner` — child-process runner entry point.
|
||||||
|
//! - `shm` — shared-memory document views.
|
||||||
|
|
||||||
pub mod manifest;
|
pub mod manifest;
|
||||||
pub mod ribbon;
|
pub mod ribbon;
|
||||||
|
|
@ -64,5 +75,15 @@ pub use type_registry::{
|
||||||
};
|
};
|
||||||
pub use version_info::get_embedded_version_info_json;
|
pub use version_info::get_embedded_version_info_json;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) mod test_lock {
|
||||||
|
//! Shared lock for tests that mutate process environment variables.
|
||||||
|
//!
|
||||||
|
//! Environment variables are global mutable state. Any test that sets or
|
||||||
|
//! removes an env var must hold this lock for the duration of the mutation
|
||||||
|
//! so tests in other modules do not observe half-written state.
|
||||||
|
pub static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(feature = "host")]
|
#[cfg(feature = "host")]
|
||||||
pub use process::{DispatchResult, PluginError, PluginManager, PluginProcess};
|
pub use process::{DispatchResult, PluginError, PluginManager, PluginProcess};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,16 @@
|
||||||
//! Plugin identity and capability declaration.
|
//! Plugin identity, API version constants, and compatibility rules.
|
||||||
|
//!
|
||||||
|
//! This module defines the host ABI major version (`API_VERSION`), the oldest
|
||||||
|
//! major the host still loads (`API_VERSION_MIN_SUPPORTED`), and the runtime
|
||||||
|
//! `OCS_PLUGIN_MAX_API_VERSION` gate that lets operators disable newer API
|
||||||
|
//! majors without rebuilding.
|
||||||
|
//!
|
||||||
|
//! Compatibility rule: a plugin compiled against major `N` runs on any host
|
||||||
|
//! whose major is `>= N`, because new vtable entries and enum variants are
|
||||||
|
//! appended at the end. `host_accepts_plugin_version` enforces the supported
|
||||||
|
//! range after applying the runtime cap.
|
||||||
|
//!
|
||||||
|
//! See `ARCHITECTURE.md` in the crate root for the full versioning policy.
|
||||||
|
|
||||||
/// Host plugin API version. Bump when the host runtime surface breaks
|
/// Host plugin API version. Bump when the host runtime surface breaks
|
||||||
/// compatibility. v2 added `HostApi::start_interactive`. v3 changes
|
/// compatibility. v2 added `HostApi::start_interactive`. v3 changes
|
||||||
|
|
@ -56,12 +68,10 @@ pub fn host_accepts_plugin_version(plugin_major: u32) -> bool {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::sync::Mutex;
|
use crate::test_lock::ENV_LOCK;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
static ENV_LOCK: Mutex<()> = Mutex::new(());
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn current_matches_const() {
|
fn current_matches_const() {
|
||||||
assert_eq!(ApiVersion::CURRENT.major, API_VERSION);
|
assert_eq!(ApiVersion::CURRENT.major, API_VERSION);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,22 @@
|
||||||
//! Process management for out-of-process plugins.
|
//! Process management for out-of-process plugins.
|
||||||
|
//!
|
||||||
|
//! This module defines the lifecycle of a single plugin process:
|
||||||
|
//!
|
||||||
|
//! 1. `PluginProcess::spawn` creates a local socket, spawns the host binary in
|
||||||
|
//! runner mode (`--ocs-plugin-runner <socket> <cdylib>`), and waits for the
|
||||||
|
//! runner to connect back.
|
||||||
|
//! 2. The runner presents a pre-shared token via [`crate::ipc::protocol::PLUGIN_TOKEN_ENV`];
|
||||||
|
//! the host rejects the connection on mismatch.
|
||||||
|
//! 3. The host requests the manifest, checks `api_version` and (for v4+) the
|
||||||
|
//! acadrust source gate, then keeps the process alive.
|
||||||
|
//! 4. Host → plugin calls (`dispatch`, `execute_code`, interactive events) are
|
||||||
|
//! sent over the socket with a configurable per-call timeout.
|
||||||
|
//! 5. Stdout/stderr of the child are drained into `PluginIoLine` records for
|
||||||
|
//! logging and UI display.
|
||||||
|
//!
|
||||||
|
//! `NullHost` is a dummy `HostApi` implementation used for V4 paths that do not
|
||||||
|
//! supply a real host surface (e.g., interactive events). Timeouts and message
|
||||||
|
//! size limits are centralized here.
|
||||||
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::{Child, Command, Stdio};
|
use std::process::{Child, Command, Stdio};
|
||||||
|
|
@ -1119,6 +1137,7 @@ mod timeout_tests {
|
||||||
};
|
};
|
||||||
use crate::ipc::transport::{recv, send};
|
use crate::ipc::transport::{recv, send};
|
||||||
use crate::ribbon::owned::OwnedPluginManifest;
|
use crate::ribbon::owned::OwnedPluginManifest;
|
||||||
|
use crate::test_lock::ENV_LOCK;
|
||||||
use acadrust::xdata::ExtendedDataRecord;
|
use acadrust::xdata::ExtendedDataRecord;
|
||||||
use acadrust::{CadDocument, EntityType, Handle};
|
use acadrust::{CadDocument, EntityType, Handle};
|
||||||
use interprocess::local_socket::{
|
use interprocess::local_socket::{
|
||||||
|
|
@ -1130,8 +1149,6 @@ mod timeout_tests {
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
static ENV_LOCK: StdMutex<()> = StdMutex::new(());
|
|
||||||
|
|
||||||
struct EmptyReader;
|
struct EmptyReader;
|
||||||
|
|
||||||
impl DocumentReader for EmptyReader {
|
impl DocumentReader for EmptyReader {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,19 @@
|
||||||
//! Process manager for out-of-process plugins.
|
//! Process manager for out-of-process plugins.
|
||||||
|
//!
|
||||||
|
//! `PluginManager` is the host's owner of every loaded plugin process. It is
|
||||||
|
//! responsible for:
|
||||||
|
//!
|
||||||
|
//! - Spawning plugins via [`PluginProcess::spawn`] and building their ribbon
|
||||||
|
//! modules.
|
||||||
|
//! - Routing commands to plugins through [`PluginManager::dispatch`].
|
||||||
|
//! - Broadcasting host notifications to every alive V4 plugin via
|
||||||
|
//! [`PluginManager::broadcast_notification`].
|
||||||
|
//! - Surfacing dead plugins and per-plugin errors in [`DispatchResult`].
|
||||||
|
//!
|
||||||
|
//! Notifications are V4-only: `broadcast_notification` skips non-V4 processes.
|
||||||
|
//! The plugin-to-host notification handler (set via
|
||||||
|
//! [`PluginManager::set_notification_handler`]) runs on the V4 reader thread
|
||||||
|
//! and must not block. For heavy work, forward the notification to a channel.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
|
||||||
|
|
@ -517,7 +517,7 @@ mod tests {
|
||||||
|
|
||||||
use std::sync::Mutex as StdMutex;
|
use std::sync::Mutex as StdMutex;
|
||||||
|
|
||||||
static ENV_LOCK: StdMutex<()> = StdMutex::new(());
|
use crate::test_lock::ENV_LOCK;
|
||||||
|
|
||||||
fn set_test_env() {
|
fn set_test_env() {
|
||||||
std::env::set_var("OCS_PLUGIN_CALL_TIMEOUT_SECS", "2");
|
std::env::set_var("OCS_PLUGIN_CALL_TIMEOUT_SECS", "2");
|
||||||
|
|
@ -532,6 +532,13 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn execute_code_timeout_floor_is_at_least_60s_and_env_override() {
|
fn execute_code_timeout_floor_is_at_least_60s_and_env_override() {
|
||||||
let _guard = ENV_LOCK.lock().unwrap();
|
let _guard = ENV_LOCK.lock().unwrap();
|
||||||
|
let prev_call_timeout = std::env::var("OCS_PLUGIN_CALL_TIMEOUT_SECS").ok();
|
||||||
|
let prev_test_floor = std::env::var("OCS_PLUGIN_TEST_FLOOR_SECS").ok();
|
||||||
|
let prev_execute_timeout = std::env::var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS").ok();
|
||||||
|
// Remove the test floor bypass and the call-timeout override so the
|
||||||
|
// ExecuteCode-specific floor (60 s) is actually exercised.
|
||||||
|
std::env::remove_var("OCS_PLUGIN_CALL_TIMEOUT_SECS");
|
||||||
|
std::env::remove_var("OCS_PLUGIN_TEST_FLOOR_SECS");
|
||||||
std::env::remove_var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS");
|
std::env::remove_var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS");
|
||||||
assert!(
|
assert!(
|
||||||
request_timeout("ExecuteCode") >= Duration::from_secs(60),
|
request_timeout("ExecuteCode") >= Duration::from_secs(60),
|
||||||
|
|
@ -544,7 +551,19 @@ mod tests {
|
||||||
Duration::from_secs(120),
|
Duration::from_secs(120),
|
||||||
"env override should be respected"
|
"env override should be respected"
|
||||||
);
|
);
|
||||||
std::env::remove_var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS");
|
|
||||||
|
match prev_call_timeout {
|
||||||
|
Some(v) => std::env::set_var("OCS_PLUGIN_CALL_TIMEOUT_SECS", v),
|
||||||
|
None => std::env::remove_var("OCS_PLUGIN_CALL_TIMEOUT_SECS"),
|
||||||
|
}
|
||||||
|
match prev_test_floor {
|
||||||
|
Some(v) => std::env::set_var("OCS_PLUGIN_TEST_FLOOR_SECS", v),
|
||||||
|
None => std::env::remove_var("OCS_PLUGIN_TEST_FLOOR_SECS"),
|
||||||
|
}
|
||||||
|
match prev_execute_timeout {
|
||||||
|
Some(v) => std::env::set_var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS", v),
|
||||||
|
None => std::env::remove_var("OCS_PLUGIN_EXECUTE_TIMEOUT_SECS"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
//! (`--ocs-plugin-runner <socket> <cdylib>`). Keeping the runner code inside
|
//! (`--ocs-plugin-runner <socket> <cdylib>`). Keeping the runner code inside
|
||||||
//! `ocs_plugin_api` means the host only needs to know the CLI contract, not the
|
//! `ocs_plugin_api` means the host only needs to know the CLI contract, not the
|
||||||
//! internal plugin-loading and IPC details.
|
//! internal plugin-loading and IPC details.
|
||||||
|
//!
|
||||||
|
//! Entry points and loops:
|
||||||
|
//!
|
||||||
|
//! - [`run`] — loads the cdylib with `libloading`, connects back to the host,
|
||||||
|
//! and dispatches to either the V2/V3 loop or the V4 loop based on the plugin's
|
||||||
|
//! declared API version.
|
||||||
|
//! - `run_v3` — synchronous request/response loop over [`crate::ipc::client::IpcClient`].
|
||||||
|
//! - `run_v4` — multiplexed frame loop over [`crate::ipc::v4::client::V4Client`];
|
||||||
|
//! drains notifications and dispatches host requests.
|
||||||
|
//!
|
||||||
|
//! Panics inside plugin callbacks are caught by `catch_unwind` and converted to
|
||||||
|
//! error responses so a buggy plugin callback does not tear down the runner.
|
||||||
|
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,21 @@
|
||||||
//! This module is generic over the payload type so that the V3 simplified view
|
//! This module is generic over the payload type so that the V3 simplified view
|
||||||
//! and the V4 bincode-per-entity view can share the same double-buffered
|
//! and the V4 bincode-per-entity view can share the same double-buffered
|
||||||
//! control-page logic.
|
//! control-page logic.
|
||||||
|
//!
|
||||||
|
//! Key types:
|
||||||
|
//!
|
||||||
|
//! - [`DocumentSnapshotStore<T>`] — host-side, file-backed double buffer. Call
|
||||||
|
//! [`DocumentSnapshotStore::publish`] to atomically swap the active segment and
|
||||||
|
//! increment the version.
|
||||||
|
//! - [`SharedDocumentReader<T>`] — plugin-side read-only mapping.
|
||||||
|
//! - [`DocumentViewInfo`] — path + version returned to the plugin so it can
|
||||||
|
//! open the mapping.
|
||||||
|
//! - [`DocumentViewData`] / [`DocumentViewDataV4`] — concrete snapshot payloads
|
||||||
|
//! for V3 and V4.
|
||||||
|
//!
|
||||||
|
//! The control page at the start of the mapping stores magic, version, active
|
||||||
|
//! segment, and length. The two snapshot segments follow it. Publishing writes
|
||||||
|
//! to the inactive segment, then flips the active segment atomically.
|
||||||
|
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,15 @@
|
||||||
//! The JSON embedded here is produced by tracing a curated allow-list of
|
//! The JSON embedded here is produced by tracing a curated allow-list of
|
||||||
//! `acadrust` types with `serde-reflection` and mapping the result into a
|
//! `acadrust` types with `serde-reflection` and mapping the result into a
|
||||||
//! stable, language-binding-friendly schema defined in
|
//! stable, language-binding-friendly schema defined in
|
||||||
//! [`crate::type_registry_types`].
|
//! [`crate::type_registry_types`]. The registry is embedded via
|
||||||
|
//! `include_str!(concat!(env!("OUT_DIR"), "/type_registry.json"))` so the
|
||||||
|
//! `host` feature is not required to read it.
|
||||||
|
//!
|
||||||
|
//! The allow-list is intentionally narrow: it excludes generic containers and
|
||||||
|
//! internal enums such as `EntityType` in favor of concrete serializable structs
|
||||||
|
//! like `Point`, `Line`, `Circle`, and `MText`. Language bindings and tooling
|
||||||
|
//! can depend on this schema without pulling the full `acadrust` dependency
|
||||||
|
//! tree.
|
||||||
|
|
||||||
pub use crate::type_registry_types::*;
|
pub use crate::type_registry_types::*;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,18 @@
|
||||||
//! Embedded version metadata generated at build time.
|
//! Embedded version metadata generated at build time.
|
||||||
|
//!
|
||||||
|
//! At compile time `build.rs` writes a small JSON blob to
|
||||||
|
//! `OUT_DIR/version_info.json` containing host version, `ocs_plugin_api`
|
||||||
|
//! version, `acadrust` version and source, API version bounds, and a build
|
||||||
|
//! timestamp. The blob is embedded via `include_str!` and is available without
|
||||||
|
//! enabling the `host` feature.
|
||||||
|
//!
|
||||||
|
//! The acadrust source string is used to detect binary-incompatible plugin
|
||||||
|
//! builds. For API v4 and later the host compares the plugin's resolved
|
||||||
|
//! `acadrust` source with its own via [`acadrust_sources_compatible`]. Two
|
||||||
|
//! sources are considered compatible only when they resolve to the same 40
|
||||||
|
//! character git commit hash.
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
/// The embedded version info as a JSON string.
|
/// The embedded version info as a JSON string.
|
||||||
pub const EMBEDDED_VERSION_INFO_JSON: &str =
|
pub const EMBEDDED_VERSION_INFO_JSON: &str =
|
||||||
|
|
@ -9,6 +23,57 @@ pub fn get_embedded_version_info_json() -> &'static str {
|
||||||
EMBEDDED_VERSION_INFO_JSON
|
EMBEDDED_VERSION_INFO_JSON
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct VersionInfo {
|
||||||
|
acadrust_source: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ACADRUST_GATE_API_VERSION: u32 = 4;
|
||||||
|
|
||||||
|
/// Returns whether this API version uses the dependency gate.
|
||||||
|
pub fn uses_acadrust_gate(api_version: u32) -> bool {
|
||||||
|
api_version >= ACADRUST_GATE_API_VERSION
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts a string from the generated compact JSON.
|
||||||
|
fn json_string_value(json: &str, key: &str) -> Option<String> {
|
||||||
|
let pattern = format!(r#""{key}":""#);
|
||||||
|
let start = json.find(&pattern)? + pattern.len();
|
||||||
|
let end = json[start..].find('"')?;
|
||||||
|
Some(json[start..start + end].to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parsed_version_info() -> &'static VersionInfo {
|
||||||
|
static INFO: OnceLock<VersionInfo> = OnceLock::new();
|
||||||
|
INFO.get_or_init(|| VersionInfo {
|
||||||
|
acadrust_source: json_string_value(EMBEDDED_VERSION_INFO_JSON, "acadrust_source")
|
||||||
|
.unwrap_or_default(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the host's full `acadrust` Cargo source.
|
||||||
|
pub fn host_acadrust_source() -> &'static str {
|
||||||
|
&parsed_version_info().acadrust_source
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extracts the full git commit hash from a Cargo source.
|
||||||
|
pub fn acadrust_source_hash(source: &str) -> Option<&str> {
|
||||||
|
let hash = source.rsplit('#').next()?;
|
||||||
|
if hash.len() == 40 && hash.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||||
|
Some(hash)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether two Cargo sources use the same commit.
|
||||||
|
pub fn acadrust_sources_compatible(a: &str, b: &str) -> bool {
|
||||||
|
match (acadrust_source_hash(a), acadrust_source_hash(b)) {
|
||||||
|
(Some(ha), Some(hb)) => ha.eq_ignore_ascii_case(hb),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -19,7 +84,7 @@ mod tests {
|
||||||
assert!(!json.is_empty());
|
assert!(!json.is_empty());
|
||||||
let value: serde_json::Value = serde_json::from_str(json).expect("valid JSON");
|
let value: serde_json::Value = serde_json::from_str(json).expect("valid JSON");
|
||||||
assert!(value.get("ocs_version").is_some());
|
assert!(value.get("ocs_version").is_some());
|
||||||
assert!(value.get("acadrust_version").is_some());
|
assert!(value.get("acadrust_source").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -54,4 +119,57 @@ mod tests {
|
||||||
let acadrust = value["acadrust_version"].as_str().expect("acadrust_version string");
|
let acadrust = value["acadrust_version"].as_str().expect("acadrust_version string");
|
||||||
assert_eq!(acadrust.split('.').count(), 3);
|
assert_eq!(acadrust.split('.').count(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn host_acadrust_source_is_non_empty() {
|
||||||
|
assert!(!host_acadrust_source().is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn dependency_gate_starts_at_api_v4() {
|
||||||
|
assert!(!uses_acadrust_gate(3));
|
||||||
|
assert!(uses_acadrust_gate(4));
|
||||||
|
assert!(uses_acadrust_gate(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extracts_full_hash_from_cargo_source() {
|
||||||
|
let src = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94df2c3f87fa051b16ffc3923f80e9247c85c5fd";
|
||||||
|
assert_eq!(
|
||||||
|
acadrust_source_hash(src),
|
||||||
|
Some("94df2c3f87fa051b16ffc3923f80e9247c85c5fd")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_malformed_or_missing_hash() {
|
||||||
|
assert!(acadrust_source_hash("").is_none());
|
||||||
|
assert!(acadrust_source_hash("registry+https://crates.io").is_none());
|
||||||
|
assert!(acadrust_source_hash("git+https://github.com/foo/bar.git#short").is_none());
|
||||||
|
assert!(
|
||||||
|
acadrust_source_hash("git+https://github.com/foo/bar.git#gggggggggggggggggggggggggggggggggggggggg")
|
||||||
|
.is_none()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_comparison_matches_full_hashes() {
|
||||||
|
let a = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94df2c3f87fa051b16ffc3923f80e9247c85c5fd";
|
||||||
|
let b = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94df2c3f87fa051b16ffc3923f80e9247c85c5fd";
|
||||||
|
assert!(acadrust_sources_compatible(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_comparison_detects_mismatch() {
|
||||||
|
let a = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94df2c3f87fa051b16ffc3923f80e9247c85c5fd";
|
||||||
|
let b = "git+https://github.com/HakanSeven12/cadcodec.git?rev=0908da7#0908da7b6e4f702a6c78359a57f53e2b79cf39eb";
|
||||||
|
assert!(!acadrust_sources_compatible(a, b));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn source_comparison_case_insensitive() {
|
||||||
|
let a = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94df2c3f87fa051b16ffc3923f80e9247c85c5fd";
|
||||||
|
let b = "git+https://github.com/HakanSeven12/cadcodec.git?rev=94df2c3#94DF2C3F87FA051B16FFC3923F80E9247C85C5FD";
|
||||||
|
assert!(acadrust_sources_compatible(a, b));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ publish = false
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "db73dab", features = ["serde"] }
|
acadrust = { git = "https://github.com/HakanSeven12/cadcodec.git", rev = "5b2ae66", 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"
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ concrete types:
|
||||||
| Command line | `push_info`, `push_output`, `push_error` |
|
| Command line | `push_info`, `push_output`, `push_error` |
|
||||||
| Undo / dirty | `push_undo`, `set_dirty` |
|
| Undo / dirty | `push_undo`, `set_dirty` |
|
||||||
| Tab | `tab_index()` |
|
| Tab | `tab_index()` |
|
||||||
|
| Notifications (V4) | `on_notification` receives `HostNotification::SelectionChangedV4 { tab_id, handles }` when the active tab's selection changes, plus `DocumentChangedV4` / `DocumentTabClosed`. |
|
||||||
|
|
||||||
### `export_plugin!` — the C-ABI export
|
### `export_plugin!` — the C-ABI export
|
||||||
|
|
||||||
|
|
@ -309,7 +310,7 @@ Two timeouts protect the host from a stuck runner:
|
||||||
|
|
||||||
| Timeout | Env var | Default | Floor |
|
| Timeout | Env var | Default | Floor |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| Spawn (connection) | `OCS_PLUGIN_SPAWN_TIMEOUT_SECS` | 10 s | — |
|
| Spawn (connection) | `OCS_PLUGIN_SPAWN_TIMEOUT_SECS` | 30 s | — |
|
||||||
| Per-call | `OCS_PLUGIN_CALL_TIMEOUT_SECS` | 30 s | `GetManifest`/`GetRibbon` ≥ 5 s, `Dispatch` ≥ 10 s, interactive events/prompt/pick ≥ 2 s |
|
| Per-call | `OCS_PLUGIN_CALL_TIMEOUT_SECS` | 30 s | `GetManifest`/`GetRibbon` ≥ 5 s, `Dispatch` ≥ 10 s, interactive events/prompt/pick ≥ 2 s |
|
||||||
|
|
||||||
A call timeout covers the full round-trip, including any nested plugin→host
|
A call timeout covers the full round-trip, including any nested plugin→host
|
||||||
|
|
@ -402,6 +403,7 @@ Next:
|
||||||
| Piece | Location |
|
| Piece | Location |
|
||||||
|-------|----------|
|
|-------|----------|
|
||||||
| Contract crate + runtime | [`crates/ocs_plugin_api`](../crates/ocs_plugin_api) |
|
| Contract crate + runtime | [`crates/ocs_plugin_api`](../crates/ocs_plugin_api) |
|
||||||
|
| Internal API architecture | [`crates/ocs_plugin_api/ARCHITECTURE.md`](../crates/ocs_plugin_api/ARCHITECTURE.md) |
|
||||||
| Plugin runner implementation | [`crates/ocs_plugin_api/src/runner.rs`](../crates/ocs_plugin_api/src/runner.rs) |
|
| Plugin runner implementation | [`crates/ocs_plugin_api/src/runner.rs`](../crates/ocs_plugin_api/src/runner.rs) |
|
||||||
| Host spawn logic | [`crates/ocs_plugin_api/src/process.rs`](../crates/ocs_plugin_api/src/process.rs) |
|
| Host spawn logic | [`crates/ocs_plugin_api/src/process.rs`](../crates/ocs_plugin_api/src/process.rs) |
|
||||||
| Host plugin integration | `src/plugin/`, `src/app/plugin_host.rs` |
|
| Host plugin integration | `src/plugin/`, `src/app/plugin_host.rs` |
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = رسم
|
.draw = رسم
|
||||||
.annotate = تعليقات توضيحية
|
.annotate = تعليقات توضيحية
|
||||||
.insert = إدراج
|
.insert = إدراج
|
||||||
.model = نموذج
|
.model = النمذجة
|
||||||
.layout = تخطيط
|
.layout = تخطيط
|
||||||
.manage = إدارة
|
.manage = إدارة
|
||||||
.view = عرض
|
.view = عرض
|
||||||
|
|
@ -345,6 +345,7 @@ common =
|
||||||
.brightness = السطوع
|
.brightness = السطوع
|
||||||
.by-layer = حسب الطبقة
|
.by-layer = حسب الطبقة
|
||||||
.byblock = حسب الكتلة
|
.byblock = حسب الكتلة
|
||||||
|
.bycolor = حسب اللون
|
||||||
.bylayer = حسب الطبقة
|
.bylayer = حسب الطبقة
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: تم تحويل __ocs_fmt_0__ مجسم إلى أسطح.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: تم تحويل __ocs_fmt_0__ مجسم إلى أسطح.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: حدد مجسمًا أُنشئ في هذه الجلسة.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: حدد مجسمًا أُنشئ في هذه الجلسة.
|
||||||
|
|
@ -1811,14 +1812,24 @@ annotate =
|
||||||
.collect-leaders = تجميع الخطوط القائدة
|
.collect-leaders = تجميع الخطوط القائدة
|
||||||
.ddedit-select-text-entity = DDEDIT حدد كيان النص:
|
.ddedit-select-text-entity = DDEDIT حدد كيان النص:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED أدخل نص البُعد (فارغ = القيمة المقاسة):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED أدخل نص البُعد (فارغ = القيمة المقاسة):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED حدد موقع خط البُعد [نص/زاوية]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED أدخل نص البُعد المنسق (فارغ = القيمة المقاسة):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED حدد أصل خط الامتداد الأول:
|
.dimaligned-select-object-to-dimension = DIMALIGNED حدد كائنًا لقياسه:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED حدد أصل خط الامتداد الثاني [نص/زاوية]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED حدد موقع خط البُعد [Mtext/نص/زاوية]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED حدد أصل خط الامتداد الأول أو اضغط Enter لتحديد كائن:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED حدد أصل خط الامتداد الثاني:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED حدد زاوية النص (بالدرجات):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED حدد زاوية النص (بالدرجات):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR أدخل نص البُعد (فارغ = القيمة المقاسة):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR أدخل نص البُعد المنسق (فارغ = القيمة المقاسة):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR حدد قوسًا أو دائرة أو خطًا، أو حدد رأس زاوية:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR حدد الخط الثاني:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR حدد رأس الزاوية:
|
.dimangular-specify-angle-vertex = DIMANGULAR حدد رأس الزاوية:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR حدد موقع قوس البُعد [Mtext/نص/زاوية/ربع]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR حدد موقع قوس البُعد:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR حدد موقع قوس البُعد:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR حدد نقطة خط الامتداد الأول:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR حدد نقطة خط الامتداد الأول:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR حدد الربع:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR حدد نقطة خط الامتداد الثاني:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR حدد نقطة خط الامتداد الثاني:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR حدد النقطة الثانية لزاوية النص:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR حدد زاوية النص أو النقطة الأولى:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE لم يُعثر على بُعد أساسي. ضع بُعدًا أولًا.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE لم يُعثر على بُعد أساسي. ضع بُعدًا أولًا.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE حدد أصل خط امتداد ثانٍ (Enter للخروج):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE حدد أصل خط امتداد ثانٍ (Enter للخروج):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK حدد البُعد المراد قطعه:
|
.dimbreak-select-dimension-to-break = DIMBREAK حدد البُعد المراد قطعه:
|
||||||
|
|
@ -1826,8 +1837,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE لم يُعثر على بُعد أساسي. ضع بُعدًا أولًا.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE لم يُعثر على بُعد أساسي. ضع بُعدًا أولًا.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE حدد أصل خط امتداد ثانٍ (Enter للخروج):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE حدد أصل خط امتداد ثانٍ (Enter للخروج):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER أدخل نص البُعد (فارغ = القيمة المقاسة):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER أدخل نص البُعد (فارغ = القيمة المقاسة):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER حدد نقطة المركز:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER حدد قوسًا أو دائرة أو قوسًا متعدد الخطوط:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER حدد موقع خط البُعد [نص/زاوية]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER حدد موقع خط البُعد [نص متعدد الأسطر/نص/زاوية]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER حدد نقطة على الدائرة:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER حدد نقطة على الدائرة:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER حدد زاوية النص (بالدرجات):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER حدد زاوية النص (بالدرجات):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT أدخل نص التجاوز (فارغ = إعادة للقيمة المقاسة):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT أدخل نص التجاوز (فارغ = إعادة للقيمة المقاسة):
|
||||||
|
|
@ -1843,10 +1854,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR حدد أصل خط الامتداد الثاني [نص/زاوية]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR حدد أصل خط الامتداد الثاني [نص/زاوية]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR حدد زاوية النص (بالدرجات):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR حدد زاوية النص (بالدرجات):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE حدد موقع المعلم:
|
.dimordinate-specify-feature-location = DIMORDINATE حدد موقع المعلم:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE حدد نقطة نهاية الخط القائد:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE أدخل نص البُعد (فارغ = القيمة المقاسة):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE حدد نقطة نهاية الخط القائد [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE حدد زاوية النص (بالدرجات):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS أدخل نص البُعد (فارغ = القيمة المقاسة):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS أدخل نص البُعد (فارغ = القيمة المقاسة):
|
||||||
.dimradius-specify-center-point = DIMRADIUS حدد نقطة المركز:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS حدد قوسًا أو دائرة أو قوسًا متعدد الخطوط:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS حدد موقع خط البُعد [نص/زاوية]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS حدد موقع خط البُعد [نص متعدد الأسطر/نص/زاوية]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS حدد نقطة نصف القطر:
|
.dimradius-specify-radius-point = DIMRADIUS حدد نقطة نصف القطر:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS حدد زاوية النص (بالدرجات):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS حدد زاوية النص (بالدرجات):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE أدخل القيمة (0 = تلقائي):
|
.dimspace-enter-value-0-auto = DIMSPACE أدخل القيمة (0 = تلقائي):
|
||||||
|
|
@ -1878,6 +1891,7 @@ annotate =
|
||||||
البيانات
|
البيانات
|
||||||
.lower = خفض
|
.lower = خفض
|
||||||
.mleader-specify-arrowhead-point = MLEADER حدد نقطة رأس السهم:
|
.mleader-specify-arrowhead-point = MLEADER حدد نقطة رأس السهم:
|
||||||
|
.mleader-specify-landing-point = MLEADER حدد نقطة خط الإسناد:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER حدد النقطة التالية [__ocs_arg_count__ نقاط — Enter لوضع النص]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER حدد النقطة التالية [__ocs_arg_count__ نقاط — Enter لوضع النص]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD حدد خطًا قائدًا متعددًا:
|
.mleaderadd-select-a-multileader = MLEADERADD حدد خطًا قائدًا متعددًا:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD حدد موقع رأس السهم:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD حدد موقع رأس السهم:
|
||||||
|
|
@ -2047,6 +2061,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = قُص __ocs_fmt_0__ كائن إلى الحافظة.
|
.object-s-cut-to-clipboard = قُص __ocs_fmt_0__ كائن إلى الحافظة.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = إنشاء
|
||||||
|
.boolean = عمليات منطقية
|
||||||
|
.pyramid = هرم
|
||||||
|
.polysolid = مجسّم متعدد
|
||||||
|
.extrude = بثق
|
||||||
|
.revolve = تدوير
|
||||||
|
.loft = تشكيل انتقالي
|
||||||
|
.sweep = سحب على مسار
|
||||||
|
.presspull = دفع/سحب
|
||||||
.n-specify-center-point = __ocs_arg_n__ حدد نقطة المركز:
|
.n-specify-center-point = __ocs_arg_n__ حدد نقطة المركز:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ حدد الزاوية الأولى:
|
.n-specify-first-corner = __ocs_arg_n__ حدد الزاوية الأولى:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ حدد الارتفاع <Enter للافتراضي>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ حدد الارتفاع <Enter للافتراضي>:
|
||||||
|
|
@ -2347,6 +2370,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
تكبير إلى
|
تكبير إلى
|
||||||
النطاقات
|
النطاقات
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ┘äϺ Ϭ┘êϼϻ ┘âϺϪ┘åϺϬ Ϭϩ┘ä┘è┘ä.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: لا توجد كائنات نص أو أبعاد.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: لا توجد كائنات نص أو أبعاد.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: لم يُنفّذ بعد.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: لم يُنفّذ بعد.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ منفذ عرض في التخطيط "__ocs_fmt_1__":
|
.viewport-s-in-layout = __ocs_fmt_0__ منفذ عرض في التخطيط "__ocs_fmt_1__":
|
||||||
|
|
@ -2724,6 +2748,9 @@ properties =
|
||||||
.plot-style-table = جدول نمط الطباعة
|
.plot-style-table = جدول نمط الطباعة
|
||||||
.plot-table-attached-to = جدول الطباعة مرفق بـ
|
.plot-table-attached-to = جدول الطباعة مرفق بـ
|
||||||
.plot-table-type = نوع جدول الطباعة
|
.plot-table-type = نوع جدول الطباعة
|
||||||
|
.color-dependent-plot-styles = أنماط طباعة معتمدة على اللون
|
||||||
|
.named-plot-styles = أنماط طباعة مسماة
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = نمط الطباعة مقيد باللون في الوضع المعتمد على اللون (CTB)
|
||||||
.point-1-x = النقطة 1 X
|
.point-1-x = النقطة 1 X
|
||||||
.point-1-y = النقطة 1 Y
|
.point-1-y = النقطة 1 Y
|
||||||
.point-1-z = النقطة 1 Z
|
.point-1-z = النقطة 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Чертане
|
.draw = Чертане
|
||||||
.annotate = Анотиране
|
.annotate = Анотиране
|
||||||
.insert = Вмъкване
|
.insert = Вмъкване
|
||||||
.model = Модел
|
.model = Моделиране
|
||||||
.layout = Оформление
|
.layout = Оформление
|
||||||
.manage = Управление
|
.manage = Управление
|
||||||
.view = Изглед
|
.view = Изглед
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = Яркост
|
.brightness = Яркост
|
||||||
.by-layer = По слой
|
.by-layer = По слой
|
||||||
.byblock = По блок
|
.byblock = По блок
|
||||||
|
.bycolor = По цвят
|
||||||
.bylayer = По слой
|
.bylayer = По слой
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: конвертирани __ocs_fmt_0__ тяло/а в повърхност/и.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: конвертирани __ocs_fmt_0__ тяло/а в повърхност/и.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: избери тяло, създадено в тази сесия.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: избери тяло, създадено в тази сесия.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = Събери водачи
|
.collect-leaders = Събери водачи
|
||||||
.ddedit-select-text-entity = DDEDIT Избери текстов обект:
|
.ddedit-select-text-entity = DDEDIT Избери текстов обект:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Въведи текст на оразмеряване (празно = измерена стойност):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Въведи текст на оразмеряване (празно = измерена стойност):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Посочи местоположение на линията за оразмеряване [Текст/Ъгъл]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Въведи форматиран текст на оразмеряване (празно = измерена стойност):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Посочи начало на първата удължителна линия:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Избери обект за оразмеряване:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Посочи начало на втората удължителна линия [Текст/Ъгъл]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Посочи местоположение на линията за оразмеряване [Mtext/Текст/Ъгъл]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Посочи начало на първата удължителна линия или натисни Enter за избор на обект:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Посочи начало на втората удължителна линия:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Посочи ъгъл на текста (градуси):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Посочи ъгъл на текста (градуси):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Въведи текст на оразмеряване (празно = измерена стойност):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Въведи форматиран текст на оразмеряване (празно = измерена стойност):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Избери дъга, окръжност, линия или посочи връх на ъгъла:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Избери втората линия:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Посочи връх на ъгъла:
|
.dimangular-specify-angle-vertex = DIMANGULAR Посочи връх на ъгъла:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Посочи местоположение на дъгата за оразмеряване [Mtext/Текст/Ъгъл/Квадрант]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Посочи местоположение на дъгата за оразмеряване:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Посочи местоположение на дъгата за оразмеряване:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Посочи точка на първата удължителна линия:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Посочи точка на първата удължителна линия:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Посочи квадрант:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Посочи точка на втората удължителна линия:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Посочи точка на втората удължителна линия:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Посочи втора точка за ъгъла на текста:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Посочи ъгъл на текста или първа точка:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Не е намерено базово оразмеряване. Първо постави оразмеряване.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Не е намерено базово оразмеряване. Първо постави оразмеряване.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Посочи начало на втора удължителна линия (Enter за изход):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Посочи начало на втора удължителна линия (Enter за изход):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Избери оразмеряване за прекъсване:
|
.dimbreak-select-dimension-to-break = DIMBREAK Избери оразмеряване за прекъсване:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Не е намерено базово оразмеряване. Първо постави оразмеряване.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Не е намерено базово оразмеряване. Първо постави оразмеряване.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Посочи начало на втора удължителна линия (Enter за изход):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Посочи начало на втора удължителна линия (Enter за изход):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Въведи текст на оразмеряване (празно = измерена стойност):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Въведи текст на оразмеряване (празно = измерена стойност):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Посочи централна точка:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Избери дъга, окръжност или дъгов сегмент на полилиния:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Посочи местоположение на линията за оразмеряване [Текст/Ъгъл]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Посочи местоположение на линията за оразмеряване [Мтекст/Текст/Ъгъл]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Посочи точка на окръжност:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Посочи точка на окръжност:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Посочи ъгъл на текста (градуси):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Посочи ъгъл на текста (градуси):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Въведи заместващ текст (празно = върни към измерена стойност):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Въведи заместващ текст (празно = върни към измерена стойност):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Посочи начало на втората удължителна линия [Текст/Ъгъл]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Посочи начало на втората удължителна линия [Текст/Ъгъл]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Посочи ъгъл на текста (градуси):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Посочи ъгъл на текста (градуси):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Посочи местоположение на елемента:
|
.dimordinate-specify-feature-location = DIMORDINATE Посочи местоположение на елемента:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Посочи крайна точка на водача:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Въведи текст на оразмеряване (празно = измерена стойност):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Посочи крайна точка на водача [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Посочи ъгъл на текста (градуси):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Въведи текст на оразмеряване (празно = измерена стойност):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Въведи текст на оразмеряване (празно = измерена стойност):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Посочи централна точка:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Избери дъга, окръжност или дъгов сегмент на полилиния:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Посочи местоположение на линията за оразмеряване [Текст/Ъгъл]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Посочи местоположение на линията за оразмеряване [Мтекст/Текст/Ъгъл]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Посочи точка на радиус:
|
.dimradius-specify-radius-point = DIMRADIUS Посочи точка на радиус:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Посочи ъгъл на текста (градуси):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Посочи ъгъл на текста (градуси):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Въведи стойност (0 = авто):
|
.dimspace-enter-value-0-auto = DIMSPACE Въведи стойност (0 = авто):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
данни
|
данни
|
||||||
.lower = Малки букви
|
.lower = Малки букви
|
||||||
.mleader-specify-arrowhead-point = MLEADER Посочи точка на върха на стрелката:
|
.mleader-specify-arrowhead-point = MLEADER Посочи точка на върха на стрелката:
|
||||||
|
.mleader-specify-landing-point = MLEADER Посочи точката на хоризонталния участък:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Посочи следваща точка [__ocs_arg_count__ т. — Enter за поставяне на текст]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Посочи следваща точка [__ocs_arg_count__ т. — Enter за поставяне на текст]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Избери многоводач:
|
.mleaderadd-select-a-multileader = MLEADERADD Избери многоводач:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Посочи местоположение на върха на стрелката:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Посочи местоположение на върха на стрелката:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ обект(а) изрязани в клипборда.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ обект(а) изрязани в клипборда.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Създаване
|
||||||
|
.boolean = Булеви операции
|
||||||
|
.pyramid = Пирамида
|
||||||
|
.polysolid = Полисолид
|
||||||
|
.extrude = Екструдиране
|
||||||
|
.revolve = Завъртане
|
||||||
|
.loft = Лофт
|
||||||
|
.sweep = Изтегляне по траектория
|
||||||
|
.presspull = Натискане/издърпване
|
||||||
.n-specify-center-point = __ocs_arg_n__ Посочи централна точка:
|
.n-specify-center-point = __ocs_arg_n__ Посочи централна точка:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Посочи първи ъгъл:
|
.n-specify-first-corner = __ocs_arg_n__ Посочи първи ъгъл:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Посочи височина <Enter за подразбиране>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Посочи височина <Enter за подразбиране>:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Мащаб
|
Мащаб
|
||||||
Обхват
|
Обхват
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ð¢ÐÅð╝ð░ ÐëÐÇð©Ðàð¥ð▓ÐèÐçð¢ð© ð¥ð▒ðÁð║Ðéð©.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: няма текстови обекти или оразмерявания.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: няма текстови обекти или оразмерявания.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: все още не е реализирано.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: все още не е реализирано.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ изглед(а) в оформление "__ocs_fmt_1__":
|
.viewport-s-in-layout = __ocs_fmt_0__ изглед(а) в оформление "__ocs_fmt_1__":
|
||||||
|
|
@ -2712,6 +2736,9 @@ properties =
|
||||||
.plot-style-table = Таблица със стилове за плотиране
|
.plot-style-table = Таблица със стилове за плотиране
|
||||||
.plot-table-attached-to = Таблицата за плотиране е прикачена към
|
.plot-table-attached-to = Таблицата за плотиране е прикачена към
|
||||||
.plot-table-type = Тип на таблица за плотиране
|
.plot-table-type = Тип на таблица за плотиране
|
||||||
|
.color-dependent-plot-styles = Цветово-зависими стилове за печат
|
||||||
|
.named-plot-styles = Именувани стилове за печат
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = Стилът за печат е заключен към цвета в цветово-зависим режим (CTB)
|
||||||
.point-1-x = Точка 1 X
|
.point-1-x = Точка 1 X
|
||||||
.point-1-y = Точка 1 Y
|
.point-1-y = Точка 1 Y
|
||||||
.point-1-z = Точка 1 Z
|
.point-1-z = Точка 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Kreslení
|
.draw = Kreslení
|
||||||
.annotate = Poznámky
|
.annotate = Poznámky
|
||||||
.insert = Vložit
|
.insert = Vložit
|
||||||
.model = Model
|
.model = Modelování
|
||||||
.layout = Rozvržení
|
.layout = Rozvržení
|
||||||
.manage = Správa
|
.manage = Správa
|
||||||
.view = Zobrazení
|
.view = Zobrazení
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = Jas
|
.brightness = Jas
|
||||||
.by-layer = Podle vrstvy
|
.by-layer = Podle vrstvy
|
||||||
.byblock = ByBlock
|
.byblock = ByBlock
|
||||||
|
.bycolor = Podle barvy
|
||||||
.bylayer = Podle vrstvy
|
.bylayer = Podle vrstvy
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: převede tělesa __ocs_fmt_0__ na povrchy.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: převede tělesa __ocs_fmt_0__ na povrchy.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: vyberte těleso vytvořené v této relaci.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: vyberte těleso vytvořené v této relaci.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = Sbírejte vůdce
|
.collect-leaders = Sbírejte vůdce
|
||||||
.ddedit-select-text-entity = DDEDIT Vyberte textovou entitu:
|
.ddedit-select-text-entity = DDEDIT Vyberte textovou entitu:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = Text rozměru DIMALIGNED Enter (prázdná naměřená hodnota =):
|
.dimaligned-enter-dimension-text-blank-measured = Text rozměru DIMALIGNED Enter (prázdná naměřená hodnota =):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Určete umístění kótovací čáry [Text/Úhel]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Zadejte formátovaný text kóty (prázdné = naměřená hodnota):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Zadejte počátek prvního vynášecího řádku:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Vyberte objekt ke kótování:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Zadejte počátek druhé vynášecí čáry [Text/Úhel]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Určete umístění kótovací čáry [Mtext/Text/Úhel]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Zadejte počátek první vynášecí čáry nebo stiskněte Enter pro výběr objektu:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Zadejte počátek druhé vynášecí čáry:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Zadejte úhel textu (stupně):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Zadejte úhel textu (stupně):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Zadejte text kóty (prázdné = naměřená hodnota):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Zadejte formátovaný text kóty (prázdné = naměřená hodnota):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Vyberte oblouk, kružnici či čáru, nebo zadejte vrchol úhlu:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Vyberte druhou čáru:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Zadejte vrchol úhlu:
|
.dimangular-specify-angle-vertex = DIMANGULAR Zadejte vrchol úhlu:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Zadejte umístění oblouku kóty [Mtext/Text/Úhel/Kvadrant]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Zadejte umístění oblouku kóty:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Zadejte umístění oblouku kóty:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Zadejte první bod vynášecí čáry:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Zadejte první bod vynášecí čáry:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Zadejte kvadrant:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Zadejte druhý bod vynášecí čáry:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Zadejte druhý bod vynášecí čáry:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Zadejte druhý bod úhlu textu:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Zadejte úhel textu nebo první bod:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nebyl nalezen žádný rozměr základny. Nejprve umístěte kótu.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nebyl nalezen žádný rozměr základny. Nejprve umístěte kótu.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Zadejte počátek druhé prodlužovací čáry (Enter pro ukončení):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Zadejte počátek druhé prodlužovací čáry (Enter pro ukončení):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Vyberte rozměr, který chcete přerušit:
|
.dimbreak-select-dimension-to-break = DIMBREAK Vyberte rozměr, který chcete přerušit:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nebyl nalezen žádný rozměr základny. Nejprve umístěte kótu.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nebyl nalezen žádný rozměr základny. Nejprve umístěte kótu.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Zadejte počátek druhé prodlužovací čáry (Enter pro ukončení):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Zadejte počátek druhé prodlužovací čáry (Enter pro ukončení):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = Text rozměru DIMDIAMETER Enter (prázdná naměřená hodnota =):
|
.dimdiameter-enter-dimension-text-blank-measure = Text rozměru DIMDIAMETER Enter (prázdná naměřená hodnota =):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Zadejte středový bod:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Vyberte oblouk, kružnici nebo obloukový segment křivky:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Určete umístění kótovací čáry [Text/Úhel]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Určete umístění kótovací čáry [MText/Text/Úhel]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Určete bod na kružnici:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Určete bod na kružnici:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Zadejte úhel textu (stupně):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Zadejte úhel textu (stupně):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = Přepsání textu DIMEDIT Enter (prázdné = resetováno na měření):
|
.dimedit-enter-text-override-blank-reset-to-mea = Přepsání textu DIMEDIT Enter (prázdné = resetováno na měření):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Zadejte počátek druhé vynášecí čáry [Text/Úhel]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Zadejte počátek druhé vynášecí čáry [Text/Úhel]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Zadejte úhel textu (stupně):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Zadejte úhel textu (stupně):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Zadejte umístění funkce:
|
.dimordinate-specify-feature-location = DIMORDINATE Zadejte umístění funkce:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Zadejte koncový bod odkazu:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Zadejte text rozměru (prázdné = naměřená hodnota):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Zadejte koncový bod odkazu [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Zadejte úhel textu (stupně):
|
||||||
.dimradius-enter-dimension-text-blank-measured = Text rozměru DIMRADIUS Enter (prázdná naměřená hodnota =):
|
.dimradius-enter-dimension-text-blank-measured = Text rozměru DIMRADIUS Enter (prázdná naměřená hodnota =):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Zadejte středový bod:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Vyberte oblouk, kružnici nebo obloukový segment křivky:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Určete umístění kótovací čáry [Text/Úhel]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Určete umístění kótovací čáry [MText/Text/Úhel]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Zadejte bod poloměru:
|
.dimradius-specify-radius-point = DIMRADIUS Zadejte bod poloměru:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Zadejte úhel textu (stupně):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Zadejte úhel textu (stupně):
|
||||||
.dimspace-enter-value-0-auto = Hodnota DIMSPACE Enter (0 = auto):
|
.dimspace-enter-value-0-auto = Hodnota DIMSPACE Enter (0 = auto):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
Data
|
Data
|
||||||
.lower = Nižší
|
.lower = Nižší
|
||||||
.mleader-specify-arrowhead-point = MLEADER Zadejte bod šipky:
|
.mleader-specify-arrowhead-point = MLEADER Zadejte bod šipky:
|
||||||
|
.mleader-specify-landing-point = MLEADER Zadejte bod odskoku:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Zadejte další bod [__ocs_arg_count__ bodů — Enter pro umístění textu]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Zadejte další bod [__ocs_arg_count__ bodů — Enter pro umístění textu]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Vyberte multiodkaz:
|
.mleaderadd-select-a-multileader = MLEADERADD Vyberte multiodkaz:
|
||||||
.mleaderadd-specify-arrowhead-location =MLEADERADD Zadejte umístění šipky:
|
.mleaderadd-specify-arrowhead-location =MLEADERADD Zadejte umístění šipky:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = Objekt(y) __ocs_fmt_0__ vyříznutý do schránky.
|
.object-s-cut-to-clipboard = Objekt(y) __ocs_fmt_0__ vyříznutý do schránky.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Vytvořit
|
||||||
|
.boolean = Booleovské operace
|
||||||
|
.pyramid = Jehlan
|
||||||
|
.polysolid = Polysolid
|
||||||
|
.extrude = Vysunutí
|
||||||
|
.revolve = Rotace
|
||||||
|
.loft = Přechod
|
||||||
|
.sweep = Tažení
|
||||||
|
.presspull = Tlačit/táhnout
|
||||||
.n-specify-center-point = __ocs_arg_n__ Zadejte středový bod:
|
.n-specify-center-point = __ocs_arg_n__ Zadejte středový bod:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Určete první roh:
|
.n-specify-first-corner = __ocs_arg_n__ Určete první roh:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Zadejte výšku <Enter for default>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Zadejte výšku <Enter for default>:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Přiblížení
|
Přiblížení
|
||||||
Rozsahy
|
Rozsahy
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ┼¥├ídn├® ┼írafovac├¡ objekty.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: žádný text ani kótovací objekty.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: žádný text ani kótovací objekty.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: dosud neimplementováno.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: dosud neimplementováno.
|
||||||
.viewport-s-in-layout =Výřezy __ocs_fmt_0__ v rozložení "__ocs_fmt_1__":
|
.viewport-s-in-layout =Výřezy __ocs_fmt_0__ v rozložení "__ocs_fmt_1__":
|
||||||
|
|
@ -2711,6 +2735,9 @@ properties =
|
||||||
.plot-style-table = Tabulka stylu pozemku
|
.plot-style-table = Tabulka stylu pozemku
|
||||||
.plot-table-attached-to =Plot tabulka připojena k
|
.plot-table-attached-to =Plot tabulka připojena k
|
||||||
.plot-table-type = Typ tabulky grafu
|
.plot-table-type = Typ tabulky grafu
|
||||||
|
.color-dependent-plot-styles = Barevně závislé styly vykreslování
|
||||||
|
.named-plot-styles = Pojmenované styly vykreslování
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = V barevně závislém režimu (CTB) je styl vykreslování uzamčen podle barvy
|
||||||
.point-1-x = Bod 1 X
|
.point-1-x = Bod 1 X
|
||||||
.point-1-y = Bod 1 Y
|
.point-1-y = Bod 1 Y
|
||||||
.point-1-z = Bod 1 Z
|
.point-1-z = Bod 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Draw
|
.draw = Draw
|
||||||
.annotate = Annotate
|
.annotate = Annotate
|
||||||
.insert = Insert
|
.insert = Insert
|
||||||
.model = Model
|
.model = Modelling
|
||||||
.layout = Layout
|
.layout = Layout
|
||||||
.manage = Manage
|
.manage = Manage
|
||||||
.view = View
|
.view = View
|
||||||
|
|
@ -333,6 +333,7 @@ common =
|
||||||
.brightness = Brightness
|
.brightness = Brightness
|
||||||
.by-layer = By layer
|
.by-layer = By layer
|
||||||
.byblock = ByBlock
|
.byblock = ByBlock
|
||||||
|
.bycolor = ByColor
|
||||||
.bylayer = ByLayer
|
.bylayer = ByLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: converted __ocs_fmt_0__ solid(s) to surface(s).
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: converted __ocs_fmt_0__ solid(s) to surface(s).
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: select a solid created this session.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: select a solid created this session.
|
||||||
|
|
@ -1271,6 +1272,8 @@ draw =
|
||||||
.donut-specify-center-of-donut-enter-to-exit = DONUT Specify center of donut (Enter to exit):
|
.donut-specify-center-of-donut-enter-to-exit = DONUT Specify center of donut (Enter to exit):
|
||||||
.donut-specify-inside-diameter-0 = DONUT Specify inside diameter <0>:
|
.donut-specify-inside-diameter-0 = DONUT Specify inside diameter <0>:
|
||||||
.donut-specify-outside-diameter = DONUT Specify outside diameter:
|
.donut-specify-outside-diameter = DONUT Specify outside diameter:
|
||||||
|
.donut-specify-second-point-for-inside-diameter = DONUT Specify second point for inside diameter:
|
||||||
|
.donut-specify-second-point-for-outside-diameter = DONUT Specify second point for outside diameter:
|
||||||
.draw-manually = Draw manually
|
.draw-manually = Draw manually
|
||||||
.ellipse-specify-center = ELLIPSE Specify center:
|
.ellipse-specify-center = ELLIPSE Specify center:
|
||||||
.ellipse-specify-major-axis-endpoint = ELLIPSE Specify major axis endpoint:
|
.ellipse-specify-major-axis-endpoint = ELLIPSE Specify major axis endpoint:
|
||||||
|
|
@ -1827,14 +1830,24 @@ annotate =
|
||||||
.collect-leaders = Collect Leaders
|
.collect-leaders = Collect Leaders
|
||||||
.ddedit-select-text-entity = DDEDIT Select text entity:
|
.ddedit-select-text-entity = DDEDIT Select text entity:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter dimension text (blank = measured value):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter dimension text (blank = measured value):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Specify dimension line location [Text/Angle]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Enter formatted dimension text (blank = measured value):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Specify first extension line origin:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Select object to dimension:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Specify second extension line origin [Text/Angle]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Specify dimension line location [Mtext/Text/Angle]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Specify first extension line origin or press Enter to select object:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Specify second extension line origin:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Specify text angle (degrees):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Specify text angle (degrees):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Enter dimension text (blank = measured value):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Enter formatted dimension text (blank = measured value):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Select arc, circle, line, or specify an angle vertex:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Select second line:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Specify angle vertex:
|
.dimangular-specify-angle-vertex = DIMANGULAR Specify angle vertex:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Specify dimension arc location [Mtext/Text/Angle/Quadrant]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Specify dimension arc location:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Specify dimension arc location:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Specify first extension line point:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Specify first extension line point:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Specify quadrant:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Specify second extension line point:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Specify second extension line point:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Specify second point for text angle:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Specify text angle or first point:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE No base dimension found. Place a dimension first.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE No base dimension found. Place a dimension first.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Specify a second extension line origin (Enter to exit):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Specify a second extension line origin (Enter to exit):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Select dimension to break:
|
.dimbreak-select-dimension-to-break = DIMBREAK Select dimension to break:
|
||||||
|
|
@ -1842,8 +1855,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE No base dimension found. Place a dimension first.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE No base dimension found. Place a dimension first.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Specify a second extension line origin (Enter to exit):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Specify a second extension line origin (Enter to exit):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter dimension text (blank = measured value):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter dimension text (blank = measured value):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Specify center point:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Select arc, circle, or polyline arc:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Specify dimension line location [Text/Angle]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Specify dimension line location [Mtext/Text/Angle]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Specify point on circle:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Specify point on circle:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Specify text angle (degrees):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Specify text angle (degrees):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter text override (blank = reset to measured):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter text override (blank = reset to measured):
|
||||||
|
|
@ -1851,6 +1864,12 @@ annotate =
|
||||||
.dimjogged-created-a-jogged-radius-dimension = DIMJOGGED: created a jogged radius dimension.
|
.dimjogged-created-a-jogged-radius-dimension = DIMJOGGED: created a jogged radius dimension.
|
||||||
.dimjogged-invalid-radius = DIMJOGGED: invalid radius.
|
.dimjogged-invalid-radius = DIMJOGGED: invalid radius.
|
||||||
.dimjogged-select-an-arc-or-circle-first = DIMJOGGED: select an arc or circle first.
|
.dimjogged-select-an-arc-or-circle-first = DIMJOGGED: select an arc or circle first.
|
||||||
|
.dimjogged-enter-dimension-text-blank-measured = DIMJOGGED Enter dimension text (blank = measured value):
|
||||||
|
.dimjogged-select-arc-circle-or-polyline-arc = DIMJOGGED Select arc, circle, or polyline arc:
|
||||||
|
.dimjogged-specify-center-location-override = DIMJOGGED Specify center location override:
|
||||||
|
.dimjogged-specify-dimension-line-location-text = DIMJOGGED Specify dimension line location [Mtext/Text/Angle]:
|
||||||
|
.dimjogged-specify-jog-location = DIMJOGGED Specify jog location:
|
||||||
|
.dimjogged-specify-text-angle-degrees = DIMJOGGED Specify text angle (degrees):
|
||||||
.dimjogline-select-linear-or-aligned-dimension = DIMJOGLINE Select linear or aligned dimension:
|
.dimjogline-select-linear-or-aligned-dimension = DIMJOGLINE Select linear or aligned dimension:
|
||||||
.dimjogline-specify-jog-location = DIMJOGLINE Specify jog location:
|
.dimjogline-specify-jog-location = DIMJOGLINE Specify jog location:
|
||||||
.dimlinear-enter-dimension-text-blank-measured = DIMLINEAR Enter dimension text (blank = measured value):
|
.dimlinear-enter-dimension-text-blank-measured = DIMLINEAR Enter dimension text (blank = measured value):
|
||||||
|
|
@ -1859,10 +1878,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Specify second extension line origin [Text/Angle]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Specify second extension line origin [Text/Angle]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Specify text angle (degrees):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Specify text angle (degrees):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Specify feature location:
|
.dimordinate-specify-feature-location = DIMORDINATE Specify feature location:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Specify leader endpoint:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Enter dimension text (blank = measured value):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Specify leader endpoint [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Specify text angle (degrees):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter dimension text (blank = measured value):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter dimension text (blank = measured value):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Specify center point:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Select arc, circle, or polyline arc:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Specify dimension line location [Text/Angle]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Specify dimension line location [Mtext/Text/Angle]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Specify radius point:
|
.dimradius-specify-radius-point = DIMRADIUS Specify radius point:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Specify text angle (degrees):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Specify text angle (degrees):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Enter value (0 = auto):
|
.dimspace-enter-value-0-auto = DIMSPACE Enter value (0 = auto):
|
||||||
|
|
@ -1894,6 +1915,7 @@ annotate =
|
||||||
Data
|
Data
|
||||||
.lower = Lower
|
.lower = Lower
|
||||||
.mleader-specify-arrowhead-point = MLEADER Specify arrowhead point:
|
.mleader-specify-arrowhead-point = MLEADER Specify arrowhead point:
|
||||||
|
.mleader-specify-landing-point = MLEADER Specify landing point:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Specify next point [__ocs_arg_count__ pts — Enter to place text]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Specify next point [__ocs_arg_count__ pts — Enter to place text]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Select a multileader:
|
.mleaderadd-select-a-multileader = MLEADERADD Select a multileader:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Specify arrowhead location:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Specify arrowhead location:
|
||||||
|
|
@ -1927,8 +1949,13 @@ annotate =
|
||||||
.scale-list = Scale List
|
.scale-list = Scale List
|
||||||
.sentence = Sentence
|
.sentence = Sentence
|
||||||
.sync-scales = Sync Scales
|
.sync-scales = Sync Scales
|
||||||
|
.table-cell-enter-text-row-column-current = TABLE CELL Enter text for [__ocs_arg_row__,__ocs_arg_column__] <__ocs_arg_current__>:
|
||||||
|
.table-enter-column-width-width = TABLE Enter column width [__ocs_arg_width__]:
|
||||||
.table-enter-number-of-columns-cols = TABLE Enter number of columns [__ocs_arg_cols__]:
|
.table-enter-number-of-columns-cols = TABLE Enter number of columns [__ocs_arg_cols__]:
|
||||||
|
.table-enter-number-of-data-rows-rows-cols = TABLE Enter number of data rows [__ocs_arg_rows__] (__ocs_arg_cols__ cols):
|
||||||
.table-enter-number-of-rows-incl-header-rows-co = TABLE Enter number of rows (incl. header) [__ocs_arg_rows__] (__ocs_arg_cols__ cols):
|
.table-enter-number-of-rows-incl-header-rows-co = TABLE Enter number of rows (incl. header) [__ocs_arg_rows__] (__ocs_arg_cols__ cols):
|
||||||
|
.table-enter-row-height-height = TABLE Enter row height [__ocs_arg_height__]:
|
||||||
|
.table-insertion-behavior-point-window-mode = TABLE Insertion behavior [Point/Window] <__ocs_arg_mode__>:
|
||||||
.table-specify-insertion-point-cols-rows = TABLE Specify insertion point [__ocs_arg_cols__×__ocs_arg_rows__]:
|
.table-specify-insertion-point-cols-rows = TABLE Specify insertion point [__ocs_arg_cols__×__ocs_arg_rows__]:
|
||||||
.tcase-upper-lower-sentence-title = TCASE [Upper / Lower / Sentence / Title]:
|
.tcase-upper-lower-sentence-title = TCASE [Upper / Lower / Sentence / Title]:
|
||||||
.tcase-select-text-objects-first = TCASE: select text objects first.
|
.tcase-select-text-objects-first = TCASE: select text objects first.
|
||||||
|
|
@ -2063,6 +2090,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ object(s) cut to clipboard.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ object(s) cut to clipboard.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Create
|
||||||
|
.boolean = Boolean
|
||||||
|
.pyramid = Pyramid
|
||||||
|
.polysolid = Polysolid
|
||||||
|
.extrude = Extrude
|
||||||
|
.revolve = Revolve
|
||||||
|
.loft = Loft
|
||||||
|
.sweep = Sweep
|
||||||
|
.presspull = Presspull
|
||||||
.n-specify-center-point = __ocs_arg_n__ Specify center point:
|
.n-specify-center-point = __ocs_arg_n__ Specify center point:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Specify first corner:
|
.n-specify-first-corner = __ocs_arg_n__ Specify first corner:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Specify height <Enter for default>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Specify height <Enter for default>:
|
||||||
|
|
@ -2206,6 +2242,7 @@ view =
|
||||||
.color-scheme-set-to-name = Color scheme set to '__ocs_fmt_0__'.
|
.color-scheme-set-to-name = Color scheme set to '__ocs_fmt_0__'.
|
||||||
.current-annotation-scale-name = Current annotation scale: __ocs_fmt_0__
|
.current-annotation-scale-name = Current annotation scale: __ocs_fmt_0__
|
||||||
.datalink-path-to-the-csv-file = DATALINK path to the .csv file:
|
.datalink-path-to-the-csv-file = DATALINK path to the .csv file:
|
||||||
|
.datalink-specify-insertion-point = DATALINK Specify insertion point:
|
||||||
.datalink-cannot-read-path-e = DATALINK: cannot read "__ocs_fmt_0__": __ocs_fmt_1__
|
.datalink-cannot-read-path-e = DATALINK: cannot read "__ocs_fmt_0__": __ocs_fmt_1__
|
||||||
.datalink-imported-nrows-ncols-cells-into-a-tab = DATALINK: imported __ocs_fmt_0__×__ocs_fmt_1__ cells into a table at the origin.
|
.datalink-imported-nrows-ncols-cells-into-a-tab = DATALINK: imported __ocs_fmt_0__×__ocs_fmt_1__ cells into a table at the origin.
|
||||||
.datalink-the-csv-file-is-empty = DATALINK: the CSV file is empty.
|
.datalink-the-csv-file-is-empty = DATALINK: the CSV file is empty.
|
||||||
|
|
@ -2280,6 +2317,11 @@ view =
|
||||||
.shortcut-set-key-cmd-str = Shortcut set: __ocs_fmt_0__ → __ocs_fmt_1__
|
.shortcut-set-key-cmd-str = Shortcut set: __ocs_fmt_0__ → __ocs_fmt_1__
|
||||||
.table-cell-select-a-table-entity-first-or-row = TABLE CELL: select a Table entity first, or row/col out of range.
|
.table-cell-select-a-table-entity-first-or-row = TABLE CELL: select a Table entity first, or row/col out of range.
|
||||||
.table-cell-set-row-col-text = TABLE CELL: set [__ocs_fmt_0__,__ocs_fmt_1__] = "__ocs_fmt_2__".
|
.table-cell-set-row-col-text = TABLE CELL: set [__ocs_fmt_0__,__ocs_fmt_1__] = "__ocs_fmt_2__".
|
||||||
|
.table-block-definition-not-found = TABLE: block definition not found.
|
||||||
|
.table-edit-applied = TABLE: edit applied.
|
||||||
|
.table-field-handle-not-found = TABLE: field handle not found.
|
||||||
|
.table-row-column-out-of-range-or-cell-locked = TABLE: row/column is out of range or the cell is locked.
|
||||||
|
.table-select-a-table-first = TABLE: select a table first.
|
||||||
.toolpalettes-tool-palettes-not-yet-implemented = TOOLPALETTES: Tool Palettes not yet implemented.
|
.toolpalettes-tool-palettes-not-yet-implemented = TOOLPALETTES: Tool Palettes not yet implemented.
|
||||||
.tile-horiz =
|
.tile-horiz =
|
||||||
Tile
|
Tile
|
||||||
|
|
@ -2322,6 +2364,7 @@ view =
|
||||||
.usage-shortcuts-list-set-key-cmd-clear-key = Usage: SHORTCUTS LIST | SET <key> <cmd> | CLEAR <key>
|
.usage-shortcuts-list-set-key-cmd-clear-key = Usage: SHORTCUTS LIST | SET <key> <cmd> | CLEAR <key>
|
||||||
.usage-shortcuts-set-key-command-e-g-shortcuts = Usage: SHORTCUTS SET <key> <command> e.g. SHORTCUTS SET CTRL+D DIST
|
.usage-shortcuts-set-key-command-e-g-shortcuts = Usage: SHORTCUTS SET <key> <command> e.g. SHORTCUTS SET CTRL+D DIST
|
||||||
.usage-table-cell-row-col-text = Usage: TABLE CELL <row> <col> <text>
|
.usage-table-cell-row-col-text = Usage: TABLE CELL <row> <col> <text>
|
||||||
|
.usage-table-edit-actions = Usage: TABLE CELL | INSERTROW | DELETEROW | INSERTCOL | DELETECOL | MERGE | UNMERGE | ROWHEIGHT | COLWIDTH | LOCK | BLOCK | FORMULA | FIELD
|
||||||
.usage-ucsicon-on-off-noorigin-origin = Usage: UCSICON ON | OFF | NOORIGIN | ORIGIN
|
.usage-ucsicon-on-off-noorigin-origin = Usage: UCSICON ON | OFF | NOORIGIN | ORIGIN
|
||||||
.usage-underlay-fade-n-contrast-n-on-off-clip-o = Usage: UNDERLAY [FADE <n>|CONTRAST <n>|ON|OFF|CLIP ON|OFF|MONO ON|OFF]
|
.usage-underlay-fade-n-contrast-n-on-off-clip-o = Usage: UNDERLAY [FADE <n>|CONTRAST <n>|ON|OFF|CLIP ON|OFF|MONO ON|OFF]
|
||||||
.usage-useri-1-5-int-userr-1-5-real = Usage: USERI <1-5> <int> | USERR <1-5> <real>
|
.usage-useri-1-5-int-userr-1-5-real = Usage: USERI <1-5> <int> | USERR <1-5> <real>
|
||||||
|
|
@ -2363,6 +2406,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoom
|
Zoom
|
||||||
Extents
|
Extents
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: no hatch objects.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: no text or dimension objects.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: no text or dimension objects.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: not yet implemented.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: not yet implemented.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ viewport(s) in layout "__ocs_fmt_1__":
|
.viewport-s-in-layout = __ocs_fmt_0__ viewport(s) in layout "__ocs_fmt_1__":
|
||||||
|
|
@ -2563,6 +2607,7 @@ properties =
|
||||||
.ext-line-lineweight = Ext line lineweight
|
.ext-line-lineweight = Ext line lineweight
|
||||||
.ext-line-offset = Ext line offset
|
.ext-line-offset = Ext line offset
|
||||||
.extended-entity = Extended Entity
|
.extended-entity = Extended Entity
|
||||||
|
.extended-data = Extended Data
|
||||||
.extended-lighting = Extended Lighting
|
.extended-lighting = Extended Lighting
|
||||||
.extents-max = Extents Max
|
.extents-max = Extents Max
|
||||||
.extents-min = Extents Min
|
.extents-min = Extents Min
|
||||||
|
|
@ -2628,6 +2673,7 @@ properties =
|
||||||
.island-detection-style = Island detection style
|
.island-detection-style = Island detection style
|
||||||
.italic = Italic
|
.italic = Italic
|
||||||
.jog-angle = Jog Angle
|
.jog-angle = Jog Angle
|
||||||
|
.jogged-radius = Jogged Radius
|
||||||
.jog-x = Jog X
|
.jog-x = Jog X
|
||||||
.jog-y = Jog Y
|
.jog-y = Jog Y
|
||||||
.jog-z = Jog Z
|
.jog-z = Jog Z
|
||||||
|
|
@ -2758,6 +2804,9 @@ properties =
|
||||||
.plot-style-table = Plot style table
|
.plot-style-table = Plot style table
|
||||||
.plot-table-attached-to = Plot table attached to
|
.plot-table-attached-to = Plot table attached to
|
||||||
.plot-table-type = Plot table type
|
.plot-table-type = Plot table type
|
||||||
|
.color-dependent-plot-styles = Color-dependent plot styles
|
||||||
|
.named-plot-styles = Named plot styles
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = Plot style is locked to color in Color-Dependent (CTB) mode
|
||||||
.point-1-x = Point 1 X
|
.point-1-x = Point 1 X
|
||||||
.point-1-y = Point 1 Y
|
.point-1-y = Point 1 Y
|
||||||
.point-1-z = Point 1 Z
|
.point-1-z = Point 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Dibujo
|
.draw = Dibujo
|
||||||
.annotate = Anotar
|
.annotate = Anotar
|
||||||
.insert = Insertar
|
.insert = Insertar
|
||||||
.model = Modelo
|
.model = Modelado
|
||||||
.layout = Presentación
|
.layout = Presentación
|
||||||
.manage = Administrar
|
.manage = Administrar
|
||||||
.view = Vista
|
.view = Vista
|
||||||
|
|
@ -333,6 +333,7 @@ common =
|
||||||
.brightness = Brillo
|
.brightness = Brillo
|
||||||
.by-layer = Por capa
|
.by-layer = Por capa
|
||||||
.byblock = PorBloque
|
.byblock = PorBloque
|
||||||
|
.bycolor = PorColor
|
||||||
.bylayer = PorCapa
|
.bylayer = PorCapa
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: se convirtieron __ocs_fmt_0__ sólidos en superficies.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: se convirtieron __ocs_fmt_0__ sólidos en superficies.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: seleccione un sólido creado en esta sesión.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: seleccione un sólido creado en esta sesión.
|
||||||
|
|
@ -1799,14 +1800,24 @@ annotate =
|
||||||
.collect-leaders = Recopilar directrices
|
.collect-leaders = Recopilar directrices
|
||||||
.ddedit-select-text-entity = DDEDIT Seleccione una entidad de texto:
|
.ddedit-select-text-entity = DDEDIT Seleccione una entidad de texto:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Introduzca el texto de cota (vacío = valor medido):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Introduzca el texto de cota (vacío = valor medido):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Especifique la ubicación de la línea de cota [Texto/Ángulo]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Introduzca el texto de cota con formato (vacío = valor medido):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Especifique el origen de la primera línea de referencia:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Seleccione el objeto que desea acotar:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Especifique el origen de la segunda línea de referencia [Texto/Ángulo]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Especifique la ubicación de la línea de cota [Mtext/Texto/Ángulo]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Especifique el origen de la primera línea de referencia o pulse Enter para seleccionar un objeto:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Especifique el origen de la segunda línea de referencia:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Especifique el ángulo del texto (grados):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Especifique el ángulo del texto (grados):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Introduzca el texto de cota (vacío = valor medido):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Introduzca el texto de cota con formato (vacío = valor medido):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Seleccione arco, círculo, línea o especifique un vértice de ángulo:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Seleccione la segunda línea:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Especifique el vértice del ángulo:
|
.dimangular-specify-angle-vertex = DIMANGULAR Especifique el vértice del ángulo:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Especifique la ubicación del arco de cota [Mtext/Texto/Ángulo/Cuadrante]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Especifique la ubicación del arco de cota:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Especifique la ubicación del arco de cota:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Especifique un punto de la primera línea de referencia:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Especifique un punto de la primera línea de referencia:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Especifique el cuadrante:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Especifique un punto de la segunda línea de referencia:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Especifique un punto de la segunda línea de referencia:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Especifique el segundo punto para el ángulo del texto:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Especifique el ángulo del texto o el primer punto:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE No se encontró ninguna cota base. Coloque primero una cota.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE No se encontró ninguna cota base. Coloque primero una cota.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Especifique el origen de una segunda línea de referencia (Enter para salir):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Especifique el origen de una segunda línea de referencia (Enter para salir):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Seleccione la cota que desea partir:
|
.dimbreak-select-dimension-to-break = DIMBREAK Seleccione la cota que desea partir:
|
||||||
|
|
@ -1814,8 +1825,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE No se encontró ninguna cota base. Coloque primero una cota.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE No se encontró ninguna cota base. Coloque primero una cota.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Especifique el origen de una segunda línea de referencia (Enter para salir):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Especifique el origen de una segunda línea de referencia (Enter para salir):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Introduzca el texto de cota (vacío = valor medido):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Introduzca el texto de cota (vacío = valor medido):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Especifique el centro:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Seleccione un arco, círculo o arco de polilínea:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Especifique la ubicación de la línea de cota [Texto/Ángulo]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Especifique la ubicación de la línea de cota [Mtexto/Texto/Ángulo]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Especifique un punto del círculo:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Especifique un punto del círculo:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Especifique el ángulo del texto (grados):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Especifique el ángulo del texto (grados):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Introduzca la sustitución de texto (vacío = restablecer al valor medido):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Introduzca la sustitución de texto (vacío = restablecer al valor medido):
|
||||||
|
|
@ -1831,10 +1842,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Especifique el origen de la segunda línea de referencia [Texto/Ángulo]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Especifique el origen de la segunda línea de referencia [Texto/Ángulo]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Especifique el ángulo del texto (grados):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Especifique el ángulo del texto (grados):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Especifique la ubicación del elemento:
|
.dimordinate-specify-feature-location = DIMORDINATE Especifique la ubicación del elemento:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Especifique el punto final de la directriz:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Introduzca el texto de cota (vacío = valor medido):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Especifique el punto final de la directriz [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Especifique el ángulo del texto (grados):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Introduzca el texto de cota (vacío = valor medido):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Introduzca el texto de cota (vacío = valor medido):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Especifique el centro:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Seleccione un arco, círculo o arco de polilínea:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Especifique la ubicación de la línea de cota [Texto/Ángulo]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Especifique la ubicación de la línea de cota [Mtexto/Texto/Ángulo]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Especifique el punto del radio:
|
.dimradius-specify-radius-point = DIMRADIUS Especifique el punto del radio:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Especifique el ángulo del texto (grados):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Especifique el ángulo del texto (grados):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Introduzca el valor (0 = automático):
|
.dimspace-enter-value-0-auto = DIMSPACE Introduzca el valor (0 = automático):
|
||||||
|
|
@ -1866,6 +1879,7 @@ annotate =
|
||||||
datos
|
datos
|
||||||
.lower = Inferior
|
.lower = Inferior
|
||||||
.mleader-specify-arrowhead-point = MLEADER Especifique el punto de la punta de flecha:
|
.mleader-specify-arrowhead-point = MLEADER Especifique el punto de la punta de flecha:
|
||||||
|
.mleader-specify-landing-point = MLEADER Especifique el punto de conexión:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Especifique el punto siguiente [__ocs_arg_count__ puntos; Enter para colocar texto]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Especifique el punto siguiente [__ocs_arg_count__ puntos; Enter para colocar texto]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Seleccione una directriz múltiple:
|
.mleaderadd-select-a-multileader = MLEADERADD Seleccione una directriz múltiple:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Especifique la ubicación de la punta de flecha:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Especifique la ubicación de la punta de flecha:
|
||||||
|
|
@ -2035,6 +2049,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = Se cortaron __ocs_fmt_0__ objetos al portapapeles.
|
.object-s-cut-to-clipboard = Se cortaron __ocs_fmt_0__ objetos al portapapeles.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Crear
|
||||||
|
.boolean = Booleanas
|
||||||
|
.pyramid = Pirámide
|
||||||
|
.polysolid = Polisólido
|
||||||
|
.extrude = Extruir
|
||||||
|
.revolve = Revolucionar
|
||||||
|
.loft = Transición
|
||||||
|
.sweep = Barrido
|
||||||
|
.presspull = Presionar/tirar
|
||||||
.n-specify-center-point = __ocs_arg_n__ Especifique el centro:
|
.n-specify-center-point = __ocs_arg_n__ Especifique el centro:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Especifique la primera esquina:
|
.n-specify-first-corner = __ocs_arg_n__ Especifique la primera esquina:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Especifique la altura <Enter para predeterminada>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Especifique la altura <Enter para predeterminada>:
|
||||||
|
|
@ -2335,6 +2358,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoom
|
Zoom
|
||||||
Extensión
|
Extensión
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: no hay objetos de sombreado.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: no hay objetos de texto ni de cota.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: no hay objetos de texto ni de cota.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: aún no implementado.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: aún no implementado.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ ventanas gráficas en la presentación «__ocs_fmt_1__»:
|
.viewport-s-in-layout = __ocs_fmt_0__ ventanas gráficas en la presentación «__ocs_fmt_1__»:
|
||||||
|
|
@ -2712,6 +2736,9 @@ properties =
|
||||||
.plot-style-table = Tabla de estilos de trazado
|
.plot-style-table = Tabla de estilos de trazado
|
||||||
.plot-table-attached-to = Tabla de trazado asociada a
|
.plot-table-attached-to = Tabla de trazado asociada a
|
||||||
.plot-table-type = Tipo de tabla de trazado
|
.plot-table-type = Tipo de tabla de trazado
|
||||||
|
.color-dependent-plot-styles = Estilos de trazado dependientes del color
|
||||||
|
.named-plot-styles = Estilos de trazado con nombre
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = En el modo dependiente del color (CTB), el estilo de trazado está vinculado al color
|
||||||
.point-1-x = Punto 1 X
|
.point-1-x = Punto 1 X
|
||||||
.point-1-y = Punto 1 Y
|
.point-1-y = Punto 1 Y
|
||||||
.point-1-z = Punto 1 Z
|
.point-1-z = Punto 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Piirrä
|
.draw = Piirrä
|
||||||
.annotate = Merkinnät
|
.annotate = Merkinnät
|
||||||
.insert = Lisää
|
.insert = Lisää
|
||||||
.model = Malli
|
.model = Mallinnus
|
||||||
.layout = Asettelu
|
.layout = Asettelu
|
||||||
.manage = Hallinta
|
.manage = Hallinta
|
||||||
.view = Näkymä
|
.view = Näkymä
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = Kirkkaus
|
.brightness = Kirkkaus
|
||||||
.by-layer = Kerroksen mukaan
|
.by-layer = Kerroksen mukaan
|
||||||
.byblock = ByBlock
|
.byblock = ByBlock
|
||||||
|
.bycolor = Värin mukaan
|
||||||
.bylayer = Layerin mukaan
|
.bylayer = Layerin mukaan
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: muutettu __ocs_fmt_0__-kiintoaine(t) pinnoiksi.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: muutettu __ocs_fmt_0__-kiintoaine(t) pinnoiksi.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: valitse tässä istunnossa luotu solid.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: valitse tässä istunnossa luotu solid.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = Kerää johtajia
|
.collect-leaders = Kerää johtajia
|
||||||
.ddedit-select-text-entity = DDEDIT Valitse tekstikokonaisuus:
|
.ddedit-select-text-entity = DDEDIT Valitse tekstikokonaisuus:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter mittateksti (tyhjä = mitattu arvo):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter mittateksti (tyhjä = mitattu arvo):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Määritä mittaviivan sijainti [teksti/kulma]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Syötä muotoiltu mittateksti (tyhjä = mitattu arvo):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Määritä ensimmäisen jatkolinjan alkuperä:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Valitse mitoitettava objekti:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Määritä toisen jatkolinjan alkuperä [teksti/kulma]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Määritä mittaviivan sijainti [Mtext/Teksti/Kulma]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Määritä ensimmäisen apuviivan alkupiste tai valitse objekti painamalla Enter:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Määritä toisen apuviivan alkupiste:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Määritä tekstin kulma (asteita):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Määritä tekstin kulma (asteita):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Syötä mittateksti (tyhjä = mitattu arvo):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Syötä muotoiltu mittateksti (tyhjä = mitattu arvo):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Valitse kaari, ympyrä tai viiva tai määritä kulman kärkipiste:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Valitse toinen viiva:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Määritä kulman kärkipiste:
|
.dimangular-specify-angle-vertex = DIMANGULAR Määritä kulman kärkipiste:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Määritä mittakaaren sijainti [Mtext/Teksti/Kulma/Neljännes]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Määritä mittakaaren sijainti:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Määritä mittakaaren sijainti:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Määritä ensimmäinen jatkolinjan piste:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Määritä ensimmäinen jatkolinjan piste:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Määritä neljännes:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Määritä toinen jatkolinjan piste:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Määritä toinen jatkolinjan piste:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Määritä tekstikulman toinen piste:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Määritä tekstikulma tai ensimmäinen piste:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Perusmittaa ei löytynyt. Aseta ensin mitta.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Perusmittaa ei löytynyt. Aseta ensin mitta.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Määritä toinen alalinjan lähtökohta (Enter poistuaksesi):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Määritä toinen alalinjan lähtökohta (Enter poistuaksesi):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Valitse katkaistava mitta:
|
.dimbreak-select-dimension-to-break = DIMBREAK Valitse katkaistava mitta:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Perusmittaa ei löytynyt. Aseta ensin mitta.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Perusmittaa ei löytynyt. Aseta ensin mitta.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Määritä toinen alalinjan lähtökohta (Enter poistuaksesi):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Määritä toinen alalinjan lähtökohta (Enter poistuaksesi):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter mittateksti (tyhjä = mitattu arvo):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter mittateksti (tyhjä = mitattu arvo):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Määritä keskipiste:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Valitse kaari, ympyrä tai murtoviivan kaari:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Määritä mittaviivan sijainti [teksti/kulma]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Määritä mittaviivan sijainti [Mteksti/teksti/kulma]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Määritä piste ympyrässä:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Määritä piste ympyrässä:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Määritä tekstin kulma (asteita):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Määritä tekstin kulma (asteita):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter tekstin ohitus (tyhjä = nollattu mitattuun):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter tekstin ohitus (tyhjä = nollattu mitattuun):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Määritä toisen jatkolinjan alkuperä [teksti/kulma]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Määritä toisen jatkolinjan alkuperä [teksti/kulma]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Määritä tekstin kulma (asteita):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Määritä tekstin kulma (asteita):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Määritä ominaisuuden sijainti:
|
.dimordinate-specify-feature-location = DIMORDINATE Määritä ominaisuuden sijainti:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Määritä johtajan päätepiste:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Syötä mittateksti (tyhjä = mitattu arvo):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Määritä johtajan päätepiste [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Määritä tekstin kulma (asteita):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter mittateksti (tyhjä = mitattu arvo):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter mittateksti (tyhjä = mitattu arvo):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Määritä keskipiste:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Valitse kaari, ympyrä tai murtoviivan kaari:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Määritä mittaviivan sijainti [teksti/kulma]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Määritä mittaviivan sijainti [Mteksti/teksti/kulma]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Määritä sädepiste:
|
.dimradius-specify-radius-point = DIMRADIUS Määritä sädepiste:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Määritä tekstin kulma (asteita):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Määritä tekstin kulma (asteita):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Enter-arvo (0 = automaattinen):
|
.dimspace-enter-value-0-auto = DIMSPACE Enter-arvo (0 = automaattinen):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
Data
|
Data
|
||||||
.lower = Alempi
|
.lower = Alempi
|
||||||
.mleader-specify-arrowhead-point = MLEADER Määritä nuolenpää:
|
.mleader-specify-arrowhead-point = MLEADER Määritä nuolenpää:
|
||||||
|
.mleader-specify-landing-point = MLEADER Määritä vaakaviivan piste:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Määritä seuraava piste [__ocs_arg_count__ pts — Enter tekstin sijoittamiseksi]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Määritä seuraava piste [__ocs_arg_count__ pts — Enter tekstin sijoittamiseksi]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Valitse monijohtaja:
|
.mleaderadd-select-a-multileader = MLEADERADD Valitse monijohtaja:
|
||||||
.mleaderadd-specify-arrowhead-location =MLEADERADD Määritä nuolenpään sijainti:
|
.mleaderadd-specify-arrowhead-location =MLEADERADD Määritä nuolenpään sijainti:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ objekti(t) leikattu leikepöydälle.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ objekti(t) leikattu leikepöydälle.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Luo
|
||||||
|
.boolean = Boolen operaatiot
|
||||||
|
.pyramid = Pyramidi
|
||||||
|
.polysolid = Monikappale
|
||||||
|
.extrude = Pursota
|
||||||
|
.revolve = Pyöräytä
|
||||||
|
.loft = Loftaus
|
||||||
|
.sweep = Pyyhkäisy
|
||||||
|
.presspull = Työnnä/vedä
|
||||||
.n-specify-center-point = __ocs_arg_n__ Määritä keskipiste:
|
.n-specify-center-point = __ocs_arg_n__ Määritä keskipiste:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Määritä ensimmäinen kulma:
|
.n-specify-first-corner = __ocs_arg_n__ Määritä ensimmäinen kulma:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Määritä korkeus <Enter for default>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Määritä korkeus <Enter for default>:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoomaa
|
Zoomaa
|
||||||
Laajuudet
|
Laajuudet
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ei t├ñytt├Âobjekteja.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: ei tekstiä tai mittaobjekteja.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: ei tekstiä tai mittaobjekteja.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: ei vielä otettu käyttöön.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: ei vielä otettu käyttöön.
|
||||||
.viewport-s-in-layout =__ocs_fmt_0__-näkymä(t) asettelussa "__ocs_fmt_1__":
|
.viewport-s-in-layout =__ocs_fmt_0__-näkymä(t) asettelussa "__ocs_fmt_1__":
|
||||||
|
|
@ -2711,6 +2735,9 @@ properties =
|
||||||
.plot-style-table = Juontyylinen pöytä
|
.plot-style-table = Juontyylinen pöytä
|
||||||
.plot-table-attached-to =Tonttitaulukko liitteenä
|
.plot-table-attached-to =Tonttitaulukko liitteenä
|
||||||
.plot-table-type = Piirretaulukon tyyppi
|
.plot-table-type = Piirretaulukon tyyppi
|
||||||
|
.color-dependent-plot-styles = Väristä riippuvat tulostustyylit
|
||||||
|
.named-plot-styles = Nimetyt tulostustyylit
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = Väristä riippuvassa tilassa (CTB) tulostustyyli määräytyy värin mukaan
|
||||||
.point-1-x = Kohta 1 X
|
.point-1-x = Kohta 1 X
|
||||||
.point-1-y = Kohta 1 Y
|
.point-1-y = Kohta 1 Y
|
||||||
.point-1-z = Kohta 1 Z
|
.point-1-z = Kohta 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Dessin
|
.draw = Dessin
|
||||||
.annotate = Annoter
|
.annotate = Annoter
|
||||||
.insert = Insérer
|
.insert = Insérer
|
||||||
.model = Modèle
|
.model = Modélisation
|
||||||
.layout = Mise en page
|
.layout = Mise en page
|
||||||
.manage = Gérer
|
.manage = Gérer
|
||||||
.view = Affichage
|
.view = Affichage
|
||||||
|
|
@ -333,6 +333,7 @@ common =
|
||||||
.brightness = Luminosité
|
.brightness = Luminosité
|
||||||
.by-layer = DuCalque
|
.by-layer = DuCalque
|
||||||
.byblock = Par bloc
|
.byblock = Par bloc
|
||||||
|
.bycolor = Par couleur
|
||||||
.bylayer = ParLayer
|
.bylayer = ParLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: converti en surface ou en surface __ocs_fmt_0__ solide(s).
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: converti en surface ou en surface __ocs_fmt_0__ solide(s).
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE : sélectionnez une session solide créée.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE : sélectionnez une session solide créée.
|
||||||
|
|
@ -1799,14 +1800,24 @@ annotate =
|
||||||
.collect-leaders = Regrouper les repères
|
.collect-leaders = Regrouper les repères
|
||||||
.ddedit-select-text-entity = DDEDIT Sélectionnez l’objet texte :
|
.ddedit-select-text-entity = DDEDIT Sélectionnez l’objet texte :
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Entrer le texte dimensionnel (blanc = valeur mesurée):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Entrer le texte dimensionnel (blanc = valeur mesurée):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Spécifiez la position de la ligne de cote [Texte / Angle] :
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Saisissez le texte de cote formaté (vide = valeur mesurée) :
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Spécifiez l'origine de la première ligne d'extension:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Sélectionnez l'objet à coter :
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Spécifiez l'origine de la deuxième ligne d'extension [Texte/Angle]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Spécifiez la position de la ligne de cote [Mtext/Texte/Angle] :
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Spécifiez l'origine de la première ligne d'attache ou appuyez sur Entrée pour sélectionner un objet :
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Spécifiez l'origine de la deuxième ligne d'attache :
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Spécifiez l'angle de texte (degrés):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Spécifiez l'angle de texte (degrés):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Saisissez le texte de cote (vide = valeur mesurée) :
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Saisissez le texte de cote formaté (vide = valeur mesurée) :
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Sélectionnez un arc, un cercle ou une ligne, ou spécifiez un sommet d'angle :
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Sélectionnez la deuxième ligne :
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Spécifiez le sommet d'angle:
|
.dimangular-specify-angle-vertex = DIMANGULAR Spécifiez le sommet d'angle:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Spécifiez l'emplacement de l'arc de cote [Mtext/Texte/Angle/Quadrant] :
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Spécifier l'emplacement de l'arc dimensionnel:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Spécifier l'emplacement de l'arc dimensionnel:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Spécifiez le premier point de ligne d'extension:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Spécifiez le premier point de ligne d'extension:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Spécifiez le quadrant :
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Spécifiez le deuxième point de la ligne d'extension:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Spécifiez le deuxième point de la ligne d'extension:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Spécifiez le deuxième point pour l'angle du texte :
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Spécifiez l'angle du texte ou le premier point :
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Aucune cote de base trouvée. Créez d’abord une cote.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Aucune cote de base trouvée. Créez d’abord une cote.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Spécifiez une deuxième ligne d'extension d'origine (Entrer pour sortir):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Spécifiez une deuxième ligne d'extension d'origine (Entrer pour sortir):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Sélectionnez la dimension à casser:
|
.dimbreak-select-dimension-to-break = DIMBREAK Sélectionnez la dimension à casser:
|
||||||
|
|
@ -1814,8 +1825,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Aucune cote de base trouvée. Créez d’abord une cote.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Aucune cote de base trouvée. Créez d’abord une cote.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Spécifiez une deuxième ligne d'extension d'origine (Entrer pour sortir):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Spécifiez une deuxième ligne d'extension d'origine (Entrer pour sortir):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Entrer le texte dimensionnel (blanc = valeur mesurée):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Entrer le texte dimensionnel (blanc = valeur mesurée):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Spécifier le point central:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Sélectionnez un arc, un cercle ou un arc de polyligne :
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Spécifiez la position de la ligne de cote [Texte / Angle] :
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Spécifiez la position de la ligne de cote [Mtexte/Texte/Angle] :
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Spécifier le point sur le cercle:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Spécifier le point sur le cercle:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Spécifiez l'angle de texte (degrés):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Spécifiez l'angle de texte (degrés):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Saisissez la valeur de la valeur de remplacement (blank = reset to meased):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Saisissez la valeur de la valeur de remplacement (blank = reset to meased):
|
||||||
|
|
@ -1831,10 +1842,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Spécifiez l'origine de la deuxième ligne d'extension [Texte/Angle]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Spécifiez l'origine de la deuxième ligne d'extension [Texte/Angle]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Spécifiez l'angle de texte (degrés):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Spécifiez l'angle de texte (degrés):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Spécifiez le point à coter :
|
.dimordinate-specify-feature-location = DIMORDINATE Spécifiez le point à coter :
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Spécifiez l’extrémité du repère :
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Entrez le texte de cote (vide = valeur mesurée) :
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Spécifiez l’extrémité du repère [Xdatum/Ydatum/Mtext/Text/Angle] :
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Spécifiez l'angle du texte (degrés) :
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Entrer le texte dimensionnel (blanc = valeur mesurée):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Entrer le texte dimensionnel (blanc = valeur mesurée):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Spécifier le point central:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Sélectionnez un arc, un cercle ou un arc de polyligne :
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Spécifiez la position de la ligne de cote [Texte / Angle] :
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Spécifiez la position de la ligne de cote [Mtexte/Texte/Angle] :
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Spécifier le point de rayon:
|
.dimradius-specify-radius-point = DIMRADIUS Spécifier le point de rayon:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Spécifiez l'angle de texte (degrés):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Spécifiez l'angle de texte (degrés):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Entrer la valeur (0 = automatique) :
|
.dimspace-enter-value-0-auto = DIMSPACE Entrer la valeur (0 = automatique) :
|
||||||
|
|
@ -1866,6 +1879,7 @@ annotate =
|
||||||
Données
|
Données
|
||||||
.lower = Moins
|
.lower = Moins
|
||||||
.mleader-specify-arrowhead-point = MLEADER Spécifiez le point de la tête de flèche:
|
.mleader-specify-arrowhead-point = MLEADER Spécifiez le point de la tête de flèche:
|
||||||
|
.mleader-specify-landing-point = MLEADER Spécifiez le point de palier:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Spécifiez le point suivant [__ocs_arg_count__ points — Entrée pour placer le texte] :
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Spécifiez le point suivant [__ocs_arg_count__ points — Entrée pour placer le texte] :
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Sélectionnez une ligne de repère multiple :
|
.mleaderadd-select-a-multileader = MLEADERADD Sélectionnez une ligne de repère multiple :
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Spécifiez l'emplacement de la tête de flèche:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Spécifiez l'emplacement de la tête de flèche:
|
||||||
|
|
@ -2035,6 +2049,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = Objets coupés vers le presse-papiers : __ocs_fmt_0__.
|
.object-s-cut-to-clipboard = Objets coupés vers le presse-papiers : __ocs_fmt_0__.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Créer
|
||||||
|
.boolean = Opérations booléennes
|
||||||
|
.pyramid = Pyramide
|
||||||
|
.polysolid = Polysolide
|
||||||
|
.extrude = Extruder
|
||||||
|
.revolve = Révolution
|
||||||
|
.loft = Lissage
|
||||||
|
.sweep = Balayage
|
||||||
|
.presspull = Tirer-pousser
|
||||||
.n-specify-center-point = __ocs_arg_n__ Spécifier le point central:
|
.n-specify-center-point = __ocs_arg_n__ Spécifier le point central:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Spécifiez le premier coin :
|
.n-specify-first-corner = __ocs_arg_n__ Spécifiez le premier coin :
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Spécifiez la hauteur <Entrer pour la valeur par défaut> :
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Spécifiez la hauteur <Entrer pour la valeur par défaut> :
|
||||||
|
|
@ -2335,6 +2358,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoom
|
Zoom
|
||||||
Portée
|
Portée
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: aucun objet de hachure.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: aucun objet texte ou dimensionnel.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: aucun objet texte ou dimensionnel.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: pas encore mis en œuvre.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: pas encore mis en œuvre.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ fenêtre(s) dans la présentation « __ocs_fmt_1__ » :
|
.viewport-s-in-layout = __ocs_fmt_0__ fenêtre(s) dans la présentation « __ocs_fmt_1__ » :
|
||||||
|
|
@ -2712,6 +2736,9 @@ properties =
|
||||||
.plot-style-table = Table des styles de tracé
|
.plot-style-table = Table des styles de tracé
|
||||||
.plot-table-attached-to = Tableau de correspondance joint à la
|
.plot-table-attached-to = Tableau de correspondance joint à la
|
||||||
.plot-table-type = Type de table de tracé
|
.plot-table-type = Type de table de tracé
|
||||||
|
.color-dependent-plot-styles = Styles de tracé dépendant de la couleur
|
||||||
|
.named-plot-styles = Styles de tracé nommés
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = En mode dépendant de la couleur (CTB), le style de tracé est lié à la couleur
|
||||||
.point-1-x = Point 1 X
|
.point-1-x = Point 1 X
|
||||||
.point-1-y = Point 1 Y
|
.point-1-y = Point 1 Y
|
||||||
.point-1-z = Point 1 Z
|
.point-1-z = Point 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = ड्रॉ
|
.draw = ड्रॉ
|
||||||
.annotate = एनोटेट
|
.annotate = एनोटेट
|
||||||
.insert = सम्मिलित करें
|
.insert = सम्मिलित करें
|
||||||
.model = मॉडल
|
.model = मॉडलिंग
|
||||||
.layout = लेआउट
|
.layout = लेआउट
|
||||||
.manage = प्रबंधित करें
|
.manage = प्रबंधित करें
|
||||||
.view = देखें
|
.view = देखें
|
||||||
|
|
@ -321,6 +321,7 @@ common =
|
||||||
.brightness = चमक
|
.brightness = चमक
|
||||||
.by-layer = ByLayer
|
.by-layer = ByLayer
|
||||||
.byblock = ब्लॉक द्वारा
|
.byblock = ब्लॉक द्वारा
|
||||||
|
.bycolor = रंग द्वारा
|
||||||
.bylayer = ByLayer
|
.bylayer = ByLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: सतह (s) के लिए __ocs_fmt_0__ ठोस (s) परिवर्तित।
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: सतह (s) के लिए __ocs_fmt_0__ ठोस (s) परिवर्तित।
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: इस सत्र को एक ठोस बनाया गया चयन करें।
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: इस सत्र को एक ठोस बनाया गया चयन करें।
|
||||||
|
|
@ -1787,14 +1788,24 @@ annotate =
|
||||||
.collect-leaders = लीडर एकत्र करें
|
.collect-leaders = लीडर एकत्र करें
|
||||||
.ddedit-select-text-entity = DDEDIT टेक्स्ट ऑब्जेक्ट चुनें:
|
.ddedit-select-text-entity = DDEDIT टेक्स्ट ऑब्जेक्ट चुनें:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED आयाम टेक्स्ट दर्ज करें (blank = मापा मान):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED आयाम टेक्स्ट दर्ज करें (blank = मापा मान):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED आयाम लाइन स्थान निर्दिष्ट करें [टेक्स्ट / एंगल]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED स्वरूपित आयाम टेक्स्ट दर्ज करें (खाली = मापा मान):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED पहली एक्सटेंशन लाइन उत्पत्ति निर्दिष्ट करें:
|
.dimaligned-select-object-to-dimension = DIMALIGNED आयाम देने के लिए ऑब्जेक्ट चुनें:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED दूसरी एक्सटेंशन लाइन उत्पत्ति निर्दिष्ट करें [टेक्स्ट / एंगल]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED आयाम रेखा का स्थान निर्दिष्ट करें [Mtext/टेक्स्ट/कोण]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED पहली एक्सटेंशन रेखा का मूल निर्दिष्ट करें या ऑब्जेक्ट चुनने के लिए Enter दबाएँ:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED दूसरी एक्सटेंशन रेखा का मूल निर्दिष्ट करें:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED पाठ कोण निर्दिष्ट करें (डिग्री):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED पाठ कोण निर्दिष्ट करें (डिग्री):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR आयाम टेक्स्ट दर्ज करें (खाली = मापा मान):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR स्वरूपित आयाम टेक्स्ट दर्ज करें (खाली = मापा मान):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR चाप, वृत्त या रेखा चुनें, या कोण का शीर्ष निर्दिष्ट करें:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR दूसरी रेखा चुनें:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR कोण वर्टेक्स निर्दिष्ट करें:
|
.dimangular-specify-angle-vertex = DIMANGULAR कोण वर्टेक्स निर्दिष्ट करें:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR आयाम आर्क स्थान निर्दिष्ट करें [Mtext/टेक्स्ट/कोण/चतुर्थांश]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR आयाम आर्क स्थान निर्दिष्ट करें:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR आयाम आर्क स्थान निर्दिष्ट करें:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR पहली एक्सटेंशन लाइन बिंदु निर्दिष्ट करें:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR पहली एक्सटेंशन लाइन बिंदु निर्दिष्ट करें:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR चतुर्थांश निर्दिष्ट करें:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR दूसरी एक्सटेंशन लाइन बिंदु निर्दिष्ट करें:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR दूसरी एक्सटेंशन लाइन बिंदु निर्दिष्ट करें:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR टेक्स्ट कोण के लिए दूसरा बिंदु निर्दिष्ट करें:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR टेक्स्ट कोण या पहला बिंदु निर्दिष्ट करें:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE कोई आधार आयाम नहीं मिला। पहले आयाम दें।
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE कोई आधार आयाम नहीं मिला। पहले आयाम दें।
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE एक दूसरी विस्तार लाइन मूल निर्दिष्ट करें (बाहर निकलने के लिए बेहतर):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE एक दूसरी विस्तार लाइन मूल निर्दिष्ट करें (बाहर निकलने के लिए बेहतर):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK तोड़ने के लिए आयाम का चयन करें:
|
.dimbreak-select-dimension-to-break = DIMBREAK तोड़ने के लिए आयाम का चयन करें:
|
||||||
|
|
@ -1802,8 +1813,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE कोई आधार आयाम नहीं मिला। पहले आयाम दें।
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE कोई आधार आयाम नहीं मिला। पहले आयाम दें।
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE एक दूसरी विस्तार लाइन मूल निर्दिष्ट करें (बाहर निकलने के लिए बेहतर):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE एक दूसरी विस्तार लाइन मूल निर्दिष्ट करें (बाहर निकलने के लिए बेहतर):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER आयाम टेक्स्ट दर्ज करें (blank = मापा मान):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER आयाम टेक्स्ट दर्ज करें (blank = मापा मान):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER केंद्र बिंदु निर्दिष्ट करें:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER आर्क, सर्कल या पॉलीलाइन आर्क चुनें:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER आयाम लाइन स्थान निर्दिष्ट करें [टेक्स्ट / एंगल]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER आयाम लाइन स्थान निर्दिष्ट करें [एमटेक्स्ट/टेक्स्ट/एंगल]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER सर्कल पर बिंदु निर्दिष्ट करें:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER सर्कल पर बिंदु निर्दिष्ट करें:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER पाठ कोण निर्दिष्ट करें (डिग्री):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER पाठ कोण निर्दिष्ट करें (डिग्री):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT टेक्स्ट ओवरराइड (ब्लैंक = मापा गया करने के लिए रीसेट):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT टेक्स्ट ओवरराइड (ब्लैंक = मापा गया करने के लिए रीसेट):
|
||||||
|
|
@ -1819,10 +1830,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR दूसरी एक्सटेंशन लाइन उत्पत्ति निर्दिष्ट करें [टेक्स्ट / एंगल]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR दूसरी एक्सटेंशन लाइन उत्पत्ति निर्दिष्ट करें [टेक्स्ट / एंगल]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR पाठ कोण निर्दिष्ट करें (डिग्री):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR पाठ कोण निर्दिष्ट करें (डिग्री):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE सुविधा स्थान निर्दिष्ट करें:
|
.dimordinate-specify-feature-location = DIMORDINATE सुविधा स्थान निर्दिष्ट करें:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE लीडर समापन बिंदु निर्दिष्ट करें:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE आयाम टेक्स्ट दर्ज करें (रिक्त = मापा मान):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE लीडर समापन बिंदु निर्दिष्ट करें [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE पाठ कोण निर्दिष्ट करें (डिग्री):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS आयाम टेक्स्ट दर्ज करें (blank = मापा मान):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS आयाम टेक्स्ट दर्ज करें (blank = मापा मान):
|
||||||
.dimradius-specify-center-point = DIMRADIUS केंद्र बिंदु निर्दिष्ट करें:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS आर्क, सर्कल या पॉलीलाइन आर्क चुनें:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS आयाम लाइन स्थान निर्दिष्ट करें [टेक्स्ट / एंगल]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS आयाम लाइन स्थान निर्दिष्ट करें [एमटेक्स्ट/टेक्स्ट/एंगल]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS त्रिज्या बिंदु निर्दिष्ट करें:
|
.dimradius-specify-radius-point = DIMRADIUS त्रिज्या बिंदु निर्दिष्ट करें:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS पाठ कोण निर्दिष्ट करें (डिग्री):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS पाठ कोण निर्दिष्ट करें (डिग्री):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE मान दर्ज करें (0 = ऑटो):
|
.dimspace-enter-value-0-auto = DIMSPACE मान दर्ज करें (0 = ऑटो):
|
||||||
|
|
@ -1854,6 +1867,7 @@ annotate =
|
||||||
डेटा
|
डेटा
|
||||||
.lower = कम
|
.lower = कम
|
||||||
.mleader-specify-arrowhead-point = MLEADER निर्दिष्ट तीरहेड बिंदु:
|
.mleader-specify-arrowhead-point = MLEADER निर्दिष्ट तीरहेड बिंदु:
|
||||||
|
.mleader-specify-landing-point = MLEADER लैंडिंग बिंदु निर्दिष्ट करें:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER अगला बिंदु निर्दिष्ट करें [__ocs_arg_count__ बिंदु — टेक्स्ट रखने के लिए Enter दबाएँ]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER अगला बिंदु निर्दिष्ट करें [__ocs_arg_count__ बिंदु — टेक्स्ट रखने के लिए Enter दबाएँ]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD मल्टीलीडर चुनें:
|
.mleaderadd-select-a-multileader = MLEADERADD मल्टीलीडर चुनें:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD एरोहेड का स्थान निर्दिष्ट करें:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD एरोहेड का स्थान निर्दिष्ट करें:
|
||||||
|
|
@ -2023,6 +2037,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ ऑब्जेक्ट क्लिपबोर्ड पर काटे गए।
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ ऑब्जेक्ट क्लिपबोर्ड पर काटे गए।
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = बनाएँ
|
||||||
|
.boolean = बूलियन संक्रियाएँ
|
||||||
|
.pyramid = पिरामिड
|
||||||
|
.polysolid = बहुखंड ठोस
|
||||||
|
.extrude = एक्सट्रूड
|
||||||
|
.revolve = घुमाएँ
|
||||||
|
.loft = लॉफ्ट
|
||||||
|
.sweep = स्वीप
|
||||||
|
.presspull = दबाएँ/खींचें
|
||||||
.n-specify-center-point = __ocs_arg_n__ केंद्र बिंदु निर्दिष्ट करें:
|
.n-specify-center-point = __ocs_arg_n__ केंद्र बिंदु निर्दिष्ट करें:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ पहले कोने को निर्दिष्ट करें:
|
.n-specify-first-corner = __ocs_arg_n__ पहले कोने को निर्दिष्ट करें:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ ऊंचाई निर्दिष्ट करें <Diefal> के लिए दर्ज करें>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ ऊंचाई निर्दिष्ट करें <Diefal> के लिए दर्ज करें>:
|
||||||
|
|
@ -2323,6 +2346,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
ज़ूम
|
ज़ूम
|
||||||
बहार
|
बहार
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ÓñòÓÑïÓñê Óñ╣ÓÑêÓñÜ ÓñæÓñ¼ÓÑìÓñ£ÓÑçÓñòÓÑìÓñƒ Óñ¿Óñ╣ÓÑÇÓñéÓÑñ
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: कोई टेक्स्ट या डाइमेंशन ऑब्जेक्ट नहीं।
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: कोई टेक्स्ट या डाइमेंशन ऑब्जेक्ट नहीं।
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: अभी तक लागू नहीं है।
|
.cmd-not-yet-implemented = __ocs_fmt_0__: अभी तक लागू नहीं है।
|
||||||
.viewport-s-in-layout = लेआउट "__ocs_fmt_1__" में __ocs_fmt_0__ व्यूपोर्ट:
|
.viewport-s-in-layout = लेआउट "__ocs_fmt_1__" में __ocs_fmt_0__ व्यूपोर्ट:
|
||||||
|
|
@ -2700,6 +2724,9 @@ properties =
|
||||||
.plot-style-table = प्लॉट स्टाइल टेबल
|
.plot-style-table = प्लॉट स्टाइल टेबल
|
||||||
.plot-table-attached-to = से जुड़ी प्लॉट टेबल
|
.plot-table-attached-to = से जुड़ी प्लॉट टेबल
|
||||||
.plot-table-type = प्लॉट तालिका प्रकार
|
.plot-table-type = प्लॉट तालिका प्रकार
|
||||||
|
.color-dependent-plot-styles = रंग-निर्भर प्लॉट शैलियाँ
|
||||||
|
.named-plot-styles = नामित प्लॉट शैलियाँ
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = रंग-निर्भर (CTB) मोड में प्लॉट शैली रंग से लॉक होती है
|
||||||
.point-1-x = अंक 1 X
|
.point-1-x = अंक 1 X
|
||||||
.point-1-y = अंक 1 Y
|
.point-1-y = अंक 1 Y
|
||||||
.point-1-z = प्वाइंट 1
|
.point-1-z = प्वाइंट 1
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Rajz
|
.draw = Rajz
|
||||||
.annotate = Feliratozás
|
.annotate = Feliratozás
|
||||||
.insert = Beszúrás
|
.insert = Beszúrás
|
||||||
.model = Modell
|
.model = Modellezés
|
||||||
.layout = Elrendezés
|
.layout = Elrendezés
|
||||||
.manage = Kezelés
|
.manage = Kezelés
|
||||||
.view = Nézet
|
.view = Nézet
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = Fényerő
|
.brightness = Fényerő
|
||||||
.by-layer = Réteg szerint
|
.by-layer = Réteg szerint
|
||||||
.byblock = ByBlock
|
.byblock = ByBlock
|
||||||
|
.bycolor = Szín szerint
|
||||||
.bylayer = ByLayer
|
.bylayer = ByLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ szilárdtest(ek)et felületté alakított(ok).
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ szilárdtest(ek)et felületté alakított(ok).
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: válassza ki a munkamenetben létrehozott szilárdtestet.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: válassza ki a munkamenetben létrehozott szilárdtestet.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = Gyűjtsd össze a vezetőket
|
.collect-leaders = Gyűjtsd össze a vezetőket
|
||||||
.ddedit-select-text-entity = DDEDIT Szöveg entitás kiválasztása:
|
.ddedit-select-text-entity = DDEDIT Szöveg entitás kiválasztása:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter méretszöveg (üres = mért érték):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter méretszöveg (üres = mért érték):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED A méretvonal helyének megadása [szöveg/szög]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Adja meg a formázott méretszöveget (üres = mért érték):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Adja meg az első mellékvonal eredetét:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Válassza ki a méretezendő objektumot:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Adja meg a második mellékvonal eredetét [szöveg/szög]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Adja meg a méretvonal helyét [Mtext/Szöveg/Szög]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Adja meg az első segédvonal kezdőpontját, vagy nyomja meg az Entert egy objektum kiválasztásához:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Adja meg a második segédvonal kezdőpontját:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Adja meg a szöveg szögét (fokban):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Adja meg a szöveg szögét (fokban):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Adja meg a méretszöveget (üres = mért érték):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Adja meg a formázott méretszöveget (üres = mért érték):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Válasszon ívet, kört vagy vonalat, vagy adjon meg egy szögcsúcsot:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Válassza ki a második vonalat:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Szögcsúcs megadása:
|
.dimangular-specify-angle-vertex = DIMANGULAR Szögcsúcs megadása:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Adja meg a méretív helyét [Mtext/Szöveg/Szög/Kvadráns]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR A méretív helyének megadása:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR A méretív helyének megadása:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Adja meg az első hosszabbító vonalpontot:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Adja meg az első hosszabbító vonalpontot:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Adja meg a kvadránst:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Adja meg a második kiterjesztési vonalpontot:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Adja meg a második kiterjesztési vonalpontot:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Adja meg a szövegszög második pontját:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Adja meg a szövegszöget vagy az első pontot:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nem található alapméret. Először helyezzen el egy méretet.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nem található alapméret. Először helyezzen el egy méretet.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Adjon meg egy második mellékvonal kezdőpontját (Enter a kilépéshez):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Adjon meg egy második mellékvonal kezdőpontját (Enter a kilépéshez):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Válassza ki a törni kívánt méretet:
|
.dimbreak-select-dimension-to-break = DIMBREAK Válassza ki a törni kívánt méretet:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nem található alapméret. Először helyezzen el egy méretet.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nem található alapméret. Először helyezzen el egy méretet.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Adjon meg egy második mellékvonal kezdőpontját (Enter a kilépéshez):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Adjon meg egy második mellékvonal kezdőpontját (Enter a kilépéshez):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter méretszöveg (üres = mért érték):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter méretszöveg (üres = mért érték):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Középpont megadása:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Válasszon ívet, kört vagy vonalláncívet:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER A méretvonal helyének megadása [szöveg/szög]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER A méretvonal helyének megadása [Mszöveg/szöveg/szög]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Pont megadása a körön:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Pont megadása a körön:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Adja meg a szöveg szögét (fokban):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Adja meg a szöveg szögét (fokban):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter szöveg felülírása (üres = visszaállítása mért értékre):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter szöveg felülírása (üres = visszaállítása mért értékre):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Adja meg a második mellékvonal eredetét [szöveg/szög]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Adja meg a második mellékvonal eredetét [szöveg/szög]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Adja meg a szöveg szögét (fokban):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Adja meg a szöveg szögét (fokban):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Jellemző helyének megadása:
|
.dimordinate-specify-feature-location = DIMORDINATE Jellemző helyének megadása:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Vezető végpont megadása:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Adja meg a méretszöveget (üres = mért érték):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Vezető végpont megadása [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Adja meg a szöveg szögét (fokban):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter méretszöveg (üres = mért érték):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter méretszöveg (üres = mért érték):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Középpont megadása:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Válasszon ívet, kört vagy vonalláncívet:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS A méretvonal helyének megadása [szöveg/szög]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS A méretvonal helyének megadása [Mszöveg/szöveg/szög]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Adja meg a sugárpontot:
|
.dimradius-specify-radius-point = DIMRADIUS Adja meg a sugárpontot:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Adja meg a szöveg szögét (fokban):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Adja meg a szöveg szögét (fokban):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Enter érték (0 = automatikus):
|
.dimspace-enter-value-0-auto = DIMSPACE Enter érték (0 = automatikus):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
Adatok
|
Adatok
|
||||||
.lower = Lejjebb
|
.lower = Lejjebb
|
||||||
.mleader-specify-arrowhead-point = MLEADER Nyílhegy pont megadása:
|
.mleader-specify-arrowhead-point = MLEADER Nyílhegy pont megadása:
|
||||||
|
.mleader-specify-landing-point = MLEADER Bekötési pont megadása:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Adja meg a következő pontot [__ocs_arg_count__ pts — Enter a szöveg elhelyezéséhez]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Adja meg a következő pontot [__ocs_arg_count__ pts — Enter a szöveg elhelyezéséhez]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Válasszon többvezetőt:
|
.mleaderadd-select-a-multileader = MLEADERADD Válasszon többvezetőt:
|
||||||
.mleaderadd-specify-arrowhead-location =MLEADERADD Adja meg a nyílhegy helyét:
|
.mleaderadd-specify-arrowhead-location =MLEADERADD Adja meg a nyílhegy helyét:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ objektum(ok) vágólapra vágva.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ objektum(ok) vágólapra vágva.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Létrehozás
|
||||||
|
.boolean = Logikai műveletek
|
||||||
|
.pyramid = Gúla
|
||||||
|
.polysolid = Poliszilárdtest
|
||||||
|
.extrude = Kihúzás
|
||||||
|
.revolve = Forgatás
|
||||||
|
.loft = Átmenet
|
||||||
|
.sweep = Seprés
|
||||||
|
.presspull = Nyomás/húzás
|
||||||
.n-specify-center-point = __ocs_arg_n__ Középpont megadása:
|
.n-specify-center-point = __ocs_arg_n__ Középpont megadása:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Adja meg az első sarkot:
|
.n-specify-first-corner = __ocs_arg_n__ Adja meg az első sarkot:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Adja meg a <Enter for default> magasságot:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Adja meg a <Enter for default> magasságot:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoom
|
Zoom
|
||||||
Terjedelem
|
Terjedelem
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: nincs sraffozási objektum.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: nincs szöveg vagy méretobjektum.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: nincs szöveg vagy méretobjektum.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: még nincs implementálva.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: még nincs implementálva.
|
||||||
.viewport-s-in-layout =__ocs_fmt_0__ nézetablak(ok) a „__ocs_fmt_1__” elrendezésben:
|
.viewport-s-in-layout =__ocs_fmt_0__ nézetablak(ok) a „__ocs_fmt_1__” elrendezésben:
|
||||||
|
|
@ -2711,6 +2735,9 @@ properties =
|
||||||
.plot-style-table = Telek stílusú táblázat
|
.plot-style-table = Telek stílusú táblázat
|
||||||
.plot-table-attached-to =Telektábla csatolva
|
.plot-table-attached-to =Telektábla csatolva
|
||||||
.plot-table-type = Plot tábla típusa
|
.plot-table-type = Plot tábla típusa
|
||||||
|
.color-dependent-plot-styles = Színfüggő nyomtatási stílusok
|
||||||
|
.named-plot-styles = Elnevezett nyomtatási stílusok
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = Színfüggő (CTB) módban a nyomtatási stílust a szín határozza meg
|
||||||
.point-1-x = 1. pont X
|
.point-1-x = 1. pont X
|
||||||
.point-1-y = 1. pont Y
|
.point-1-y = 1. pont Y
|
||||||
.point-1-z = 1. pont Z
|
.point-1-z = 1. pont Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Disegno
|
.draw = Disegno
|
||||||
.annotate = Annotazione
|
.annotate = Annotazione
|
||||||
.insert = Inserisci
|
.insert = Inserisci
|
||||||
.model = Modello
|
.model = Modellazione
|
||||||
.layout = Disposizione
|
.layout = Disposizione
|
||||||
.manage = Gestisci
|
.manage = Gestisci
|
||||||
.view = Vista
|
.view = Vista
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = Luminosità
|
.brightness = Luminosità
|
||||||
.by-layer = Per strato
|
.by-layer = Per strato
|
||||||
.byblock = PerBlocco
|
.byblock = PerBlocco
|
||||||
|
.bycolor = PerColore
|
||||||
.bylayer = PerLayer
|
.bylayer = PerLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: convertito(i) solido(i) __ocs_fmt_0__ in superficie(i).
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: convertito(i) solido(i) __ocs_fmt_0__ in superficie(i).
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: seleziona un solido creato in questa sessione.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: seleziona un solido creato in questa sessione.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = Colleziona leader
|
.collect-leaders = Colleziona leader
|
||||||
.ddedit-select-text-entity = DDEDIT Seleziona entità testo:
|
.ddedit-select-text-entity = DDEDIT Seleziona entità testo:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = Testo della dimensione DIMALIGNED Enter (valore misurato = vuoto):
|
.dimaligned-enter-dimension-text-blank-measured = Testo della dimensione DIMALIGNED Enter (valore misurato = vuoto):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Specificare la posizione della linea di quota [Testo/Angolo]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Immettere il testo di quota formattato (vuoto = valore misurato):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Specificare l'origine della prima linea di estensione:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Selezionare l'oggetto da quotare:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Specificare l'origine della seconda linea di estensione [Testo/Angolo]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Specificare la posizione della linea di quota [Mtext/Testo/Angolo]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Specificare l'origine della prima linea di estensione o premere Invio per selezionare un oggetto:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Specificare l'origine della seconda linea di estensione:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Specificare l'angolo del testo (gradi):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Specificare l'angolo del testo (gradi):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Immettere il testo di quota (vuoto = valore misurato):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Immettere il testo di quota formattato (vuoto = valore misurato):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Selezionare un arco, un cerchio o una linea oppure specificare un vertice dell'angolo:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Selezionare la seconda linea:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Specificare il vertice dell'angolo:
|
.dimangular-specify-angle-vertex = DIMANGULAR Specificare il vertice dell'angolo:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Specificare la posizione dell'arco di quota [Mtext/Testo/Angolo/Quadrante]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Specificare la posizione dell'arco di quota:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Specificare la posizione dell'arco di quota:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Specificare il primo punto della linea di estensione:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Specificare il primo punto della linea di estensione:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Specificare il quadrante:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Specificare il secondo punto della linea di estensione:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Specificare il secondo punto della linea di estensione:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Specificare il secondo punto per l'angolo del testo:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Specificare l'angolo del testo o il primo punto:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nessuna dimensione base trovata. Posiziona prima una dimensione.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nessuna dimensione base trovata. Posiziona prima una dimensione.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Specificare una seconda origine della linea di estensione (Enter per uscire):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Specificare una seconda origine della linea di estensione (Enter per uscire):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Selezionare la dimensione da interrompere:
|
.dimbreak-select-dimension-to-break = DIMBREAK Selezionare la dimensione da interrompere:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nessuna dimensione base trovata. Posiziona prima una dimensione.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nessuna dimensione base trovata. Posiziona prima una dimensione.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Specificare una seconda origine della linea di estensione (Enter per uscire):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Specificare una seconda origine della linea di estensione (Enter per uscire):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = Testo della dimensione DIMDIAMETER Enter (valore misurato = vuoto):
|
.dimdiameter-enter-dimension-text-blank-measure = Testo della dimensione DIMDIAMETER Enter (valore misurato = vuoto):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Specificare il punto centrale:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Selezionare un arco, un cerchio o un arco di polilinea:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Specificare la posizione della linea di quota [Testo/Angolo]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Specificare la posizione della linea di quota [Mtesto/Testo/Angolo]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Specificare il punto sul cerchio:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Specificare il punto sul cerchio:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Specificare l'angolo del testo (gradi):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Specificare l'angolo del testo (gradi):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter sovrascrittura testo (= vuoto ripristinato su misurato):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter sovrascrittura testo (= vuoto ripristinato su misurato):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Specificare l'origine della seconda linea di estensione [Testo/Angolo]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Specificare l'origine della seconda linea di estensione [Testo/Angolo]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Specificare l'angolo del testo (gradi):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Specificare l'angolo del testo (gradi):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Specificare la posizione dell'elemento:
|
.dimordinate-specify-feature-location = DIMORDINATE Specificare la posizione dell'elemento:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Specificare il punto finale della guida:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Immettere il testo della quota (vuoto = valore misurato):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Specificare il punto finale della guida [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Specificare l'angolo del testo (gradi):
|
||||||
.dimradius-enter-dimension-text-blank-measured = Testo della dimensione DIMRADIUS Enter (valore misurato = vuoto):
|
.dimradius-enter-dimension-text-blank-measured = Testo della dimensione DIMRADIUS Enter (valore misurato = vuoto):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Specificare il punto centrale:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Selezionare un arco, un cerchio o un arco di polilinea:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Specificare la posizione della linea di quota [Testo/Angolo]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Specificare la posizione della linea di quota [Mtesto/Testo/Angolo]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Specificare il punto del raggio:
|
.dimradius-specify-radius-point = DIMRADIUS Specificare il punto del raggio:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Specificare l'angolo del testo (gradi):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Specificare l'angolo del testo (gradi):
|
||||||
.dimspace-enter-value-0-auto = Valore DIMSPACE Enter (0 = automatico):
|
.dimspace-enter-value-0-auto = Valore DIMSPACE Enter (0 = automatico):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
Dati
|
Dati
|
||||||
.lower = Più in basso
|
.lower = Più in basso
|
||||||
.mleader-specify-arrowhead-point = MLEADER Specificare il punto della freccia:
|
.mleader-specify-arrowhead-point = MLEADER Specificare il punto della freccia:
|
||||||
|
.mleader-specify-landing-point = MLEADER Specificare il punto di collegamento:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Specificare il punto successivo [__ocs_arg_count__ punti — Enter per posizionare il testo]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Specificare il punto successivo [__ocs_arg_count__ punti — Enter per posizionare il testo]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Seleziona una multidirettrice:
|
.mleaderadd-select-a-multileader = MLEADERADD Seleziona una multidirettrice:
|
||||||
.mleaderadd-specify-arrowhead-location =MLEADERADD Specificare la posizione della punta della freccia:
|
.mleaderadd-specify-arrowhead-location =MLEADERADD Specificare la posizione della punta della freccia:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ oggetto/i tagliato negli appunti.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ oggetto/i tagliato negli appunti.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Crea
|
||||||
|
.boolean = Operazioni booleane
|
||||||
|
.pyramid = Piramide
|
||||||
|
.polysolid = Polisolido
|
||||||
|
.extrude = Estrudi
|
||||||
|
.revolve = Rivoluzione
|
||||||
|
.loft = Loft
|
||||||
|
.sweep = Scorrimento
|
||||||
|
.presspull = Premi/tira
|
||||||
.n-specify-center-point = __ocs_arg_n__ Specificare il punto centrale:
|
.n-specify-center-point = __ocs_arg_n__ Specificare il punto centrale:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Specificare il primo angolo:
|
.n-specify-first-corner = __ocs_arg_n__ Specificare il primo angolo:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Specificare l'altezza <Enter for default>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Specificare l'altezza <Enter for default>:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoom
|
Zoom
|
||||||
Estensioni
|
Estensioni
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: nessun oggetto tratteggio.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: nessun testo o oggetto dimensione.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: nessun testo o oggetto dimensione.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: non ancora implementato.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: non ancora implementato.
|
||||||
.viewport-s-in-layout =Finestre __ocs_fmt_0__ nel layout "__ocs_fmt_1__":
|
.viewport-s-in-layout =Finestre __ocs_fmt_0__ nel layout "__ocs_fmt_1__":
|
||||||
|
|
@ -2711,6 +2735,9 @@ properties =
|
||||||
.plot-style-table = Tabella stili di stampa
|
.plot-style-table = Tabella stili di stampa
|
||||||
.plot-table-attached-to =Tabella di stampa allegata a
|
.plot-table-attached-to =Tabella di stampa allegata a
|
||||||
.plot-table-type = Tipo di tabella di stampa
|
.plot-table-type = Tipo di tabella di stampa
|
||||||
|
.color-dependent-plot-styles = Stili di stampa dipendenti dal colore
|
||||||
|
.named-plot-styles = Stili di stampa con nome
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = In modalità dipendente dal colore (CTB), lo stile di stampa è vincolato al colore
|
||||||
.point-1-x = Punto 1 X
|
.point-1-x = Punto 1 X
|
||||||
.point-1-y = Punto 1 Y
|
.point-1-y = Punto 1 Y
|
||||||
.point-1-z = Punto 1 Z
|
.point-1-z = Punto 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = 作図
|
.draw = 作図
|
||||||
.annotate = 注釈
|
.annotate = 注釈
|
||||||
.insert = 挿入
|
.insert = 挿入
|
||||||
.model = モデル
|
.model = モデリング
|
||||||
.layout = レイアウト
|
.layout = レイアウト
|
||||||
.manage = 管理
|
.manage = 管理
|
||||||
.view = 表示
|
.view = 表示
|
||||||
|
|
@ -323,6 +323,7 @@ common =
|
||||||
.brightness = 明るさ
|
.brightness = 明るさ
|
||||||
.by-layer = レイヤー別
|
.by-layer = レイヤー別
|
||||||
.byblock = ブロック依存
|
.byblock = ブロック依存
|
||||||
|
.bycolor = 色依存
|
||||||
.bylayer = レイヤー依存
|
.bylayer = レイヤー依存
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ 個のソリッドをサーフェスに変換しました。
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ 個のソリッドをサーフェスに変換しました。
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: このセッションで作成したソリッドを選択してください。
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: このセッションで作成したソリッドを選択してください。
|
||||||
|
|
@ -1789,14 +1790,24 @@ annotate =
|
||||||
.collect-leaders = 引出線を収集
|
.collect-leaders = 引出線を収集
|
||||||
.ddedit-select-text-entity = DDEDIT 文字図形を選択:
|
.ddedit-select-text-entity = DDEDIT 文字図形を選択:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED 寸法値を入力(空白 = 計測値):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED 寸法値を入力(空白 = 計測値):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 寸法線の位置を指定 [文字/角度]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED 書式付き寸法文字を入力(空白 = 計測値):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 1 本目の寸法補助線の起点を指定:
|
.dimaligned-select-object-to-dimension = DIMALIGNED 寸法を記入するオブジェクトを選択:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 2 本目の寸法補助線の起点を指定 [文字/角度]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 寸法線の位置を指定 [Mtext/文字/角度]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 1 本目の寸法補助線の起点を指定、または Enter でオブジェクトを選択:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 2 本目の寸法補助線の起点を指定:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED 文字角度を指定(度):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED 文字角度を指定(度):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR 寸法値を入力(空白 = 計測値):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR 書式付き寸法文字を入力(空白 = 計測値):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR 円弧、円、線分を選択、または角度の頂点を指定:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR 2 本目の線分を選択:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR 角度の頂点を指定:
|
.dimangular-specify-angle-vertex = DIMANGULAR 角度の頂点を指定:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR 寸法円弧の位置を指定 [Mtext/文字/角度/象限]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR 寸法円弧の位置を指定:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR 寸法円弧の位置を指定:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR 1 本目の寸法補助線の点を指定:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR 1 本目の寸法補助線の点を指定:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR 象限を指定:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR 2 本目の寸法補助線の点を指定:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR 2 本目の寸法補助線の点を指定:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR 文字角度の 2 点目を指定:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR 文字角度または 1 点目を指定:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 基準寸法がありません。先に寸法を配置してください。
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 基準寸法がありません。先に寸法を配置してください。
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 2 本目の寸法補助線の起点を指定(Enter で終了):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 2 本目の寸法補助線の起点を指定(Enter で終了):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK 分割する寸法を選択:
|
.dimbreak-select-dimension-to-break = DIMBREAK 分割する寸法を選択:
|
||||||
|
|
@ -1804,8 +1815,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 基準寸法がありません。先に寸法を配置してください。
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 基準寸法がありません。先に寸法を配置してください。
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 2 本目の寸法補助線の起点を指定(Enter で終了):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 2 本目の寸法補助線の起点を指定(Enter で終了):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER 寸法値を入力(空白 = 計測値):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER 寸法値を入力(空白 = 計測値):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER 中心点を指定:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER 円弧、円、またはポリライン円弧を選択:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER 寸法線の位置を指定 [文字/角度]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER 寸法線の位置を指定 [マルチテキスト/文字/角度]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER 円上の点を指定:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER 円上の点を指定:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 文字角度を指定(度):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 文字角度を指定(度):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT 寸法値の上書きを入力(空白 = 計測値に戻す):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT 寸法値の上書きを入力(空白 = 計測値に戻す):
|
||||||
|
|
@ -1821,10 +1832,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 2 本目の寸法補助線の起点を指定 [文字/角度]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 2 本目の寸法補助線の起点を指定 [文字/角度]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR 文字角度を指定(度):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR 文字角度を指定(度):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE フィーチャ位置を指定:
|
.dimordinate-specify-feature-location = DIMORDINATE フィーチャ位置を指定:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE 引出線の終点を指定:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE 寸法値を入力(空白 = 計測値):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE 引出線の終点を指定 [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE 文字角度を指定(度):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS 寸法値を入力(空白 = 計測値):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS 寸法値を入力(空白 = 計測値):
|
||||||
.dimradius-specify-center-point = DIMRADIUS 中心点を指定:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS 円弧、円、またはポリライン円弧を選択:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS 寸法線の位置を指定 [文字/角度]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS 寸法線の位置を指定 [マルチテキスト/文字/角度]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS 半径点を指定:
|
.dimradius-specify-radius-point = DIMRADIUS 半径点を指定:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS 文字角度を指定(度):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS 文字角度を指定(度):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE 値を入力(0 = 自動):
|
.dimspace-enter-value-0-auto = DIMSPACE 値を入力(0 = 自動):
|
||||||
|
|
@ -1856,6 +1869,7 @@ annotate =
|
||||||
リンク
|
リンク
|
||||||
.lower = 下げる
|
.lower = 下げる
|
||||||
.mleader-specify-arrowhead-point = MLEADER 矢印位置を指定:
|
.mleader-specify-arrowhead-point = MLEADER 矢印位置を指定:
|
||||||
|
.mleader-specify-landing-point = MLEADER ランディング点を指定:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER 次の点を指定 [__ocs_arg_count__ 点 — Enter で文字を配置]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER 次の点を指定 [__ocs_arg_count__ 点 — Enter で文字を配置]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD マルチ引出線を選択:
|
.mleaderadd-select-a-multileader = MLEADERADD マルチ引出線を選択:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD 矢印位置を指定:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD 矢印位置を指定:
|
||||||
|
|
@ -2023,6 +2037,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ 個のオブジェクトを切り取ってクリップボードに保存しました。
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ 個のオブジェクトを切り取ってクリップボードに保存しました。
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = 作成
|
||||||
|
.boolean = ブール演算
|
||||||
|
.pyramid = ピラミッド
|
||||||
|
.polysolid = ポリソリッド
|
||||||
|
.extrude = 押し出し
|
||||||
|
.revolve = 回転
|
||||||
|
.loft = ロフト
|
||||||
|
.sweep = スイープ
|
||||||
|
.presspull = プレスプル
|
||||||
.n-specify-center-point = __ocs_arg_n__ 中心点を指定:
|
.n-specify-center-point = __ocs_arg_n__ 中心点を指定:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ 最初のコーナーを指定:
|
.n-specify-first-corner = __ocs_arg_n__ 最初のコーナーを指定:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ 高さを指定 <Enter で既定値>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ 高さを指定 <Enter で既定値>:
|
||||||
|
|
@ -2323,6 +2346,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
全体表示
|
全体表示
|
||||||
ズーム
|
ズーム
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ÒâÅÒââÒâüÒâ│Òé░ Òé¬ÒâûÒé©ÒéºÒé»ÒâêÒüîÒüéÒéèÒü¥ÒüøÒéôÒÇé
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: テキストまたは寸法オブジェクトがありません。
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: テキストまたは寸法オブジェクトがありません。
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: まだ実装されていません。
|
.cmd-not-yet-implemented = __ocs_fmt_0__: まだ実装されていません。
|
||||||
.viewport-s-in-layout = レイアウト「__ocs_fmt_1__」内の __ocs_fmt_0__ 個のビューポート:
|
.viewport-s-in-layout = レイアウト「__ocs_fmt_1__」内の __ocs_fmt_0__ 個のビューポート:
|
||||||
|
|
@ -2700,6 +2724,9 @@ properties =
|
||||||
.plot-style-table = 印刷スタイルテーブル
|
.plot-style-table = 印刷スタイルテーブル
|
||||||
.plot-table-attached-to = 印刷テーブルのアタッチ先
|
.plot-table-attached-to = 印刷テーブルのアタッチ先
|
||||||
.plot-table-type = 印刷テーブルの種類
|
.plot-table-type = 印刷テーブルの種類
|
||||||
|
.color-dependent-plot-styles = 色従属印刷スタイル
|
||||||
|
.named-plot-styles = 名前付き印刷スタイル
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = 色従属(CTB)モードでは、印刷スタイルは色に固定されます
|
||||||
.point-1-x = 点 1 X
|
.point-1-x = 点 1 X
|
||||||
.point-1-y = 点 1 Y
|
.point-1-y = 点 1 Y
|
||||||
.point-1-z = 点 1 Z
|
.point-1-z = 点 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = 그리기
|
.draw = 그리기
|
||||||
.annotate = 주석
|
.annotate = 주석
|
||||||
.insert = 삽입
|
.insert = 삽입
|
||||||
.model = 모델
|
.model = 모델링
|
||||||
.layout = 배치
|
.layout = 배치
|
||||||
.manage = 관리
|
.manage = 관리
|
||||||
.view = 보기
|
.view = 보기
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = 밝기
|
.brightness = 밝기
|
||||||
.by-layer = 레이어별
|
.by-layer = 레이어별
|
||||||
.byblock = 바이블록
|
.byblock = 바이블록
|
||||||
|
.bycolor = 색상별
|
||||||
.bylayer = 레이어별
|
.bylayer = 레이어별
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ 솔리드를 표면으로 변환했습니다.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ 솔리드를 표면으로 변환했습니다.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: 이 세션에서 생성된 솔리드를 선택합니다.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: 이 세션에서 생성된 솔리드를 선택합니다.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = 리더 수집
|
.collect-leaders = 리더 수집
|
||||||
.ddedit-select-text-entity = DDEDIT 텍스트 엔터티 선택:
|
.ddedit-select-text-entity = DDEDIT 텍스트 엔터티 선택:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter 치수 문자(빈 = 측정 값):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter 치수 문자(빈 = 측정 값):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 치수선 위치 지정 [문자/각도]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED 서식 있는 치수 문자를 입력하십시오(비워 두면 측정값):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 첫 번째 연장선 원점 지정:
|
.dimaligned-select-object-to-dimension = DIMALIGNED 치수를 기입할 객체 선택:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 두 번째 연장선 원점 지정 [문자/각도]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 치수선 위치 지정 [Mtext/문자/각도]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 첫 번째 치수 보조선 원점을 지정하거나 Enter를 눌러 객체 선택:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 두 번째 치수 보조선 원점 지정:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED 텍스트 각도(도) 지정:
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED 텍스트 각도(도) 지정:
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR 치수 문자를 입력하십시오(비워 두면 측정값):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR 서식 있는 치수 문자를 입력하십시오(비워 두면 측정값):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR 호, 원 또는 선을 선택하거나 각도 꼭지점을 지정하십시오:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR 두 번째 선을 선택하십시오:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR 각도 꼭지점 지정:
|
.dimangular-specify-angle-vertex = DIMANGULAR 각도 꼭지점 지정:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR 치수 호 위치 지정 [Mtext/문자/각도/사분면]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR 치수 호 위치를 지정합니다.
|
.dimangular-specify-dimension-arc-location = DIMANGULAR 치수 호 위치를 지정합니다.
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR 첫 번째 연장선 점 지정:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR 첫 번째 연장선 점 지정:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR 사분면을 지정하십시오:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR 두 번째 연장선 점을 지정합니다.
|
.dimangular-specify-second-extension-line-point = DIMANGULAR 두 번째 연장선 점을 지정합니다.
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR 문자 각도의 두 번째 점을 지정하십시오:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR 문자 각도 또는 첫 번째 점을 지정하십시오:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 기본 치수를 찾을 수 없습니다. 먼저 치수를 배치합니다.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 기본 치수를 찾을 수 없습니다. 먼저 치수를 배치합니다.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 두 번째 연장선 원점을 지정합니다(종료하려면 Enter).
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 두 번째 연장선 원점을 지정합니다(종료하려면 Enter).
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK 끊을 치수 선택:
|
.dimbreak-select-dimension-to-break = DIMBREAK 끊을 치수 선택:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 기본 치수를 찾을 수 없습니다. 먼저 치수를 배치합니다.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 기본 치수를 찾을 수 없습니다. 먼저 치수를 배치합니다.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 두 번째 연장선 원점을 지정합니다(종료하려면 Enter).
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 두 번째 연장선 원점을 지정합니다(종료하려면 Enter).
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter 치수 문자(빈 = 측정 값):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter 치수 문자(빈 = 측정 값):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER 중심점 지정:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER 호, 원 또는 폴리선 호 선택:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER 치수선 위치 지정 [문자/각도]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER 치수선 위치 지정 [여러 줄 문자/문자/각도]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER 원의 점 지정:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER 원의 점 지정:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 텍스트 각도(도) 지정:
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 텍스트 각도(도) 지정:
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter 텍스트 재정의(빈 =가 측정으로 재설정됨):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter 텍스트 재정의(빈 =가 측정으로 재설정됨):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 두 번째 연장선 원점 지정 [문자/각도]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 두 번째 연장선 원점 지정 [문자/각도]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR 텍스트 각도(도) 지정:
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR 텍스트 각도(도) 지정:
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE 기능 위치 지정:
|
.dimordinate-specify-feature-location = DIMORDINATE 기능 위치 지정:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE 리더 끝점 지정:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE 치수 문자 입력(빈 값 = 측정 값):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE 리더 끝점 지정 [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE 텍스트 각도(도) 지정:
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter 치수 문자(빈 = 측정 값):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter 치수 문자(빈 = 측정 값):
|
||||||
.dimradius-specify-center-point = DIMRADIUS 중심점 지정:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS 호, 원 또는 폴리선 호 선택:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS 치수선 위치 지정 [문자/각도]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS 치수선 위치 지정 [여러 줄 문자/문자/각도]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS 반경 점 지정:
|
.dimradius-specify-radius-point = DIMRADIUS 반경 점 지정:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS 텍스트 각도(도) 지정:
|
.dimradius-specify-text-angle-degrees = DIMRADIUS 텍스트 각도(도) 지정:
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Enter 값(0 = 자동):
|
.dimspace-enter-value-0-auto = DIMSPACE Enter 값(0 = 자동):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
데이터
|
데이터
|
||||||
.lower = 낮은
|
.lower = 낮은
|
||||||
.mleader-specify-arrowhead-point = MLEADER 화살촉 점 지정:
|
.mleader-specify-arrowhead-point = MLEADER 화살촉 점 지정:
|
||||||
|
.mleader-specify-landing-point = MLEADER 연결점 지정:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER 다음 점 지정 [__ocs_arg_count__ 포인트 - 텍스트를 배치할 Enter]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER 다음 점 지정 [__ocs_arg_count__ 포인트 - 텍스트를 배치할 Enter]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD 다중 지시선 선택:
|
.mleaderadd-select-a-multileader = MLEADERADD 다중 지시선 선택:
|
||||||
.mleaderadd-specify-arrowhead-location =MLEADERADD 화살촉 위치 지정:
|
.mleaderadd-specify-arrowhead-location =MLEADERADD 화살촉 위치 지정:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ 개체를 클립보드로 잘라냈습니다.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ 개체를 클립보드로 잘라냈습니다.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = 생성
|
||||||
|
.boolean = 불리언 연산
|
||||||
|
.pyramid = 피라미드
|
||||||
|
.polysolid = 폴리솔리드
|
||||||
|
.extrude = 돌출
|
||||||
|
.revolve = 회전
|
||||||
|
.loft = 로프트
|
||||||
|
.sweep = 스윕
|
||||||
|
.presspull = 누르기/당기기
|
||||||
.n-specify-center-point = __ocs_arg_n__ 중심점 지정:
|
.n-specify-center-point = __ocs_arg_n__ 중심점 지정:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ 첫 번째 구석 지정:
|
.n-specify-first-corner = __ocs_arg_n__ 첫 번째 구석 지정:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ 높이 지정 <Enter for default>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ 높이 지정 <Enter for default>:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
줌
|
줌
|
||||||
범위
|
범위
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: Ýò┤ý╣ÿ Û░Øý▓┤Û░Ç ýùåýèÁÙïêÙïñ.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: 문자 또는 치수 객체가 없습니다.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: 문자 또는 치수 객체가 없습니다.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: 아직 구현되지 않았습니다.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: 아직 구현되지 않았습니다.
|
||||||
.viewport-s-in-layout ="__ocs_fmt_1__" 레이아웃의 __ocs_fmt_0__ 뷰포트:
|
.viewport-s-in-layout ="__ocs_fmt_1__" 레이아웃의 __ocs_fmt_0__ 뷰포트:
|
||||||
|
|
@ -2711,6 +2735,9 @@ properties =
|
||||||
.plot-style-table = 플롯 스타일 테이블
|
.plot-style-table = 플롯 스타일 테이블
|
||||||
.plot-table-attached-to =플롯 테이블이 첨부됨
|
.plot-table-attached-to =플롯 테이블이 첨부됨
|
||||||
.plot-table-type = 플롯 테이블 유형
|
.plot-table-type = 플롯 테이블 유형
|
||||||
|
.color-dependent-plot-styles = 색상 종속 플롯 스타일
|
||||||
|
.named-plot-styles = 명명된 플롯 스타일
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = 색상 종속(CTB) 모드에서는 플롯 스타일이 색상에 고정됩니다
|
||||||
.point-1-x = 포인트 1 X
|
.point-1-x = 포인트 1 X
|
||||||
.point-1-y = 포인트 1 Y
|
.point-1-y = 포인트 1 Y
|
||||||
.point-1-z = 포인트 1 Z
|
.point-1-z = 포인트 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Tekenen
|
.draw = Tekenen
|
||||||
.annotate = Annoteren
|
.annotate = Annoteren
|
||||||
.insert = Invoegen
|
.insert = Invoegen
|
||||||
.model = Model
|
.model = Modelleren
|
||||||
.layout = Indeling
|
.layout = Indeling
|
||||||
.manage = Beheren
|
.manage = Beheren
|
||||||
.view = Beeld
|
.view = Beeld
|
||||||
|
|
@ -333,6 +333,7 @@ common =
|
||||||
.brightness = Helderheid
|
.brightness = Helderheid
|
||||||
.by-layer = Op laag
|
.by-layer = Op laag
|
||||||
.byblock = Per blok
|
.byblock = Per blok
|
||||||
|
.bycolor = Per kleur
|
||||||
.bylayer = DoorLayer
|
.bylayer = DoorLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: omgezet __ocs_fmt_0__ vaste stof(s) naar oppervlakte(s).
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: omgezet __ocs_fmt_0__ vaste stof(s) naar oppervlakte(s).
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: selecteer een vaste aangemaakte sessie.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: selecteer een vaste aangemaakte sessie.
|
||||||
|
|
@ -1799,14 +1800,24 @@ annotate =
|
||||||
.collect-leaders = Verwijslijnen verzamelen
|
.collect-leaders = Verwijslijnen verzamelen
|
||||||
.ddedit-select-text-entity = DDEDIT Selecteer een tekstobject:
|
.ddedit-select-text-entity = DDEDIT Selecteer een tekstobject:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Voer maattekst in (leeg = gemeten waarde):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Voer maattekst in (leeg = gemeten waarde):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Geef de positie van de maatlijn op [Tekst/Hoek]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Voer opgemaakte maattekst in (leeg = gemeten waarde):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Vermeld de oorsprong van de eerste extensieregel:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Selecteer het object om te bematen:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Vermeld de oorsprong van de tweede extensieregel [Tekst/hoek]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Geef de positie van de maatlijn op [Mtext/Tekst/Hoek]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Geef het beginpunt van de eerste hulplijn op of druk op Enter om een object te selecteren:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Geef het beginpunt van de tweede hulplijn op:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Teksthoek (graden) specificeren:
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Teksthoek (graden) specificeren:
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Voer maattekst in (leeg = gemeten waarde):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Voer opgemaakte maattekst in (leeg = gemeten waarde):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Selecteer een boog, cirkel of lijn, of geef een hoekpunt op:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Selecteer de tweede lijn:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Specificeer hoekvertex:
|
.dimangular-specify-angle-vertex = DIMANGULAR Specificeer hoekvertex:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Geef de positie van de maatboog op [Mtext/Tekst/Hoek/Kwadrant]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Geef de positie van de maatboog op:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Geef de positie van de maatboog op:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Geef eerste extensieregelpunt aan:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Geef eerste extensieregelpunt aan:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Geef het kwadrant op:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Geef tweede extensieregelpunt:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Geef tweede extensieregelpunt:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Geef het tweede punt voor de teksthoek op:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Geef de teksthoek of het eerste punt op:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Geen basismaat gevonden. Plaats eerst een maat.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Geen basismaat gevonden. Plaats eerst een maat.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Vermeld een tweede extensielijn (tot afsluiten overgaan):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Vermeld een tweede extensielijn (tot afsluiten overgaan):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Selecteer de te onderbreken maat:
|
.dimbreak-select-dimension-to-break = DIMBREAK Selecteer de te onderbreken maat:
|
||||||
|
|
@ -1814,8 +1825,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Geen basismaat gevonden. Plaats eerst een maat.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Geen basismaat gevonden. Plaats eerst een maat.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Vermeld een tweede extensielijn (tot afsluiten overgaan):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Vermeld een tweede extensielijn (tot afsluiten overgaan):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Voer maattekst in (leeg = gemeten waarde):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Voer maattekst in (leeg = gemeten waarde):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Centrumpunt opgeven:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Selecteer boog, cirkel of polylijnboog:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Geef de positie van de maatlijn op [Tekst/Hoek]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Geef de positie van de maatlijn op [M-tekst/Tekst/Hoek]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Punt op cirkel specificeren:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Punt op cirkel specificeren:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Teksthoek (graden) specificeren:
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Teksthoek (graden) specificeren:
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Tekstoverride invoeren (blanc = reset op gemeten):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Tekstoverride invoeren (blanc = reset op gemeten):
|
||||||
|
|
@ -1831,10 +1842,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Vermeld de oorsprong van de tweede extensieregel [Tekst/hoek]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Vermeld de oorsprong van de tweede extensieregel [Tekst/hoek]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Teksthoek (graden) specificeren:
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Teksthoek (graden) specificeren:
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Geef functielocatie op:
|
.dimordinate-specify-feature-location = DIMORDINATE Geef functielocatie op:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Geef het eindpunt van de verwijslijn op:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Voer maattekst in (leeg = gemeten waarde):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Geef het eindpunt van de verwijslijn op [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Teksthoek (graden) specificeren:
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Voer maattekst in (leeg = gemeten waarde):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Voer maattekst in (leeg = gemeten waarde):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Centrumpunt opgeven:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Selecteer boog, cirkel of polylijnboog:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Geef de positie van de maatlijn op [Tekst/Hoek]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Geef de positie van de maatlijn op [M-tekst/Tekst/Hoek]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS radiuspunt specificeren:
|
.dimradius-specify-radius-point = DIMRADIUS radiuspunt specificeren:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Teksthoek (graden) specificeren:
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Teksthoek (graden) specificeren:
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Waarde invoeren (0 = auto):
|
.dimspace-enter-value-0-auto = DIMSPACE Waarde invoeren (0 = auto):
|
||||||
|
|
@ -1866,6 +1879,7 @@ annotate =
|
||||||
Gegevens
|
Gegevens
|
||||||
.lower = Lager
|
.lower = Lager
|
||||||
.mleader-specify-arrowhead-point = MLEADER pijlpunt specificeren:
|
.mleader-specify-arrowhead-point = MLEADER pijlpunt specificeren:
|
||||||
|
.mleader-specify-landing-point = MLEADER Landingspunt opgeven:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Geef het volgende punt op [__ocs_arg_count__ punten — Enter om tekst te plaatsen]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Geef het volgende punt op [__ocs_arg_count__ punten — Enter om tekst te plaatsen]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Selecteer een multileader:
|
.mleaderadd-select-a-multileader = MLEADERADD Selecteer een multileader:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Plaats pijlpunt opgeven:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Plaats pijlpunt opgeven:
|
||||||
|
|
@ -2035,6 +2049,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ object(en) naar het klembord geknipt.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ object(en) naar het klembord geknipt.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Maken
|
||||||
|
.boolean = Booleaanse bewerkingen
|
||||||
|
.pyramid = Piramide
|
||||||
|
.polysolid = Polysolid
|
||||||
|
.extrude = Extruderen
|
||||||
|
.revolve = Omwentelen
|
||||||
|
.loft = Loft
|
||||||
|
.sweep = Vegen
|
||||||
|
.presspull = Duwen/trekken
|
||||||
.n-specify-center-point = __ocs_arg_n__ Centrumpunt opgeven:
|
.n-specify-center-point = __ocs_arg_n__ Centrumpunt opgeven:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Geef eerste hoek:
|
.n-specify-first-corner = __ocs_arg_n__ Geef eerste hoek:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Hoogte <Enter voor standaard> opgeven:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Hoogte <Enter voor standaard> opgeven:
|
||||||
|
|
@ -2335,6 +2358,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoomen
|
Zoomen
|
||||||
Maten
|
Maten
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: geen arceerobjecten.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: geen tekst- of maatobjecten.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: geen tekst- of maatobjecten.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: nog niet geïmplementeerd.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: nog niet geïmplementeerd.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ weergavevenster(s) in indeling "__ocs_fmt_1__":
|
.viewport-s-in-layout = __ocs_fmt_0__ weergavevenster(s) in indeling "__ocs_fmt_1__":
|
||||||
|
|
@ -2712,6 +2736,9 @@ properties =
|
||||||
.plot-style-table = Plotstijltabel
|
.plot-style-table = Plotstijltabel
|
||||||
.plot-table-attached-to = Plottabel bijvoegen
|
.plot-table-attached-to = Plottabel bijvoegen
|
||||||
.plot-table-type = Type plotstijltabel
|
.plot-table-type = Type plotstijltabel
|
||||||
|
.color-dependent-plot-styles = Kleurafhankelijke plotstijlen
|
||||||
|
.named-plot-styles = Benoemde plotstijlen
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = In kleurafhankelijke modus (CTB) is de plotstijl aan de kleur gekoppeld
|
||||||
.point-1-x = Punt 1 X
|
.point-1-x = Punt 1 X
|
||||||
.point-1-y = Punt 1 Y
|
.point-1-y = Punt 1 Y
|
||||||
.point-1-z = Punt 1 Z
|
.point-1-z = Punt 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Rysowanie
|
.draw = Rysowanie
|
||||||
.annotate = Opisy
|
.annotate = Opisy
|
||||||
.insert = Wstaw
|
.insert = Wstaw
|
||||||
.model = Model
|
.model = Modelowanie
|
||||||
.layout = Układ
|
.layout = Układ
|
||||||
.manage = Zarządzaj
|
.manage = Zarządzaj
|
||||||
.view = Widok
|
.view = Widok
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = Jasność
|
.brightness = Jasność
|
||||||
.by-layer = Według warstwy
|
.by-layer = Według warstwy
|
||||||
.byblock = ByBlock
|
.byblock = ByBlock
|
||||||
|
.bycolor = Według koloru
|
||||||
.bylayer = Według warstwy
|
.bylayer = Według warstwy
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: przekształcono bryłę __ocs_fmt_0__ w powierzchnię.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: przekształcono bryłę __ocs_fmt_0__ w powierzchnię.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: wybierz bryłę utworzoną w tej sesji.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: wybierz bryłę utworzoną w tej sesji.
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = Zbieraj Liderów
|
.collect-leaders = Zbieraj Liderów
|
||||||
.ddedit-select-text-entity = DDEDIT Wybierz element tekstowy:
|
.ddedit-select-text-entity = DDEDIT Wybierz element tekstowy:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter tekst wymiarowy (pusta wartość zmierzona =):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Enter tekst wymiarowy (pusta wartość zmierzona =):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Określ położenie linii wymiarowej [Tekst/Kąt]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Wprowadź sformatowany tekst wymiaru (puste = zmierzona wartość):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Określ początek pierwszej linii pomocniczej:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Wybierz obiekt do zwymiarowania:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Określ początek drugiej linii pomocniczej [Tekst/Kąt]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Określ położenie linii wymiarowej [Mtext/Tekst/Kąt]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Określ początek pierwszej linii pomocniczej lub naciśnij Enter, aby wybrać obiekt:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Określ początek drugiej linii pomocniczej:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Określ kąt tekstu (w stopniach):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Określ kąt tekstu (w stopniach):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Wprowadź tekst wymiaru (puste = zmierzona wartość):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Wprowadź sformatowany tekst wymiaru (puste = zmierzona wartość):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Wybierz łuk, okrąg lub linię albo określ wierzchołek kąta:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Wybierz drugą linię:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Określ wierzchołek kąta:
|
.dimangular-specify-angle-vertex = DIMANGULAR Określ wierzchołek kąta:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Określ położenie łuku wymiarowego [Mtext/Tekst/Kąt/Ćwiartka]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Określ położenie łuku wymiarowego:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Określ położenie łuku wymiarowego:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Określ pierwszy punkt linii pomocniczej:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Określ pierwszy punkt linii pomocniczej:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Określ ćwiartkę:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Określ drugi punkt linii pomocniczej:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Określ drugi punkt linii pomocniczej:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Określ drugi punkt kąta tekstu:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Określ kąt tekstu lub pierwszy punkt:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nie znaleziono wymiaru podstawowego. Najpierw umieść wymiar.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nie znaleziono wymiaru podstawowego. Najpierw umieść wymiar.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Określ początek drugiej linii pomocniczej (Enter, aby wyjść):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Określ początek drugiej linii pomocniczej (Enter, aby wyjść):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Wybierz wymiar do przerwania:
|
.dimbreak-select-dimension-to-break = DIMBREAK Wybierz wymiar do przerwania:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nie znaleziono wymiaru podstawowego. Najpierw umieść wymiar.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nie znaleziono wymiaru podstawowego. Najpierw umieść wymiar.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Określ początek drugiej linii pomocniczej (Enter, aby wyjść):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Określ początek drugiej linii pomocniczej (Enter, aby wyjść):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter tekst wymiarowy (pusta wartość zmierzona =):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Enter tekst wymiarowy (pusta wartość zmierzona =):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Określ punkt środkowy:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Wybierz łuk, okrąg lub łuk polilinii:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Określ położenie linii wymiarowej [Tekst/Kąt]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Określ położenie linii wymiarowej [Mtekst/Tekst/Kąt]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Określ punkt na okręgu:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Określ punkt na okręgu:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Określ kąt tekstu (w stopniach):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Określ kąt tekstu (w stopniach):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter zastąpienie tekstu (puste = resetowane do zmierzonego):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Enter zastąpienie tekstu (puste = resetowane do zmierzonego):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Określ początek drugiej linii pomocniczej [Tekst/Kąt]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Określ początek drugiej linii pomocniczej [Tekst/Kąt]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Określ kąt tekstu (w stopniach):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Określ kąt tekstu (w stopniach):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Określ lokalizację elementu:
|
.dimordinate-specify-feature-location = DIMORDINATE Określ lokalizację elementu:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Określ punkt końcowy linii odniesienia:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Wprowadź tekst wymiarowy (puste = wartość zmierzona):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Określ punkt końcowy linii odniesienia [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Określ kąt tekstu (w stopniach):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter tekst wymiarowy (pusta wartość zmierzona =):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Enter tekst wymiarowy (pusta wartość zmierzona =):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Określ punkt środkowy:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Wybierz łuk, okrąg lub łuk polilinii:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Określ położenie linii wymiarowej [Tekst/Kąt]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Określ położenie linii wymiarowej [Mtekst/Tekst/Kąt]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Określ punkt promienia:
|
.dimradius-specify-radius-point = DIMRADIUS Określ punkt promienia:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Określ kąt tekstu (w stopniach):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Określ kąt tekstu (w stopniach):
|
||||||
.dimspace-enter-value-0-auto = Wartość DIMSPACE Enter (0 = auto):
|
.dimspace-enter-value-0-auto = Wartość DIMSPACE Enter (0 = auto):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
Dane
|
Dane
|
||||||
.lower = Niższy
|
.lower = Niższy
|
||||||
.mleader-specify-arrowhead-point = MLEADER Określ punkt grotu strzałki:
|
.mleader-specify-arrowhead-point = MLEADER Określ punkt grotu strzałki:
|
||||||
|
.mleader-specify-landing-point = MLEADER Określ punkt odsadzenia:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Określ następny punkt [__ocs_arg_count__ pkt — Enter, aby umieścić tekst]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Określ następny punkt [__ocs_arg_count__ pkt — Enter, aby umieścić tekst]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Wybierz wielolinię odniesienia:
|
.mleaderadd-select-a-multileader = MLEADERADD Wybierz wielolinię odniesienia:
|
||||||
.mleaderadd-specify-arrowhead-location =MLEADERADD Określ położenie grotu strzałki:
|
.mleaderadd-specify-arrowhead-location =MLEADERADD Określ położenie grotu strzałki:
|
||||||
|
|
@ -2034,6 +2048,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = Obiekt(y) __ocs_fmt_0__ wycięty do schowka.
|
.object-s-cut-to-clipboard = Obiekt(y) __ocs_fmt_0__ wycięty do schowka.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Utwórz
|
||||||
|
.boolean = Operacje boolowskie
|
||||||
|
.pyramid = Ostrosłup
|
||||||
|
.polysolid = Bryła wielosegmentowa
|
||||||
|
.extrude = Wyciągnij
|
||||||
|
.revolve = Obróć
|
||||||
|
.loft = Loft
|
||||||
|
.sweep = Przeciągnij
|
||||||
|
.presspull = Naciśnij/wyciągnij
|
||||||
.n-specify-center-point = __ocs_arg_n__ Określ punkt środkowy:
|
.n-specify-center-point = __ocs_arg_n__ Określ punkt środkowy:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Określ pierwszy narożnik:
|
.n-specify-first-corner = __ocs_arg_n__ Określ pierwszy narożnik:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Podaj wysokość <Enter for default>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Podaj wysokość <Enter for default>:
|
||||||
|
|
@ -2334,6 +2357,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Powiększ
|
Powiększ
|
||||||
Zakresy
|
Zakresy
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: brak obiekt├│w kreskowania.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: brak obiektów tekstowych i wymiarowych.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: brak obiektów tekstowych i wymiarowych.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: jeszcze nie wdrożony.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: jeszcze nie wdrożony.
|
||||||
.viewport-s-in-layout =Okna widokowe __ocs_fmt_0__ w układzie „__ocs_fmt_1__”:
|
.viewport-s-in-layout =Okna widokowe __ocs_fmt_0__ w układzie „__ocs_fmt_1__”:
|
||||||
|
|
@ -2711,6 +2735,9 @@ properties =
|
||||||
.plot-style-table = Tabela stylów wydruku
|
.plot-style-table = Tabela stylów wydruku
|
||||||
.plot-table-attached-to =Tabela wykresów dołączona do
|
.plot-table-attached-to =Tabela wykresów dołączona do
|
||||||
.plot-table-type = Typ tabeli wykresów
|
.plot-table-type = Typ tabeli wykresów
|
||||||
|
.color-dependent-plot-styles = Style wydruku zależne od koloru
|
||||||
|
.named-plot-styles = Nazwane style wydruku
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = W trybie zależnym od koloru (CTB) styl wydruku jest przypisany do koloru
|
||||||
.point-1-x = Punkt 1 X
|
.point-1-x = Punkt 1 X
|
||||||
.point-1-y = Punkt 1 Y
|
.point-1-y = Punkt 1 Y
|
||||||
.point-1-z = Punkt 1 Z
|
.point-1-z = Punkt 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Desenhar
|
.draw = Desenhar
|
||||||
.annotate = Anotar
|
.annotate = Anotar
|
||||||
.insert = Inserir
|
.insert = Inserir
|
||||||
.model = Modelo
|
.model = Modelagem
|
||||||
.layout = Layout
|
.layout = Layout
|
||||||
.manage = Gerenciar
|
.manage = Gerenciar
|
||||||
.view = Exibir
|
.view = Exibir
|
||||||
|
|
@ -333,6 +333,7 @@ common =
|
||||||
.brightness = Brilho
|
.brightness = Brilho
|
||||||
.by-layer = Por camada
|
.by-layer = Por camada
|
||||||
.byblock = PorBloco
|
.byblock = PorBloco
|
||||||
|
.bycolor = PorCor
|
||||||
.bylayer = PorCamada
|
.bylayer = PorCamada
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ sólidos convertidos em superfícies.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ sólidos convertidos em superfícies.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: selecione um sólido criado nesta sessão.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: selecione um sólido criado nesta sessão.
|
||||||
|
|
@ -1799,14 +1800,24 @@ annotate =
|
||||||
.collect-leaders = Coletar linhas de chamada
|
.collect-leaders = Coletar linhas de chamada
|
||||||
.ddedit-select-text-entity = DDEDIT Selecione a entidade de texto:
|
.ddedit-select-text-entity = DDEDIT Selecione a entidade de texto:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Insira o texto da cota (vazio = valor medido):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Insira o texto da cota (vazio = valor medido):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Especifique a localização da linha de cota [Texto/Ângulo]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Insira o texto formatado da cota (vazio = valor medido):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Especifique a origem da primeira linha de extensão:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Selecione o objeto a ser cotado:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Especifique a origem da segunda linha de extensão [Texto/Ângulo]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Especifique a localização da linha de cota [Mtext/Texto/Ângulo]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Especifique a origem da primeira linha de extensão ou pressione Enter para selecionar um objeto:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Especifique a origem da segunda linha de extensão:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Especifique o ângulo do texto (graus):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Especifique o ângulo do texto (graus):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Insira o texto da cota (vazio = valor medido):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Insira o texto formatado da cota (vazio = valor medido):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Selecione arco, círculo, linha ou especifique um vértice de ângulo:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR Selecione a segunda linha:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Especifique o vértice do ângulo:
|
.dimangular-specify-angle-vertex = DIMANGULAR Especifique o vértice do ângulo:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Especifique a localização do arco de cota [Mtext/Texto/Ângulo/Quadrante]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Especifique a localização do arco de cota:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Especifique a localização do arco de cota:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Especifique o ponto da primeira linha de extensão:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Especifique o ponto da primeira linha de extensão:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Especifique o quadrante:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Especifique o ponto da segunda linha de extensão:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Especifique o ponto da segunda linha de extensão:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Especifique o segundo ponto para o ângulo do texto:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Especifique o ângulo do texto ou o primeiro ponto:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nenhuma cota base encontrada. Coloque uma cota primeiro.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Nenhuma cota base encontrada. Coloque uma cota primeiro.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Especifique a origem de uma segunda linha de extensão (Enter para sair):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Especifique a origem de uma segunda linha de extensão (Enter para sair):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Selecione a cota a quebrar:
|
.dimbreak-select-dimension-to-break = DIMBREAK Selecione a cota a quebrar:
|
||||||
|
|
@ -1814,8 +1825,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nenhuma cota base encontrada. Coloque uma cota primeiro.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Nenhuma cota base encontrada. Coloque uma cota primeiro.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Especifique a origem de uma segunda linha de extensão (Enter para sair):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Especifique a origem de uma segunda linha de extensão (Enter para sair):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Insira o texto da cota (vazio = valor medido):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Insira o texto da cota (vazio = valor medido):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Especifique o ponto central:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Selecione um arco, círculo ou arco de polilinha:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Especifique a localização da linha de cota [Texto/Ângulo]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Especifique a localização da linha de cota [Mtexto/Texto/Ângulo]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Especifique um ponto no círculo:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Especifique um ponto no círculo:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Especifique o ângulo do texto (graus):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Especifique o ângulo do texto (graus):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Insira a substituição do texto (vazio = restaurar ao medido):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Insira a substituição do texto (vazio = restaurar ao medido):
|
||||||
|
|
@ -1831,10 +1842,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Especifique a origem da segunda linha de extensão [Texto/Ângulo]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Especifique a origem da segunda linha de extensão [Texto/Ângulo]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Especifique o ângulo do texto (graus):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Especifique o ângulo do texto (graus):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Especifique a localização da característica:
|
.dimordinate-specify-feature-location = DIMORDINATE Especifique a localização da característica:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Especifique a extremidade da linha de chamada:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Insira o texto da cota (vazio = valor medido):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Especifique a extremidade da linha de chamada [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Especifique o ângulo do texto (graus):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Insira o texto da cota (vazio = valor medido):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Insira o texto da cota (vazio = valor medido):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Especifique o ponto central:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Selecione um arco, círculo ou arco de polilinha:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Especifique a localização da linha de cota [Texto/Ângulo]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Especifique a localização da linha de cota [Mtexto/Texto/Ângulo]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Especifique o ponto do raio:
|
.dimradius-specify-radius-point = DIMRADIUS Especifique o ponto do raio:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Especifique o ângulo do texto (graus):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Especifique o ângulo do texto (graus):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Insira o valor (0 = automático):
|
.dimspace-enter-value-0-auto = DIMSPACE Insira o valor (0 = automático):
|
||||||
|
|
@ -1866,6 +1879,7 @@ annotate =
|
||||||
dados
|
dados
|
||||||
.lower = Inferior
|
.lower = Inferior
|
||||||
.mleader-specify-arrowhead-point = MLEADER Especifique o ponto da ponta de seta:
|
.mleader-specify-arrowhead-point = MLEADER Especifique o ponto da ponta de seta:
|
||||||
|
.mleader-specify-landing-point = MLEADER Especifique o ponto de conexão:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Especifique o próximo ponto [__ocs_arg_count__ pontos — Enter para inserir texto]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Especifique o próximo ponto [__ocs_arg_count__ pontos — Enter para inserir texto]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Selecione uma linha de chamada múltipla:
|
.mleaderadd-select-a-multileader = MLEADERADD Selecione uma linha de chamada múltipla:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Especifique a localização da ponta de seta:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Especifique a localização da ponta de seta:
|
||||||
|
|
@ -2035,6 +2049,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ objetos recortados para a área de transferência.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ objetos recortados para a área de transferência.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Criar
|
||||||
|
.boolean = Operações booleanas
|
||||||
|
.pyramid = Pirâmide
|
||||||
|
.polysolid = Polissólido
|
||||||
|
.extrude = Extrusão
|
||||||
|
.revolve = Revolução
|
||||||
|
.loft = Loft
|
||||||
|
.sweep = Varredura
|
||||||
|
.presspull = Pressionar/puxar
|
||||||
.n-specify-center-point = __ocs_arg_n__ Especifique o ponto central:
|
.n-specify-center-point = __ocs_arg_n__ Especifique o ponto central:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Especifique o primeiro canto:
|
.n-specify-first-corner = __ocs_arg_n__ Especifique o primeiro canto:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Especifique a altura <Enter para o padrão>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Especifique a altura <Enter para o padrão>:
|
||||||
|
|
@ -2335,6 +2358,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Zoom nas
|
Zoom nas
|
||||||
extensões
|
extensões
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: nenhum objeto de hachura.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: nenhum objeto de texto ou cota.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: nenhum objeto de texto ou cota.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: ainda não implementado.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: ainda não implementado.
|
||||||
.viewport-s-in-layout = __ocs_fmt_0__ janelas de visualização no layout “__ocs_fmt_1__”:
|
.viewport-s-in-layout = __ocs_fmt_0__ janelas de visualização no layout “__ocs_fmt_1__”:
|
||||||
|
|
@ -2712,6 +2736,9 @@ properties =
|
||||||
.plot-style-table = Tabela de estilos de plotagem
|
.plot-style-table = Tabela de estilos de plotagem
|
||||||
.plot-table-attached-to = Tabela de plotagem anexada a
|
.plot-table-attached-to = Tabela de plotagem anexada a
|
||||||
.plot-table-type = Tipo de tabela de plotagem
|
.plot-table-type = Tipo de tabela de plotagem
|
||||||
|
.color-dependent-plot-styles = Estilos de plotagem dependentes de cor
|
||||||
|
.named-plot-styles = Estilos de plotagem nomeados
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = No modo dependente de cor (CTB), o estilo de plotagem fica vinculado à cor
|
||||||
.point-1-x = Ponto 1 X
|
.point-1-x = Ponto 1 X
|
||||||
.point-1-y = Ponto 1 Y
|
.point-1-y = Ponto 1 Y
|
||||||
.point-1-z = Ponto 1 Z
|
.point-1-z = Ponto 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Начертить
|
.draw = Начертить
|
||||||
.annotate = Аннотации
|
.annotate = Аннотации
|
||||||
.insert = Вставка
|
.insert = Вставка
|
||||||
.model = Модель
|
.model = Моделирование
|
||||||
.layout = Расположение
|
.layout = Расположение
|
||||||
.manage = Управление
|
.manage = Управление
|
||||||
.view = Просмотр
|
.view = Просмотр
|
||||||
|
|
@ -336,6 +336,7 @@ common =
|
||||||
.brightness = Яркость
|
.brightness = Яркость
|
||||||
.by-layer = слой
|
.by-layer = слой
|
||||||
.byblock = Блокировка
|
.byblock = Блокировка
|
||||||
|
.bycolor = По цвету
|
||||||
.bylayer = ByLayer
|
.bylayer = ByLayer
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: преобразовано __ocs_fmt_0__ твердое(ые) в поверхность(ы).
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: преобразовано __ocs_fmt_0__ твердое(ые) в поверхность(ы).
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: выберите твердое решение, созданное в ходе этой сессии.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: выберите твердое решение, созданное в ходе этой сессии.
|
||||||
|
|
@ -1802,14 +1803,24 @@ annotate =
|
||||||
.collect-leaders = Собрать выноски
|
.collect-leaders = Собрать выноски
|
||||||
.ddedit-select-text-entity = DDEDIT Выберите текстовый объект:
|
.ddedit-select-text-entity = DDEDIT Выберите текстовый объект:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Введите текст размера (пусто = измеренное значение):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Введите текст размера (пусто = измеренное значение):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Укажите положение размерной линии [Текст/Угол]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Введите форматированный текст размера (пусто = измеренное значение):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Укажите происхождение первой линии расширения:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Выберите объект для нанесения размера:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Укажите происхождение второй линии расширения [Текст/Угле]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Укажите положение размерной линии [Mtext/Текст/Угол]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Укажите начало первой выносной линии или нажмите Enter для выбора объекта:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED Укажите начало второй выносной линии:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Укажите угол текста (градусы):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Укажите угол текста (градусы):
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Укажите угол вершины:
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Введите текст размера (пусто = измеренное значение):
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Укажите местоположение дуги размерности:
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Введите форматированный текст размера (пусто = измеренное значение):
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Укажите первую точку расширения:
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Выберите дугу, окружность, линию или укажите вершину угла:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR Укажите вторую точку расширения:
|
.dimangular-select-second-line = DIMANGULAR Выберите вторую линию:
|
||||||
|
.dimangular-specify-angle-vertex = DIMANGULAR Укажите вершину угла:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Укажите положение размерной дуги [Mtext/Текст/Угол/Квадрант]:
|
||||||
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Укажите положение размерной дуги:
|
||||||
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Укажите первую точку выносной линии:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Укажите квадрант:
|
||||||
|
.dimangular-specify-second-extension-line-point = DIMANGULAR Укажите вторую точку выносной линии:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Укажите вторую точку угла текста:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Укажите угол текста или первую точку:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Базовый размер не найден. Сначала создайте размер.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Базовый размер не найден. Сначала создайте размер.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Укажите происхождение второй линии расширения (Введите для выхода):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE Укажите происхождение второй линии расширения (Введите для выхода):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Выберите размер для разбиения:
|
.dimbreak-select-dimension-to-break = DIMBREAK Выберите размер для разбиения:
|
||||||
|
|
@ -1817,8 +1828,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Базовый размер не найден. Сначала создайте размер.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Базовый размер не найден. Сначала создайте размер.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Укажите происхождение второй линии расширения (Введите для выхода):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE Укажите происхождение второй линии расширения (Введите для выхода):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Введите текст размера (пусто = измеренное значение):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Введите текст размера (пусто = измеренное значение):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Укажите центральную точку:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Выберите дугу, окружность или дуговой сегмент полилинии:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Укажите положение размерной линии [Текст/Угол]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Укажите положение размерной линии [Мтекст/Текст/Угол]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Укажите точку на окружности:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Укажите точку на окружности:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Укажите угол текста (градусы):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Укажите угол текста (градусы):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Введите текстовую оверрайд (blank = reset to measured):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Введите текстовую оверрайд (blank = reset to measured):
|
||||||
|
|
@ -1834,10 +1845,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Укажите происхождение второй линии расширения [Текст/Угле]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR Укажите происхождение второй линии расширения [Текст/Угле]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Укажите угол текста (градусы):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Укажите угол текста (градусы):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Укажите местоположение:
|
.dimordinate-specify-feature-location = DIMORDINATE Укажите местоположение:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Укажите конечную точку выноски:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Введите текст размера (пусто = измеренное значение):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Укажите конечную точку выноски [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Укажите угол текста (градусы):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Введите текст размера (пусто = измеренное значение):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Введите текст размера (пусто = измеренное значение):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Укажите центральную точку:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Выберите дугу, окружность или дуговой сегмент полилинии:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Укажите положение размерной линии [Текст/Угол]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Укажите положение размерной линии [Мтекст/Текст/Угол]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Укажите точку радиуса:
|
.dimradius-specify-radius-point = DIMRADIUS Укажите точку радиуса:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Укажите угол текста (градусы):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Укажите угол текста (градусы):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Введите значение (0 = авто):
|
.dimspace-enter-value-0-auto = DIMSPACE Введите значение (0 = авто):
|
||||||
|
|
@ -1869,6 +1882,7 @@ annotate =
|
||||||
Данные
|
Данные
|
||||||
.lower = ниже
|
.lower = ниже
|
||||||
.mleader-specify-arrowhead-point = MLEADER Укажите точку наконечника стрелки:
|
.mleader-specify-arrowhead-point = MLEADER Укажите точку наконечника стрелки:
|
||||||
|
.mleader-specify-landing-point = MLEADER Укажите точку полки:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Укажите следующую точку [точек: __ocs_arg_count__ — Enter для размещения текста]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Укажите следующую точку [точек: __ocs_arg_count__ — Enter для размещения текста]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Выберите мультивыноску:
|
.mleaderadd-select-a-multileader = MLEADERADD Выберите мультивыноску:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Укажите местоположение наконечника стрелки:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Укажите местоположение наконечника стрелки:
|
||||||
|
|
@ -2038,6 +2052,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = В буфер обмена вырезано объектов: __ocs_fmt_0__.
|
.object-s-cut-to-clipboard = В буфер обмена вырезано объектов: __ocs_fmt_0__.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Создание
|
||||||
|
.boolean = Булевы операции
|
||||||
|
.pyramid = Пирамида
|
||||||
|
.polysolid = Политело
|
||||||
|
.extrude = Выдавливание
|
||||||
|
.revolve = Вращение
|
||||||
|
.loft = Лофт
|
||||||
|
.sweep = Сдвиг по траектории
|
||||||
|
.presspull = Нажать/вытянуть
|
||||||
.n-specify-center-point = __ocs_arg_n__ Укажите центральную точку:
|
.n-specify-center-point = __ocs_arg_n__ Укажите центральную точку:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ Укажите первый угол:
|
.n-specify-first-corner = __ocs_arg_n__ Укажите первый угол:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Укажите высоту <Введите по умолчанию>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Укажите высоту <Введите по умолчанию>:
|
||||||
|
|
@ -2338,6 +2361,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
Зум
|
Зум
|
||||||
размеры
|
размеры
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: ð¢ðÁÐé ð¥ð▒ÐèðÁð║Ðéð¥ð▓ ÐêÐéÐÇð©Ðàð¥ð▓ð║ð©.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: нет текстовых или размерных объектов.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: нет текстовых или размерных объектов.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: еще не реализовано.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: еще не реализовано.
|
||||||
.viewport-s-in-layout = Видовые экраны (__ocs_fmt_0__) на листе «__ocs_fmt_1__»:
|
.viewport-s-in-layout = Видовые экраны (__ocs_fmt_0__) на листе «__ocs_fmt_1__»:
|
||||||
|
|
@ -2715,6 +2739,9 @@ properties =
|
||||||
.plot-style-table = Таблица стилей печати
|
.plot-style-table = Таблица стилей печати
|
||||||
.plot-table-attached-to = Стол для игровых участков, прикрепленный
|
.plot-table-attached-to = Стол для игровых участков, прикрепленный
|
||||||
.plot-table-type = Тип стола для игровых автоматов
|
.plot-table-type = Тип стола для игровых автоматов
|
||||||
|
.color-dependent-plot-styles = Цветозависимые стили печати
|
||||||
|
.named-plot-styles = Именованные стили печати
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = В цветозависимом режиме (CTB) стиль печати определяется цветом
|
||||||
.point-1-x = Точка 1 X
|
.point-1-x = Точка 1 X
|
||||||
.point-1-y = Точка 1 Y
|
.point-1-y = Точка 1 Y
|
||||||
.point-1-z = Точка 1 Z
|
.point-1-z = Точка 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = Çizim
|
.draw = Çizim
|
||||||
.annotate = Açıklama
|
.annotate = Açıklama
|
||||||
.insert = Ekle
|
.insert = Ekle
|
||||||
.model = Model
|
.model = Modelleme
|
||||||
.layout = Yerleşim
|
.layout = Yerleşim
|
||||||
.manage = Yönet
|
.manage = Yönet
|
||||||
.view = Görünüm
|
.view = Görünüm
|
||||||
|
|
@ -319,6 +319,7 @@ common =
|
||||||
.brightness = Parlaklık
|
.brightness = Parlaklık
|
||||||
.by-layer = Katmandan
|
.by-layer = Katmandan
|
||||||
.byblock = Bloktan
|
.byblock = Bloktan
|
||||||
|
.bycolor = Renge göre
|
||||||
.bylayer = Katmandan
|
.bylayer = Katmandan
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ katı yüzeye dönüştürüldü.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE: __ocs_fmt_0__ katı yüzeye dönüştürüldü.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: bu oturumda oluşturulmuş bir katı seçin.
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE: bu oturumda oluşturulmuş bir katı seçin.
|
||||||
|
|
@ -1253,6 +1254,8 @@ draw =
|
||||||
.donut-specify-center-of-donut-enter-to-exit = DONUT Halkanın merkezini belirtin (çıkmak için Enter):
|
.donut-specify-center-of-donut-enter-to-exit = DONUT Halkanın merkezini belirtin (çıkmak için Enter):
|
||||||
.donut-specify-inside-diameter-0 = DONUT İç çapı belirtin <0>:
|
.donut-specify-inside-diameter-0 = DONUT İç çapı belirtin <0>:
|
||||||
.donut-specify-outside-diameter = DONUT Dış çapı belirtin:
|
.donut-specify-outside-diameter = DONUT Dış çapı belirtin:
|
||||||
|
.donut-specify-second-point-for-inside-diameter = DONUT İç çap için ikinci noktayı belirtin:
|
||||||
|
.donut-specify-second-point-for-outside-diameter = DONUT Dış çap için ikinci noktayı belirtin:
|
||||||
.draw-manually = Elle çiz
|
.draw-manually = Elle çiz
|
||||||
.ellipse-specify-center = ELLIPSE Merkezi belirtin:
|
.ellipse-specify-center = ELLIPSE Merkezi belirtin:
|
||||||
.ellipse-specify-major-axis-endpoint = ELLIPSE Büyük eksen uç noktasını belirtin:
|
.ellipse-specify-major-axis-endpoint = ELLIPSE Büyük eksen uç noktasını belirtin:
|
||||||
|
|
@ -1807,14 +1810,24 @@ annotate =
|
||||||
.collect-leaders = Kılavuzları Topla
|
.collect-leaders = Kılavuzları Topla
|
||||||
.ddedit-select-text-entity = DDEDIT Metin nesnesini seçin:
|
.ddedit-select-text-entity = DDEDIT Metin nesnesini seçin:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Ölçü metnini girin (boş = ölçülen değer):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED Ölçü metnini girin (boş = ölçülen değer):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Ölçü çizgisinin konumunu belirtin [Metin/Açı]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED Biçimlendirilmiş ölçü metnini girin (boş = ölçülen değer):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Birinci uzatma çizgisinin başlangıcını belirtin:
|
.dimaligned-select-object-to-dimension = DIMALIGNED Ölçülendirilecek nesneyi seçin:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED İkinci uzatma çizgisinin başlangıcını belirtin [Metin/Açı]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED Ölçü çizgisinin konumunu belirtin [Mtext/Metin/Açı]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED Birinci uzatma çizgisinin başlangıcını belirtin veya nesne seçmek için Enter'a basın:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED İkinci uzatma çizgisinin başlangıcını belirtin:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED Metin açısını belirtin (derece):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED Metin açısını belirtin (derece):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR Ölçü metnini girin (boş = ölçülen değer):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR Biçimlendirilmiş ölçü metnini girin (boş = ölçülen değer):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR Yay, çember veya çizgi seçin ya da açı köşesi belirtin:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR İkinci çizgiyi seçin:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR Açı köşesini belirtin:
|
.dimangular-specify-angle-vertex = DIMANGULAR Açı köşesini belirtin:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR Ölçü yayının konumunu belirtin [Mtext/Metin/Açı/Kadran]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR Ölçü yayının konumunu belirtin:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR Ölçü yayının konumunu belirtin:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR Birinci uzatma çizgisi noktasını belirtin:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR Birinci uzatma çizgisi noktasını belirtin:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR Kadranı belirtin:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR İkinci uzatma çizgisi noktasını belirtin:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR İkinci uzatma çizgisi noktasını belirtin:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR Metin açısının ikinci noktasını belirtin:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR Metin açısını veya ilk noktayı belirtin:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Temel ölçü bulunamadı. Önce bir ölçü yerleştirin.
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE Temel ölçü bulunamadı. Önce bir ölçü yerleştirin.
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE İkinci uzatma çizgisinin başlangıcını belirtin (çıkmak için Enter):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE İkinci uzatma çizgisinin başlangıcını belirtin (çıkmak için Enter):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK Kesilecek ölçüyü seçin:
|
.dimbreak-select-dimension-to-break = DIMBREAK Kesilecek ölçüyü seçin:
|
||||||
|
|
@ -1822,8 +1835,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Temel ölçü bulunamadı. Önce bir ölçü yerleştirin.
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE Temel ölçü bulunamadı. Önce bir ölçü yerleştirin.
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE İkinci uzatma çizgisinin başlangıcını belirtin (çıkmak için Enter):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE İkinci uzatma çizgisinin başlangıcını belirtin (çıkmak için Enter):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Ölçü metnini girin (boş = ölçülen değer):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER Ölçü metnini girin (boş = ölçülen değer):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER Merkez noktasını belirtin:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER Yay, daire veya çoklu çizgi yayı seçin:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER Ölçü çizgisinin konumunu belirtin [Metin/Açı]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER Ölçü çizgisinin konumunu belirtin [Çok satırlı metin/Metin/Açı]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER Daire üzerindeki noktayı belirtin:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER Daire üzerindeki noktayı belirtin:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Metin açısını belirtin (derece):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER Metin açısını belirtin (derece):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Metin geçersiz kılmasını girin (boş = ölçülen değere sıfırla):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT Metin geçersiz kılmasını girin (boş = ölçülen değere sıfırla):
|
||||||
|
|
@ -1831,6 +1844,12 @@ annotate =
|
||||||
.dimjogged-created-a-jogged-radius-dimension = DIMJOGGED: kırıklı yarıçap ölçüsü oluşturuldu.
|
.dimjogged-created-a-jogged-radius-dimension = DIMJOGGED: kırıklı yarıçap ölçüsü oluşturuldu.
|
||||||
.dimjogged-invalid-radius = DIMJOGGED: yarıçap geçersiz.
|
.dimjogged-invalid-radius = DIMJOGGED: yarıçap geçersiz.
|
||||||
.dimjogged-select-an-arc-or-circle-first = DIMJOGGED: önce bir yay veya daire seçin.
|
.dimjogged-select-an-arc-or-circle-first = DIMJOGGED: önce bir yay veya daire seçin.
|
||||||
|
.dimjogged-enter-dimension-text-blank-measured = DIMJOGGED Ölçü metnini girin (boş = ölçülen değer):
|
||||||
|
.dimjogged-select-arc-circle-or-polyline-arc = DIMJOGGED Yay, daire veya çoklu çizgi yayı seçin:
|
||||||
|
.dimjogged-specify-center-location-override = DIMJOGGED Geçersiz kılınmış merkez konumunu belirtin:
|
||||||
|
.dimjogged-specify-dimension-line-location-text = DIMJOGGED Ölçü çizgisinin konumunu belirtin [Çok satırlı metin/Metin/Açı]:
|
||||||
|
.dimjogged-specify-jog-location = DIMJOGGED Kırılma konumunu belirtin:
|
||||||
|
.dimjogged-specify-text-angle-degrees = DIMJOGGED Metin açısını belirtin (derece):
|
||||||
.dimjogline-select-linear-or-aligned-dimension = DIMJOGLINE Doğrusal veya hizalı ölçüyü seçin:
|
.dimjogline-select-linear-or-aligned-dimension = DIMJOGLINE Doğrusal veya hizalı ölçüyü seçin:
|
||||||
.dimjogline-specify-jog-location = DIMJOGLINE Kırılma konumunu belirtin:
|
.dimjogline-specify-jog-location = DIMJOGLINE Kırılma konumunu belirtin:
|
||||||
.dimlinear-enter-dimension-text-blank-measured = DIMLINEAR Ölçü metnini girin (boş = ölçülen değer):
|
.dimlinear-enter-dimension-text-blank-measured = DIMLINEAR Ölçü metnini girin (boş = ölçülen değer):
|
||||||
|
|
@ -1839,10 +1858,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR İkinci uzatma çizgisinin başlangıcını belirtin [Metin/Açı]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR İkinci uzatma çizgisinin başlangıcını belirtin [Metin/Açı]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR Metin açısını belirtin (derece):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR Metin açısını belirtin (derece):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE Unsur konumunu belirtin:
|
.dimordinate-specify-feature-location = DIMORDINATE Unsur konumunu belirtin:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE Kılavuzun uç noktasını belirtin:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE Ölçü metnini girin (boş = ölçülen değer):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE Kılavuzun uç noktasını belirtin [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE Metin açısını belirtin (derece):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Ölçü metnini girin (boş = ölçülen değer):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS Ölçü metnini girin (boş = ölçülen değer):
|
||||||
.dimradius-specify-center-point = DIMRADIUS Merkez noktasını belirtin:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS Yay, daire veya çoklu çizgi yayı seçin:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS Ölçü çizgisinin konumunu belirtin [Metin/Açı]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS Ölçü çizgisinin konumunu belirtin [Çok satırlı metin/Metin/Açı]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS Yarıçap noktasını belirtin:
|
.dimradius-specify-radius-point = DIMRADIUS Yarıçap noktasını belirtin:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS Metin açısını belirtin (derece):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS Metin açısını belirtin (derece):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE Değeri girin (0 = otomatik):
|
.dimspace-enter-value-0-auto = DIMSPACE Değeri girin (0 = otomatik):
|
||||||
|
|
@ -1870,6 +1891,7 @@ annotate =
|
||||||
.link-data = Verileri Bağla
|
.link-data = Verileri Bağla
|
||||||
.lower = Alt
|
.lower = Alt
|
||||||
.mleader-specify-arrowhead-point = MLEADER Ok ucu noktasını belirtin:
|
.mleader-specify-arrowhead-point = MLEADER Ok ucu noktasını belirtin:
|
||||||
|
.mleader-specify-landing-point = MLEADER Yatay bağlantı noktasını belirtin:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER Sonraki noktayı belirtin [__ocs_arg_count__ nokta — metni yerleştirmek için Enter]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER Sonraki noktayı belirtin [__ocs_arg_count__ nokta — metni yerleştirmek için Enter]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD Bir çoklu kılavuz seçin:
|
.mleaderadd-select-a-multileader = MLEADERADD Bir çoklu kılavuz seçin:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD Ok ucu konumunu belirtin:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD Ok ucu konumunu belirtin:
|
||||||
|
|
@ -1901,8 +1923,13 @@ annotate =
|
||||||
.scale-list = Ölçek Listesi
|
.scale-list = Ölçek Listesi
|
||||||
.sentence = Cümle Düzeni
|
.sentence = Cümle Düzeni
|
||||||
.sync-scales = Ölçekleri Eşitle
|
.sync-scales = Ölçekleri Eşitle
|
||||||
|
.table-cell-enter-text-row-column-current = TABLE CELL [__ocs_arg_row__,__ocs_arg_column__] hücresi için metni girin <__ocs_arg_current__>:
|
||||||
|
.table-enter-column-width-width = TABLE Sütun genişliğini girin [__ocs_arg_width__]:
|
||||||
.table-enter-number-of-columns-cols = TABLE Sütun sayısını girin [__ocs_arg_cols__]:
|
.table-enter-number-of-columns-cols = TABLE Sütun sayısını girin [__ocs_arg_cols__]:
|
||||||
|
.table-enter-number-of-data-rows-rows-cols = TABLE Veri satırı sayısını girin [__ocs_arg_rows__] (__ocs_arg_cols__ sütun):
|
||||||
.table-enter-number-of-rows-incl-header-rows-co = TABLE Satır sayısını girin (başlık dahil) [__ocs_arg_rows__] (__ocs_arg_cols__ sütun):
|
.table-enter-number-of-rows-incl-header-rows-co = TABLE Satır sayısını girin (başlık dahil) [__ocs_arg_rows__] (__ocs_arg_cols__ sütun):
|
||||||
|
.table-enter-row-height-height = TABLE Satır yüksekliğini girin [__ocs_arg_height__]:
|
||||||
|
.table-insertion-behavior-point-window-mode = TABLE Ekleme yöntemi [Nokta/Pencere] <__ocs_arg_mode__>:
|
||||||
.table-specify-insertion-point-cols-rows = TABLE Ekleme noktasını belirtin [__ocs_arg_cols__×__ocs_arg_rows__]:
|
.table-specify-insertion-point-cols-rows = TABLE Ekleme noktasını belirtin [__ocs_arg_cols__×__ocs_arg_rows__]:
|
||||||
.tcase-upper-lower-sentence-title = TCASE [Büyük Harf / Küçük Harf / Cümle Düzeni / Başlık Düzeni]:
|
.tcase-upper-lower-sentence-title = TCASE [Büyük Harf / Küçük Harf / Cümle Düzeni / Başlık Düzeni]:
|
||||||
.tcase-select-text-objects-first = TCASE: Önce metin nesnelerini seçin.
|
.tcase-select-text-objects-first = TCASE: Önce metin nesnelerini seçin.
|
||||||
|
|
@ -2023,6 +2050,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ nesne panoya kesildi.
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ nesne panoya kesildi.
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = Oluştur
|
||||||
|
.boolean = Boole işlemleri
|
||||||
|
.pyramid = Piramit
|
||||||
|
.polysolid = Çoklu katı
|
||||||
|
.extrude = Katılaştır
|
||||||
|
.revolve = Döndür
|
||||||
|
.loft = Kesitler arası
|
||||||
|
.sweep = Yol boyunca süpür
|
||||||
|
.presspull = İt/Çek
|
||||||
.n-specify-center-point = __ocs_arg_n__ Merkez noktasını belirtin:
|
.n-specify-center-point = __ocs_arg_n__ Merkez noktasını belirtin:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ İlk köşeyi belirtin:
|
.n-specify-first-corner = __ocs_arg_n__ İlk köşeyi belirtin:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ Yüksekliği belirtin <varsayılan için Enter>:
|
.n-specify-height-enter-for-default = __ocs_arg_n__ Yüksekliği belirtin <varsayılan için Enter>:
|
||||||
|
|
@ -2166,6 +2202,7 @@ view =
|
||||||
.color-scheme-set-to-name = Renk düzeni '__ocs_fmt_0__' olarak ayarlandı.
|
.color-scheme-set-to-name = Renk düzeni '__ocs_fmt_0__' olarak ayarlandı.
|
||||||
.current-annotation-scale-name = Geçerli açıklama ölçeği: __ocs_fmt_0__
|
.current-annotation-scale-name = Geçerli açıklama ölçeği: __ocs_fmt_0__
|
||||||
.datalink-path-to-the-csv-file = DATALINK .csv dosyasının yolu:
|
.datalink-path-to-the-csv-file = DATALINK .csv dosyasının yolu:
|
||||||
|
.datalink-specify-insertion-point = DATALINK Ekleme noktasını belirtin:
|
||||||
.datalink-cannot-read-path-e = DATALINK: "__ocs_fmt_0__" okunamıyor: __ocs_fmt_1__
|
.datalink-cannot-read-path-e = DATALINK: "__ocs_fmt_0__" okunamıyor: __ocs_fmt_1__
|
||||||
.datalink-imported-nrows-ncols-cells-into-a-tab = DATALINK: __ocs_fmt_0__×__ocs_fmt_1__ hücre, başlangıç noktasındaki tabloya aktarıldı.
|
.datalink-imported-nrows-ncols-cells-into-a-tab = DATALINK: __ocs_fmt_0__×__ocs_fmt_1__ hücre, başlangıç noktasındaki tabloya aktarıldı.
|
||||||
.datalink-the-csv-file-is-empty = DATALINK: CSV dosyası boş.
|
.datalink-the-csv-file-is-empty = DATALINK: CSV dosyası boş.
|
||||||
|
|
@ -2234,6 +2271,11 @@ view =
|
||||||
.shortcut-set-key-cmd-str = Kısayol ayarlandı: __ocs_fmt_0__ → __ocs_fmt_1__
|
.shortcut-set-key-cmd-str = Kısayol ayarlandı: __ocs_fmt_0__ → __ocs_fmt_1__
|
||||||
.table-cell-select-a-table-entity-first-or-row = TABLE CELL: Önce bir Tablo nesnesi seçin ya da satır/sütun aralık dışında.
|
.table-cell-select-a-table-entity-first-or-row = TABLE CELL: Önce bir Tablo nesnesi seçin ya da satır/sütun aralık dışında.
|
||||||
.table-cell-set-row-col-text = TABLE CELL: [__ocs_fmt_0__,__ocs_fmt_1__] = "__ocs_fmt_2__" olarak ayarlandı.
|
.table-cell-set-row-col-text = TABLE CELL: [__ocs_fmt_0__,__ocs_fmt_1__] = "__ocs_fmt_2__" olarak ayarlandı.
|
||||||
|
.table-block-definition-not-found = TABLE: Blok tanımı bulunamadı.
|
||||||
|
.table-edit-applied = TABLE: Düzenleme uygulandı.
|
||||||
|
.table-field-handle-not-found = TABLE: Alan tanıtıcısı bulunamadı.
|
||||||
|
.table-row-column-out-of-range-or-cell-locked = TABLE: Satır/sütun aralık dışında veya hücre kilitli.
|
||||||
|
.table-select-a-table-first = TABLE: Önce bir tablo seçin.
|
||||||
.toolpalettes-tool-palettes-not-yet-implemented = TOOLPALETTES: Araç Paletleri henüz uygulanmadı.
|
.toolpalettes-tool-palettes-not-yet-implemented = TOOLPALETTES: Araç Paletleri henüz uygulanmadı.
|
||||||
.tile-horiz = Yatay Döşe
|
.tile-horiz = Yatay Döşe
|
||||||
.tile-vert = Dikey Döşe
|
.tile-vert = Dikey Döşe
|
||||||
|
|
@ -2270,6 +2312,7 @@ view =
|
||||||
.usage-shortcuts-list-set-key-cmd-clear-key = Kullanım: SHORTCUTS LIST | SET <key> <cmd> | CLEAR <key>
|
.usage-shortcuts-list-set-key-cmd-clear-key = Kullanım: SHORTCUTS LIST | SET <key> <cmd> | CLEAR <key>
|
||||||
.usage-shortcuts-set-key-command-e-g-shortcuts = Kullanım: SHORTCUTS SET <key> <command> ör. SHORTCUTS SET CTRL+D DIST
|
.usage-shortcuts-set-key-command-e-g-shortcuts = Kullanım: SHORTCUTS SET <key> <command> ör. SHORTCUTS SET CTRL+D DIST
|
||||||
.usage-table-cell-row-col-text = Kullanım: TABLE CELL <row> <col> <text>
|
.usage-table-cell-row-col-text = Kullanım: TABLE CELL <row> <col> <text>
|
||||||
|
.usage-table-edit-actions = Kullanım: TABLE CELL | INSERTROW | DELETEROW | INSERTCOL | DELETECOL | MERGE | UNMERGE | ROWHEIGHT | COLWIDTH | LOCK | BLOCK | FORMULA | FIELD
|
||||||
.usage-ucsicon-on-off-noorigin-origin = Kullanım: UCSICON ON | OFF | NOORIGIN | ORIGIN
|
.usage-ucsicon-on-off-noorigin-origin = Kullanım: UCSICON ON | OFF | NOORIGIN | ORIGIN
|
||||||
.usage-underlay-fade-n-contrast-n-on-off-clip-o = Kullanım: UNDERLAY [FADE <n>|CONTRAST <n>|ON|OFF|CLIP ON|OFF|MONO ON|OFF]
|
.usage-underlay-fade-n-contrast-n-on-off-clip-o = Kullanım: UNDERLAY [FADE <n>|CONTRAST <n>|ON|OFF|CLIP ON|OFF|MONO ON|OFF]
|
||||||
.usage-useri-1-5-int-userr-1-5-real = Kullanım: USERI <1-5> <int> | USERR <1-5> <real>
|
.usage-useri-1-5-int-userr-1-5-real = Kullanım: USERI <1-5> <int> | USERR <1-5> <real>
|
||||||
|
|
@ -2303,6 +2346,7 @@ view =
|
||||||
.zoom-window-specify-first-corner = ZOOM WINDOW İlk köşeyi belirtin:
|
.zoom-window-specify-first-corner = ZOOM WINDOW İlk köşeyi belirtin:
|
||||||
.zoom-window-specify-opposite-corner = ZOOM WINDOW Karşı köşeyi belirtin:
|
.zoom-window-specify-opposite-corner = ZOOM WINDOW Karşı köşeyi belirtin:
|
||||||
.zoom-extents = Sınırlara Yakınlaştır
|
.zoom-extents = Sınırlara Yakınlaştır
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__: Tarama nesnesi yok.
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: Metin veya ölçü nesnesi yok.
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__: Metin veya ölçü nesnesi yok.
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__: Henüz uygulanmadı.
|
.cmd-not-yet-implemented = __ocs_fmt_0__: Henüz uygulanmadı.
|
||||||
.viewport-s-in-layout = "__ocs_fmt_1__" yerleşiminde __ocs_fmt_0__ görünüm alanı:
|
.viewport-s-in-layout = "__ocs_fmt_1__" yerleşiminde __ocs_fmt_0__ görünüm alanı:
|
||||||
|
|
@ -2568,6 +2612,7 @@ properties =
|
||||||
.island-detection-style = Ada algılama stili
|
.island-detection-style = Ada algılama stili
|
||||||
.italic = İtalik
|
.italic = İtalik
|
||||||
.jog-angle = Kırılma Açısı
|
.jog-angle = Kırılma Açısı
|
||||||
|
.jogged-radius = Kırıklı Yarıçap
|
||||||
.jog-x = Kırılma X
|
.jog-x = Kırılma X
|
||||||
.jog-y = Kırılma Y
|
.jog-y = Kırılma Y
|
||||||
.jog-z = Kırılma Z
|
.jog-z = Kırılma Z
|
||||||
|
|
@ -2698,6 +2743,9 @@ properties =
|
||||||
.plot-style-table = Çizim stili tablosu
|
.plot-style-table = Çizim stili tablosu
|
||||||
.plot-table-attached-to = Çizim tablosunun eklendiği
|
.plot-table-attached-to = Çizim tablosunun eklendiği
|
||||||
.plot-table-type = Çizim tablosu türü
|
.plot-table-type = Çizim tablosu türü
|
||||||
|
.color-dependent-plot-styles = Renge bağlı çizim stilleri
|
||||||
|
.named-plot-styles = Adlandırılmış çizim stilleri
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = Renge Bağlı (CTB) modunda çizim stili renge kilitlidir
|
||||||
.point-1-x = Nokta 1 X
|
.point-1-x = Nokta 1 X
|
||||||
.point-1-y = Nokta 1 Y
|
.point-1-y = Nokta 1 Y
|
||||||
.point-1-z = Nokta 1 Z
|
.point-1-z = Nokta 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = 绘制
|
.draw = 绘制
|
||||||
.annotate = 注释
|
.annotate = 注释
|
||||||
.insert = 插入
|
.insert = 插入
|
||||||
.model = 模型
|
.model = 建模
|
||||||
.layout = 布局
|
.layout = 布局
|
||||||
.manage = 管理
|
.manage = 管理
|
||||||
.view = 视图
|
.view = 视图
|
||||||
|
|
@ -320,6 +320,7 @@ common =
|
||||||
.brightness = 亮度
|
.brightness = 亮度
|
||||||
.by-layer = 按层
|
.by-layer = 按层
|
||||||
.byblock = 按块
|
.byblock = 按块
|
||||||
|
.bycolor = 按颜色
|
||||||
.bylayer = 随层
|
.bylayer = 随层
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE:将__ocs_fmt_0__固态转换为地表.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE:将__ocs_fmt_0__固态转换为地表.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE:选择创建此会话的固件 。
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE:选择创建此会话的固件 。
|
||||||
|
|
@ -1786,14 +1787,24 @@ annotate =
|
||||||
.collect-leaders = 合并引线
|
.collect-leaders = 合并引线
|
||||||
.ddedit-select-text-entity = DDEDIT 选择文字实体:
|
.ddedit-select-text-entity = DDEDIT 选择文字实体:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED 输入标注文字(空白 = 测量值):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED 输入标注文字(空白 = 测量值):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 指定尺寸线位置 [文字/角度]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED 输入带格式的标注文字(空白 = 测量值):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 指定第一条尺寸界线原点:
|
.dimaligned-select-object-to-dimension = DIMALIGNED 选择要标注的对象:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 指定第二条尺寸界线原点 [文字/角度]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 指定尺寸线位置 [Mtext/文字/角度]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 指定第一条尺寸界线原点,或按 Enter 选择对象:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 指定第二条尺寸界线原点:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED 指定文字角度(度):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED 指定文字角度(度):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR 输入标注文字(空白 = 测量值):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR 输入带格式的标注文字(空白 = 测量值):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR 选择圆弧、圆或直线,或指定角度顶点:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR 选择第二条直线:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR 指定角度顶点:
|
.dimangular-specify-angle-vertex = DIMANGULAR 指定角度顶点:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR 指定标注圆弧位置 [Mtext/文字/角度/象限]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR 指定标注圆弧位置:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR 指定标注圆弧位置:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR 指定第一条尺寸界线点:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR 指定第一条尺寸界线点:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR 指定象限:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR 指定第二条尺寸界线点:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR 指定第二条尺寸界线点:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR 指定文字角度的第二点:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR 指定文字角度或第一点:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 未找到基准标注。请先放置标注。
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 未找到基准标注。请先放置标注。
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 指定第二条尺寸界线原点(按 Enter 退出):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 指定第二条尺寸界线原点(按 Enter 退出):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK 选择要打断的标注:
|
.dimbreak-select-dimension-to-break = DIMBREAK 选择要打断的标注:
|
||||||
|
|
@ -1801,8 +1812,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 未找到基准标注。请先放置标注。
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 未找到基准标注。请先放置标注。
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 指定第二条尺寸界线原点(按 Enter 退出):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 指定第二条尺寸界线原点(按 Enter 退出):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER 输入标注文字(空白 = 测量值):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER 输入标注文字(空白 = 测量值):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER 指定中心点:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER 选择圆弧、圆或多段线圆弧:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER 指定尺寸线位置 [文字/角度]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER 指定尺寸线位置 [多行文字/文字/角度]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER 指定圆上的点:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER 指定圆上的点:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 指定文字角度(度):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 指定文字角度(度):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT 输入文字替代(空白 = 重置为测量值):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT 输入文字替代(空白 = 重置为测量值):
|
||||||
|
|
@ -1818,10 +1829,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 指定第二条尺寸界线原点 [文字/角度]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 指定第二条尺寸界线原点 [文字/角度]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR 指定文字角度(度):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR 指定文字角度(度):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE 指定特征位置:
|
.dimordinate-specify-feature-location = DIMORDINATE 指定特征位置:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE 指定引线端点:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE 输入标注文字(空白 = 测量值):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE 指定引线端点 [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE 指定文字角度(度):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS 输入标注文字(空白 = 测量值):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS 输入标注文字(空白 = 测量值):
|
||||||
.dimradius-specify-center-point = DIMRADIUS 指定中心点:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS 选择圆弧、圆或多段线圆弧:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS 指定尺寸线位置 [文字/角度]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS 指定尺寸线位置 [多行文字/文字/角度]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS 指定半径点:
|
.dimradius-specify-radius-point = DIMRADIUS 指定半径点:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS 指定文字角度(度):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS 指定文字角度(度):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE 输入值(0 = 自动):
|
.dimspace-enter-value-0-auto = DIMSPACE 输入值(0 = 自动):
|
||||||
|
|
@ -1853,6 +1866,7 @@ annotate =
|
||||||
数据
|
数据
|
||||||
.lower = 下调
|
.lower = 下调
|
||||||
.mleader-specify-arrowhead-point = MLEADER 指定箭头点:
|
.mleader-specify-arrowhead-point = MLEADER 指定箭头点:
|
||||||
|
.mleader-specify-landing-point = MLEADER 指定水平基线点:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER 指定下一点 [__ocs_arg_count__ 个点 — 按 Enter 放置文字]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER 指定下一点 [__ocs_arg_count__ 个点 — 按 Enter 放置文字]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD 选择多重引线:
|
.mleaderadd-select-a-multileader = MLEADERADD 选择多重引线:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD 指定箭头位置:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD 指定箭头位置:
|
||||||
|
|
@ -2018,6 +2032,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ 对象切入剪贴板。
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ 对象切入剪贴板。
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = 创建
|
||||||
|
.boolean = 布尔运算
|
||||||
|
.pyramid = 棱锥
|
||||||
|
.polysolid = 多段体
|
||||||
|
.extrude = 拉伸
|
||||||
|
.revolve = 旋转
|
||||||
|
.loft = 放样
|
||||||
|
.sweep = 扫掠
|
||||||
|
.presspull = 按拉
|
||||||
.n-specify-center-point = __ocs_arg_n__ 指定中心点:
|
.n-specify-center-point = __ocs_arg_n__ 指定中心点:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ 指定第一个角点:
|
.n-specify-first-corner = __ocs_arg_n__ 指定第一个角点:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ 指定高度(按 Enter 使用默认值):
|
.n-specify-height-enter-for-default = __ocs_arg_n__ 指定高度(按 Enter 使用默认值):
|
||||||
|
|
@ -2318,6 +2341,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
缩放
|
缩放
|
||||||
范围
|
范围
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__:µ▓íµ£ëÕø¥µíêÕí½ÕààÕ»╣Þ▒íÒÇé
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__:没有文本或维度对象。
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__:没有文本或维度对象。
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__:尚未执行。
|
.cmd-not-yet-implemented = __ocs_fmt_0__:尚未执行。
|
||||||
.viewport-s-in-layout = 布局“__ocs_fmt_1__”中的视口(__ocs_fmt_0__ 个):
|
.viewport-s-in-layout = 布局“__ocs_fmt_1__”中的视口(__ocs_fmt_0__ 个):
|
||||||
|
|
@ -2695,6 +2719,9 @@ properties =
|
||||||
.plot-style-table = 打印样式表
|
.plot-style-table = 打印样式表
|
||||||
.plot-table-attached-to = 打印样式表应用于
|
.plot-table-attached-to = 打印样式表应用于
|
||||||
.plot-table-type = 打印样式表类型
|
.plot-table-type = 打印样式表类型
|
||||||
|
.color-dependent-plot-styles = 颜色相关打印样式
|
||||||
|
.named-plot-styles = 命名打印样式
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = 在颜色相关 (CTB) 模式下,打印样式由颜色决定
|
||||||
.point-1-x = 点 1 X
|
.point-1-x = 点 1 X
|
||||||
.point-1-y = 点 1 Y
|
.point-1-y = 点 1 Y
|
||||||
.point-1-z = 点 1 Z
|
.point-1-z = 点 1 Z
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ ribbon-tab =
|
||||||
.draw = 繪製
|
.draw = 繪製
|
||||||
.annotate = 註解
|
.annotate = 註解
|
||||||
.insert = 插入
|
.insert = 插入
|
||||||
.model = 模型
|
.model = 建模
|
||||||
.layout = 配置
|
.layout = 配置
|
||||||
.manage = 管理
|
.manage = 管理
|
||||||
.view = 視圖
|
.view = 視圖
|
||||||
|
|
@ -332,6 +332,7 @@ common =
|
||||||
.brightness = 亮度
|
.brightness = 亮度
|
||||||
.by-layer = 按層
|
.by-layer = 按層
|
||||||
.byblock = 依圖塊
|
.byblock = 依圖塊
|
||||||
|
.bycolor = 依色彩
|
||||||
.bylayer = 隨層
|
.bylayer = 隨層
|
||||||
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE:將__ocs_fmt_0__實體轉換為曲面.
|
.convtosurface-converted-n-solid-s-to-surface-s = CONVTOSURFACE:將__ocs_fmt_0__實體轉換為曲面.
|
||||||
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE:選擇建立此會話的固件 。
|
.convtosurface-select-a-solid-created-this-sess = CONVTOSURFACE:選擇建立此會話的固件 。
|
||||||
|
|
@ -1798,14 +1799,24 @@ annotate =
|
||||||
.collect-leaders = 合併引線
|
.collect-leaders = 合併引線
|
||||||
.ddedit-select-text-entity = DDEDIT 選擇文字實體:
|
.ddedit-select-text-entity = DDEDIT 選擇文字實體:
|
||||||
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED 輸入標註文字(空白 = 測量值):
|
.dimaligned-enter-dimension-text-blank-measured = DIMALIGNED 輸入標註文字(空白 = 測量值):
|
||||||
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 指定尺寸線位置 [文字/角度]:
|
.dimaligned-enter-formatted-dimension-text-blank-measured = DIMALIGNED 輸入帶格式的標註文字(空白 = 測量值):
|
||||||
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 指定第一條尺寸界線原點:
|
.dimaligned-select-object-to-dimension = DIMALIGNED 選取要標註的物件:
|
||||||
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 指定第二條尺寸界線原點 [文字/角度]:
|
.dimaligned-specify-dimension-line-location-tex = DIMALIGNED 指定尺寸線位置 [Mtext/文字/角度]:
|
||||||
|
.dimaligned-specify-first-extension-line-origin = DIMALIGNED 指定第一條尺寸界線原點,或按 Enter 選取物件:
|
||||||
|
.dimaligned-specify-second-extension-line-origi = DIMALIGNED 指定第二條尺寸界線原點:
|
||||||
.dimaligned-specify-text-angle-degrees = DIMALIGNED 指定文字角度(度):
|
.dimaligned-specify-text-angle-degrees = DIMALIGNED 指定文字角度(度):
|
||||||
|
.dimangular-enter-dimension-text-blank-measured = DIMANGULAR 輸入標註文字(空白 = 測量值):
|
||||||
|
.dimangular-enter-formatted-dimension-text-blank-measured = DIMANGULAR 輸入帶格式的標註文字(空白 = 測量值):
|
||||||
|
.dimangular-select-arc-circle-line-or-specify-angle-vertex = DIMANGULAR 選取圓弧、圓或直線,或指定角度頂點:
|
||||||
|
.dimangular-select-second-line = DIMANGULAR 選取第二條直線:
|
||||||
.dimangular-specify-angle-vertex = DIMANGULAR 指定角度頂點:
|
.dimangular-specify-angle-vertex = DIMANGULAR 指定角度頂點:
|
||||||
|
.dimangular-specify-dimension-arc-location-options = DIMANGULAR 指定標註圓弧位置 [Mtext/文字/角度/象限]:
|
||||||
.dimangular-specify-dimension-arc-location = DIMANGULAR 指定標註圓弧位置:
|
.dimangular-specify-dimension-arc-location = DIMANGULAR 指定標註圓弧位置:
|
||||||
.dimangular-specify-first-extension-line-point = DIMANGULAR 指定第一條尺寸界線點:
|
.dimangular-specify-first-extension-line-point = DIMANGULAR 指定第一條尺寸界線點:
|
||||||
|
.dimangular-specify-quadrant = DIMANGULAR 指定象限:
|
||||||
.dimangular-specify-second-extension-line-point = DIMANGULAR 指定第二條尺寸界線點:
|
.dimangular-specify-second-extension-line-point = DIMANGULAR 指定第二條尺寸界線點:
|
||||||
|
.dimangular-specify-second-point-for-text-angle = DIMANGULAR 指定文字角度的第二點:
|
||||||
|
.dimangular-specify-text-angle-or-first-point = DIMANGULAR 指定文字角度或第一點:
|
||||||
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 未找到基準標註。請先放置標註。
|
.dimbaseline-no-base-dimension-found-place-a-di = DIMBASELINE 未找到基準標註。請先放置標註。
|
||||||
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 指定第二條尺寸界線原點(按 Enter 退出):
|
.dimbaseline-specify-a-second-extension-line-or = DIMBASELINE 指定第二條尺寸界線原點(按 Enter 退出):
|
||||||
.dimbreak-select-dimension-to-break = DIMBREAK 選擇要打斷的標註:
|
.dimbreak-select-dimension-to-break = DIMBREAK 選擇要打斷的標註:
|
||||||
|
|
@ -1813,8 +1824,8 @@ annotate =
|
||||||
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 未找到基準標註。請先放置標註。
|
.dimcontinue-no-base-dimension-found-place-a-di = DIMCONTINUE 未找到基準標註。請先放置標註。
|
||||||
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 指定第二條尺寸界線原點(按 Enter 退出):
|
.dimcontinue-specify-a-second-extension-line-or = DIMCONTINUE 指定第二條尺寸界線原點(按 Enter 退出):
|
||||||
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER 輸入標註文字(空白 = 測量值):
|
.dimdiameter-enter-dimension-text-blank-measure = DIMDIAMETER 輸入標註文字(空白 = 測量值):
|
||||||
.dimdiameter-specify-center-point = DIMDIAMETER 指定中心點:
|
.dimdiameter-select-arc-circle-or-polyline-arc = DIMDIAMETER 選擇圓弧、圓或聚合線圓弧:
|
||||||
.dimdiameter-specify-dimension-line-location-te = DIMDIAMETER 指定尺寸線位置 [文字/角度]:
|
.dimdiameter-specify-dimension-line-location-text = DIMDIAMETER 指定尺寸線位置 [多行文字/文字/角度]:
|
||||||
.dimdiameter-specify-point-on-circle = DIMDIAMETER 指定圓上的點:
|
.dimdiameter-specify-point-on-circle = DIMDIAMETER 指定圓上的點:
|
||||||
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 指定文字角度(度):
|
.dimdiameter-specify-text-angle-degrees = DIMDIAMETER 指定文字角度(度):
|
||||||
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT 輸入文字替代(空白 = 重置為測量值):
|
.dimedit-enter-text-override-blank-reset-to-mea = DIMEDIT 輸入文字替代(空白 = 重置為測量值):
|
||||||
|
|
@ -1830,10 +1841,12 @@ annotate =
|
||||||
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 指定第二條尺寸界線原點 [文字/角度]:
|
.dimlinear-specify-second-extension-line-origin = DIMLINEAR 指定第二條尺寸界線原點 [文字/角度]:
|
||||||
.dimlinear-specify-text-angle-degrees = DIMLINEAR 指定文字角度(度):
|
.dimlinear-specify-text-angle-degrees = DIMLINEAR 指定文字角度(度):
|
||||||
.dimordinate-specify-feature-location = DIMORDINATE 指定特徵位置:
|
.dimordinate-specify-feature-location = DIMORDINATE 指定特徵位置:
|
||||||
.dimordinate-specify-leader-endpoint = DIMORDINATE 指定引線端點:
|
.dimordinate-enter-dimension-text-blank-measured = DIMORDINATE 輸入標註文字(空白 = 測量值):
|
||||||
|
.dimordinate-specify-leader-endpoint = DIMORDINATE 指定引線端點 [Xdatum/Ydatum/Mtext/Text/Angle]:
|
||||||
|
.dimordinate-specify-text-angle-degrees = DIMORDINATE 指定文字角度(度):
|
||||||
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS 輸入標註文字(空白 = 測量值):
|
.dimradius-enter-dimension-text-blank-measured = DIMRADIUS 輸入標註文字(空白 = 測量值):
|
||||||
.dimradius-specify-center-point = DIMRADIUS 指定中心點:
|
.dimradius-select-arc-circle-or-polyline-arc = DIMRADIUS 選擇圓弧、圓或聚合線圓弧:
|
||||||
.dimradius-specify-dimension-line-location-text = DIMRADIUS 指定尺寸線位置 [文字/角度]:
|
.dimradius-specify-dimension-line-location-text = DIMRADIUS 指定尺寸線位置 [多行文字/文字/角度]:
|
||||||
.dimradius-specify-radius-point = DIMRADIUS 指定半徑點:
|
.dimradius-specify-radius-point = DIMRADIUS 指定半徑點:
|
||||||
.dimradius-specify-text-angle-degrees = DIMRADIUS 指定文字角度(度):
|
.dimradius-specify-text-angle-degrees = DIMRADIUS 指定文字角度(度):
|
||||||
.dimspace-enter-value-0-auto = DIMSPACE 輸入值(0 = 自動):
|
.dimspace-enter-value-0-auto = DIMSPACE 輸入值(0 = 自動):
|
||||||
|
|
@ -1865,6 +1878,7 @@ annotate =
|
||||||
資料
|
資料
|
||||||
.lower = 下調
|
.lower = 下調
|
||||||
.mleader-specify-arrowhead-point = MLEADER 指定箭頭點:
|
.mleader-specify-arrowhead-point = MLEADER 指定箭頭點:
|
||||||
|
.mleader-specify-landing-point = MLEADER 指定水平基線點:
|
||||||
.mleader-specify-next-point-count-pts-enter-to = MLEADER 指定下一點 [__ocs_arg_count__ 個點 — 按 Enter 放置文字]:
|
.mleader-specify-next-point-count-pts-enter-to = MLEADER 指定下一點 [__ocs_arg_count__ 個點 — 按 Enter 放置文字]:
|
||||||
.mleaderadd-select-a-multileader = MLEADERADD 選擇多重引線:
|
.mleaderadd-select-a-multileader = MLEADERADD 選擇多重引線:
|
||||||
.mleaderadd-specify-arrowhead-location = MLEADERADD 指定箭頭位置:
|
.mleaderadd-specify-arrowhead-location = MLEADERADD 指定箭頭位置:
|
||||||
|
|
@ -2030,6 +2044,15 @@ insert =
|
||||||
.object-s-cut-to-clipboard = __ocs_fmt_0__ 物件切入剪貼簿。
|
.object-s-cut-to-clipboard = __ocs_fmt_0__ 物件切入剪貼簿。
|
||||||
|
|
||||||
model =
|
model =
|
||||||
|
.create = 建立
|
||||||
|
.boolean = 布林運算
|
||||||
|
.pyramid = 角錐體
|
||||||
|
.polysolid = 多段實體
|
||||||
|
.extrude = 擠出
|
||||||
|
.revolve = 旋轉
|
||||||
|
.loft = 放樣
|
||||||
|
.sweep = 掃掠
|
||||||
|
.presspull = 按拉
|
||||||
.n-specify-center-point = __ocs_arg_n__ 指定中心點:
|
.n-specify-center-point = __ocs_arg_n__ 指定中心點:
|
||||||
.n-specify-first-corner = __ocs_arg_n__ 指定第一個角點:
|
.n-specify-first-corner = __ocs_arg_n__ 指定第一個角點:
|
||||||
.n-specify-height-enter-for-default = __ocs_arg_n__ 指定高度(按 Enter 使用預設值):
|
.n-specify-height-enter-for-default = __ocs_arg_n__ 指定高度(按 Enter 使用預設值):
|
||||||
|
|
@ -2330,6 +2353,7 @@ view =
|
||||||
.zoom-extents =
|
.zoom-extents =
|
||||||
縮放
|
縮放
|
||||||
範圍
|
範圍
|
||||||
|
.cmd-no-hatch-objects = __ocs_fmt_0__:µ▓Ƶ£ëÕí½ÕààþÀÜþë®õ╗ÂÒÇé
|
||||||
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__:沒有文字或維度物件。
|
.cmd-no-text-or-dimension-objects = __ocs_fmt_0__:沒有文字或維度物件。
|
||||||
.cmd-not-yet-implemented = __ocs_fmt_0__:尚未實作。
|
.cmd-not-yet-implemented = __ocs_fmt_0__:尚未實作。
|
||||||
.viewport-s-in-layout = 配置“__ocs_fmt_1__”中的視口(__ocs_fmt_0__ 個):
|
.viewport-s-in-layout = 配置“__ocs_fmt_1__”中的視口(__ocs_fmt_0__ 個):
|
||||||
|
|
@ -2707,6 +2731,9 @@ properties =
|
||||||
.plot-style-table = 列印樣式表
|
.plot-style-table = 列印樣式表
|
||||||
.plot-table-attached-to = 列印樣式表應用於
|
.plot-table-attached-to = 列印樣式表應用於
|
||||||
.plot-table-type = 列印樣式表類型
|
.plot-table-type = 列印樣式表類型
|
||||||
|
.color-dependent-plot-styles = 色彩相關出圖型式
|
||||||
|
.named-plot-styles = 具名出圖型式
|
||||||
|
.plot-style-is-locked-to-color-in-color-dependent = 在色彩相關 (CTB) 模式下,出圖型式由色彩決定
|
||||||
.point-1-x = 點 1 X
|
.point-1-x = 點 1 X
|
||||||
.point-1-y = 點 1 Y
|
.point-1-y = 點 1 Y
|
||||||
.point-1-z = 點 1 Z
|
.point-1-z = 點 1 Z
|
||||||
|
|
|
||||||
|
|
@ -18,5 +18,10 @@
|
||||||
"repo": "KevinGriffin-new/opencad-landsurvey-plugin",
|
"repo": "KevinGriffin-new/opencad-landsurvey-plugin",
|
||||||
"name": "Land Survey",
|
"name": "Land Survey",
|
||||||
"description": "Survey points, PNEZD & LandXML import, TIN surfaces, earthwork volumes, COGO, and coordinate transforms (RTS / Helmert / resection)."
|
"description": "Survey points, PNEZD & LandXML import, TIN surfaces, earthwork volumes, COGO, and coordinate transforms (RTS / Helmert / resection)."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"repo": "piyton/ocs-mcp-bridge",
|
||||||
|
"name": "MCP Bridge",
|
||||||
|
"description": "Read/write bridge for external tools: serves the live selection and accepts draw/remove requests over a local socket (MCP servers, scripts)."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,9 @@ const WEB_ALIAS_KEY: &str = "opencadstudio.aliases";
|
||||||
/// Version of the shipped default alias table. Bump this whenever the embedded
|
/// Version of the shipped default alias table. Bump this whenever the embedded
|
||||||
/// defaults add aliases or change a default target, so existing profiles can be
|
/// defaults add aliases or change a default target, so existing profiles can be
|
||||||
/// migrated forward (see `introduced_at` / `migrate_aliases`). Version 1 shipped
|
/// migrated forward (see `introduced_at` / `migrate_aliases`). Version 1 shipped
|
||||||
/// the pre-REDRAW table; version 2 introduced the REDRAW-family aliases.
|
/// the pre-REDRAW table; version 2 introduced the REDRAW-family aliases; version 3
|
||||||
const DEFAULT_ALIASES_VERSION: u32 = 2;
|
/// introduced HB (HATCHTOBACK).
|
||||||
|
const DEFAULT_ALIASES_VERSION: u32 = 3;
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
const WEB_ALIAS_VERSION_KEY: &str = "opencadstudio.aliases.version";
|
const WEB_ALIAS_VERSION_KEY: &str = "opencadstudio.aliases.version";
|
||||||
|
|
@ -91,6 +92,7 @@ fn introduced_at(version: u32) -> &'static [(&'static str, &'static str)] {
|
||||||
("RE", "REGEN"),
|
("RE", "REGEN"),
|
||||||
("REA", "REGENALL"),
|
("REA", "REGENALL"),
|
||||||
],
|
],
|
||||||
|
3 => &[("HB", "HATCHTOBACK")],
|
||||||
_ => &[],
|
_ => &[],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -330,17 +332,18 @@ mod tests {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An existing profile that predates the REDRAW aliases gains exactly the
|
/// An existing profile that predates the REDRAW and HB aliases gains exactly the
|
||||||
/// new defaults, with every pre-existing mapping kept.
|
/// new defaults, with every pre-existing mapping kept.
|
||||||
#[test]
|
#[test]
|
||||||
fn migration_adds_new_defaults_preserving_existing() {
|
fn migration_adds_new_defaults_preserving_existing() {
|
||||||
let mut stored = map(&[("L", "LINE"), ("Z", "ZOOM"), ("HI", "HIDE")]);
|
let mut stored = map(&[("L", "LINE"), ("Z", "ZOOM"), ("HI", "HIDE")]);
|
||||||
let changed = migrate_aliases(&mut stored, 0);
|
let changed = migrate_aliases(&mut stored, 0);
|
||||||
assert!(changed, "migrating from v0 to v2 must add defaults");
|
assert!(changed, "migrating from v0 to v3 must add defaults");
|
||||||
assert_eq!(stored.get("R").map(String::as_str), Some("REDRAW"));
|
assert_eq!(stored.get("R").map(String::as_str), Some("REDRAW"));
|
||||||
assert_eq!(stored.get("RA").map(String::as_str), Some("REDRAWALL"));
|
assert_eq!(stored.get("RA").map(String::as_str), Some("REDRAWALL"));
|
||||||
assert_eq!(stored.get("RE").map(String::as_str), Some("REGEN"));
|
assert_eq!(stored.get("RE").map(String::as_str), Some("REGEN"));
|
||||||
assert_eq!(stored.get("REA").map(String::as_str), Some("REGENALL"));
|
assert_eq!(stored.get("REA").map(String::as_str), Some("REGENALL"));
|
||||||
|
assert_eq!(stored.get("HB").map(String::as_str), Some("HATCHTOBACK"));
|
||||||
// Existing entries must survive untouched.
|
// Existing entries must survive untouched.
|
||||||
assert_eq!(stored.get("L").map(String::as_str), Some("LINE"));
|
assert_eq!(stored.get("L").map(String::as_str), Some("LINE"));
|
||||||
assert_eq!(stored.get("Z").map(String::as_str), Some("ZOOM"));
|
assert_eq!(stored.get("Z").map(String::as_str), Some("ZOOM"));
|
||||||
|
|
@ -375,10 +378,10 @@ mod tests {
|
||||||
assert_eq!(stored.get("L").map(String::as_str), Some("LINE"));
|
assert_eq!(stored.get("L").map(String::as_str), Some("LINE"));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Migrating a mid-range profile (one that saw v1) only pulls in v2's
|
/// Migrating a mid-range profile (one that saw v1) pulls in v2's and v3's
|
||||||
/// additions, not hypothetical earlier ones.
|
/// additions, not hypothetical earlier ones.
|
||||||
#[test]
|
#[test]
|
||||||
fn migration_from_version_one_adds_v2_aliases() {
|
fn migration_from_version_one_adds_v2_and_v3_aliases() {
|
||||||
let mut stored = map(&[("L", "LINE")]);
|
let mut stored = map(&[("L", "LINE")]);
|
||||||
let changed = migrate_aliases(&mut stored, 1);
|
let changed = migrate_aliases(&mut stored, 1);
|
||||||
assert!(changed);
|
assert!(changed);
|
||||||
|
|
@ -386,5 +389,16 @@ mod tests {
|
||||||
assert_eq!(stored.get("RA").map(String::as_str), Some("REDRAWALL"));
|
assert_eq!(stored.get("RA").map(String::as_str), Some("REDRAWALL"));
|
||||||
assert_eq!(stored.get("RE").map(String::as_str), Some("REGEN"));
|
assert_eq!(stored.get("RE").map(String::as_str), Some("REGEN"));
|
||||||
assert_eq!(stored.get("REA").map(String::as_str), Some("REGENALL"));
|
assert_eq!(stored.get("REA").map(String::as_str), Some("REGENALL"));
|
||||||
|
assert_eq!(stored.get("HB").map(String::as_str), Some("HATCHTOBACK"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Migrating a profile that saw v2 pulls in v3's HB addition only.
|
||||||
|
#[test]
|
||||||
|
fn migration_from_version_two_adds_v3_aliases() {
|
||||||
|
let mut stored = map(&[("L", "LINE")]);
|
||||||
|
let changed = migrate_aliases(&mut stored, 2);
|
||||||
|
assert!(changed);
|
||||||
|
assert_eq!(stored.get("R"), None);
|
||||||
|
assert_eq!(stored.get("HB").map(String::as_str), Some("HATCHTOBACK"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -193,6 +193,17 @@ fn entity_json(e: &acadrust::EntityType) -> Value {
|
||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
/// Handle one JSON request line and return the JSON response.
|
/// Handle one JSON request line and return the JSON response.
|
||||||
pub(crate) fn automation_op(&mut self, line: &str) -> Value {
|
pub(crate) fn automation_op(&mut self, line: &str) -> Value {
|
||||||
|
let res = self.automation_op_inner(line);
|
||||||
|
// Most automation ops mutate `Scene::selected` directly rather than
|
||||||
|
// going through `update()` (`select` calls `deselect_all` /
|
||||||
|
// `select_entity`, and `run` can erase the selected entities), so the
|
||||||
|
// selection check has to run on this path too.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
self.notify_plugins_selection_changed();
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn automation_op_inner(&mut self, line: &str) -> Value {
|
||||||
let req: Value = match serde_json::from_str(line) {
|
let req: Value = match serde_json::from_str(line) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => return err(format!("invalid JSON: {e}")),
|
Err(e) => return err(format!("invalid JSON: {e}")),
|
||||||
|
|
@ -201,6 +212,7 @@ impl OpenCADStudio {
|
||||||
"new" => {
|
"new" => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
self.tabs[i].scene.document = acadrust::CadDocument::new();
|
self.tabs[i].scene.document = acadrust::CadDocument::new();
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
self.tabs[i].current_path = None;
|
self.tabs[i].current_path = None;
|
||||||
// The headless session starts on the welcome (Start) tab, which
|
// The headless session starts on the welcome (Start) tab, which
|
||||||
// blocks drawing commands; turn it into a real drawing.
|
// blocks drawing commands; turn it into a real drawing.
|
||||||
|
|
@ -224,6 +236,7 @@ impl OpenCADStudio {
|
||||||
Ok(doc) => {
|
Ok(doc) => {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
self.tabs[i].scene.document = doc;
|
self.tabs[i].scene.document = doc;
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
crate::app::style_ops::ensure_standard_styles(
|
crate::app::style_ops::ensure_standard_styles(
|
||||||
&mut self.tabs[i].scene.document,
|
&mut self.tabs[i].scene.document,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -581,6 +581,36 @@ impl OpenCADStudio {
|
||||||
let _ = self.apply_cmd_result(r);
|
let _ = self.apply_cmd_result(r);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let accepts_points = self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|command| command.entity_pick_accepts_points());
|
||||||
|
if accepts_points {
|
||||||
|
if let Some((coord, kind)) = super::helpers::parse_coord(token) {
|
||||||
|
let ucs = self.tabs[i].active_ucs.clone();
|
||||||
|
let wcs = match (
|
||||||
|
matches!(kind, super::helpers::CoordKind::Relative),
|
||||||
|
self.last_point,
|
||||||
|
) {
|
||||||
|
(true, Some(base)) => {
|
||||||
|
base + match &ucs {
|
||||||
|
Some(value) => super::helpers::ucs_rotate_vec(coord, value),
|
||||||
|
None => coord,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => match &ucs {
|
||||||
|
Some(value) => super::helpers::ucs_to_wcs(coord, value),
|
||||||
|
None => coord,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if self.command_point_allowed(i, wcs) {
|
||||||
|
self.last_point = Some(wcs);
|
||||||
|
self.push_ucs_to_cmd(i);
|
||||||
|
let _ = self.feed_command(StepInput::Point(wcs));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Ok(v) = u64::from_str_radix(token.trim_start_matches("0x"), 16) {
|
if let Ok(v) = u64::from_str_radix(token.trim_start_matches("0x"), 16) {
|
||||||
let handle = Handle::new(v);
|
let handle = Handle::new(v);
|
||||||
let pt = self.tabs[i]
|
let pt = self.tabs[i]
|
||||||
|
|
@ -683,8 +713,12 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let was_active = self.tabs[self.active_tab].active_cmd.is_some();
|
let was_active = self.tabs[self.active_tab].active_cmd.is_some();
|
||||||
let preserve_selection =
|
let preserve_selection = matches!(
|
||||||
matches!(result, CmdResult::Relaunch(..) | CmdResult::Dispatch(..));
|
&result,
|
||||||
|
CmdResult::Relaunch(..)
|
||||||
|
| CmdResult::Dispatch(..)
|
||||||
|
| CmdResult::SolidSubtract { .. }
|
||||||
|
);
|
||||||
let task = self.apply_cmd_result_inner(result);
|
let task = self.apply_cmd_result_inner(result);
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let preview_hidden = self.tabs[i]
|
let preview_hidden = self.tabs[i]
|
||||||
|
|
@ -711,6 +745,7 @@ impl OpenCADStudio {
|
||||||
// so CLAYER / CECOLOR / … are left unchanged (#239). No-op otherwise.
|
// so CLAYER / CECOLOR / … are left unchanged (#239). No-op otherwise.
|
||||||
if was_active && self.tabs[i].active_cmd.is_none() {
|
if was_active && self.tabs[i].active_cmd.is_none() {
|
||||||
self.tabs[i].scene.set_hover_highlight(None);
|
self.tabs[i].scene.set_hover_highlight(None);
|
||||||
|
self.command_line.set_step_options(Vec::new());
|
||||||
self.restore_add_selected_defaults();
|
self.restore_add_selected_defaults();
|
||||||
}
|
}
|
||||||
task
|
task
|
||||||
|
|
@ -729,6 +764,7 @@ impl OpenCADStudio {
|
||||||
.and_then(|c| c.attedit_pending_handle());
|
.and_then(|c| c.attedit_pending_handle());
|
||||||
if let Some(ins_handle) = attedit_handle {
|
if let Some(ins_handle) = attedit_handle {
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.command_line.set_step_options(Vec::new());
|
||||||
self.open_attribute_editor(ins_handle);
|
self.open_attribute_editor(ins_handle);
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
|
@ -736,6 +772,12 @@ impl OpenCADStudio {
|
||||||
if let Some(p) = prompt {
|
if let Some(p) = prompt {
|
||||||
self.command_line.push_info(&p);
|
self.command_line.push_info(&p);
|
||||||
}
|
}
|
||||||
|
let opts = self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.map(|c| c.options())
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.command_line.set_step_options(opts);
|
||||||
if !self.tabs[i]
|
if !self.tabs[i]
|
||||||
.active_cmd
|
.active_cmd
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -797,7 +839,26 @@ impl OpenCADStudio {
|
||||||
// the structure snapshot fallback.
|
// the structure snapshot fallback.
|
||||||
let delta_safe = self.delta_add_safe(i, &entity);
|
let delta_safe = self.delta_add_safe(i, &entity);
|
||||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||||
self.commit_entity(entity);
|
let is_associative_dimension = matches!(
|
||||||
|
entity,
|
||||||
|
acadrust::EntityType::Dimension(
|
||||||
|
acadrust::entities::Dimension::Linear(_)
|
||||||
|
| acadrust::entities::Dimension::Aligned(_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let association_enabled =
|
||||||
|
self.tabs[i].scene.document.header.dimension_associativity == 2;
|
||||||
|
let committed = self.commit_entity_handle(entity);
|
||||||
|
if is_associative_dimension && association_enabled {
|
||||||
|
if let Some(handle) = committed {
|
||||||
|
let sources = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.infer_dimension_sources(handle);
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.attach_dimension_association(handle, sources.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||||
if let Some(p) = prompt {
|
if let Some(p) = prompt {
|
||||||
|
|
@ -1181,13 +1242,155 @@ impl OpenCADStudio {
|
||||||
// and ribbon dropdowns now, not on the next reopen (#407).
|
// and ribbon dropdowns now, not on the next reopen (#407).
|
||||||
self.refresh_layer_panel();
|
self.refresh_layer_panel();
|
||||||
}
|
}
|
||||||
|
let insert_block_name = match &entity {
|
||||||
|
acadrust::EntityType::Insert(ins) => Some(ins.block_name.clone()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
// Record where this draw ended so ARC_CONT can continue from it
|
// Record where this draw ended so ARC_CONT can continue from it
|
||||||
// (before `entity` is moved into commit_entity).
|
// (before `entity` is moved into commit_entity).
|
||||||
self.update_cont_anchor(&entity);
|
self.update_cont_anchor(&entity);
|
||||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||||
let delta_safe = self.delta_add_safe(i, &entity);
|
let delta_safe = self.delta_add_safe(i, &entity);
|
||||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||||
self.commit_entity(entity);
|
let is_associative_dimension = matches!(
|
||||||
|
entity,
|
||||||
|
acadrust::EntityType::Dimension(
|
||||||
|
acadrust::entities::Dimension::Linear(_)
|
||||||
|
| acadrust::entities::Dimension::Aligned(_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let association_enabled =
|
||||||
|
self.tabs[i].scene.document.header.dimension_associativity == 2;
|
||||||
|
let committed = self.commit_entity_handle(entity);
|
||||||
|
if is_associative_dimension && association_enabled {
|
||||||
|
if let Some(handle) = committed {
|
||||||
|
let sources = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.infer_dimension_sources(handle);
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.attach_dimension_association(handle, sources.to_vec());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
if let Some(name) = insert_block_name {
|
||||||
|
self.record_block_insert(&name);
|
||||||
|
}
|
||||||
|
if let Some(pd) = pending {
|
||||||
|
self.commit_undo_delta(i, pd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmdResult::CommitDimension {
|
||||||
|
mut entity,
|
||||||
|
association,
|
||||||
|
} => {
|
||||||
|
let label = self.history_label_from_active_cmd(i, "DIMENSION");
|
||||||
|
let association_mode = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.dimension_associativity;
|
||||||
|
let single_source_dimension = matches!(
|
||||||
|
&entity,
|
||||||
|
acadrust::EntityType::Dimension(
|
||||||
|
acadrust::entities::Dimension::Ordinate(_)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let pending = if association_mode == 0 {
|
||||||
|
let layer = self.tabs[i].active_layer.clone();
|
||||||
|
if layer != "0" || entity.as_entity().layer().is_empty() {
|
||||||
|
entity.as_entity_mut().set_layer(layer);
|
||||||
|
}
|
||||||
|
crate::scene::view::dispatch::apply_color(
|
||||||
|
&mut entity,
|
||||||
|
self.ribbon.active_color,
|
||||||
|
);
|
||||||
|
crate::scene::view::dispatch::apply_common_prop(
|
||||||
|
&mut entity,
|
||||||
|
"linetype",
|
||||||
|
&self.ribbon.active_linetype.clone(),
|
||||||
|
);
|
||||||
|
crate::scene::view::dispatch::apply_line_weight(
|
||||||
|
&mut entity,
|
||||||
|
self.ribbon.active_lineweight,
|
||||||
|
);
|
||||||
|
let celtscale = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.current_entity_linetype_scale;
|
||||||
|
if (celtscale - 1.0).abs() > 1e-9 && celtscale.abs() > 1e-9 {
|
||||||
|
entity.common_mut().linetype_scale = celtscale;
|
||||||
|
}
|
||||||
|
crate::scene::creation_style::apply_current_creation_styles(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
&mut entity,
|
||||||
|
);
|
||||||
|
let pieces = crate::modules::draw::modify::explode::explode_entity(
|
||||||
|
&entity,
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
);
|
||||||
|
let delta_safe = pieces
|
||||||
|
.iter()
|
||||||
|
.all(|piece| self.delta_add_safe(i, piece));
|
||||||
|
let pending = self.begin_undo(i, label, pieces.len(), delta_safe);
|
||||||
|
for piece in pieces {
|
||||||
|
self.tabs[i].scene.add_entity(piece);
|
||||||
|
}
|
||||||
|
pending
|
||||||
|
} else {
|
||||||
|
let delta_safe = self.delta_add_safe(i, &entity);
|
||||||
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||||
|
if let Some(handle) = self.commit_entity_handle(entity) {
|
||||||
|
if association_mode == 2 {
|
||||||
|
let mut changes = vec![
|
||||||
|
(handle, crate::scene::ChangeKind::Modified),
|
||||||
|
];
|
||||||
|
match association {
|
||||||
|
crate::command::DimensionAssociationInput::Infer(source) => {
|
||||||
|
let sources: Vec<_> = source.map_or_else(
|
||||||
|
|| {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.infer_dimension_sources(handle)
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
|
},
|
||||||
|
|source| {
|
||||||
|
if single_source_dimension {
|
||||||
|
vec![Some(source)]
|
||||||
|
} else {
|
||||||
|
vec![Some(source), Some(source)]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.attach_dimension_association(handle, sources.clone());
|
||||||
|
changes.extend(sources.into_iter().flatten().map(|source| {
|
||||||
|
(source, crate::scene::ChangeKind::Modified)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
crate::command::DimensionAssociationInput::Explicit(sources) => {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.attach_dimension_association_sources(handle, sources.clone());
|
||||||
|
changes.extend(sources.into_iter().flatten().map(|source| {
|
||||||
|
(source.handle, crate::scene::ChangeKind::Modified)
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changes.sort_by_key(|(handle, _)| handle.value());
|
||||||
|
changes.dedup_by_key(|(handle, _)| handle.value());
|
||||||
|
self.tabs[i].scene.bump_entities(&changes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pending
|
||||||
|
};
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
|
|
@ -1204,21 +1407,42 @@ impl OpenCADStudio {
|
||||||
} => {
|
} => {
|
||||||
let label = self.history_label_from_active_cmd(i, "SOLID");
|
let label = self.history_label_from_active_cmd(i, "SOLID");
|
||||||
let pending = self.begin_undo(i, label, 1, true);
|
let pending = self.begin_undo(i, label, 1, true);
|
||||||
self.add_solid_model(entity, *solid, history);
|
let handle = self.add_solid_model(entity, *solid, history);
|
||||||
self.tabs[i].dirty = true;
|
if !handle.is_null() {
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
if let Some(pd) = pending {
|
||||||
|
self.commit_undo_delta(i, pd);
|
||||||
|
}
|
||||||
|
}
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
self.tabs[i].snap_result = None;
|
self.tabs[i].snap_result = None;
|
||||||
self.restore_pre_cmd_tangent();
|
self.restore_pre_cmd_tangent();
|
||||||
if let Some(pd) = pending {
|
|
||||||
self.commit_undo_delta(i, pd);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
CmdResult::CommitAndEditText(entity) => {
|
CmdResult::CommitAndEditText(entity) => {
|
||||||
|
let annotative_mleader = matches!(
|
||||||
|
&entity,
|
||||||
|
acadrust::EntityType::MultiLeader(ml) if ml.enable_annotation_scale
|
||||||
|
);
|
||||||
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
let label = self.history_label_from_active_cmd(i, "ENTITY");
|
||||||
let delta_safe = self.delta_add_safe(i, &entity);
|
let delta_safe = self.delta_add_safe(i, &entity) && !annotative_mleader;
|
||||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||||
let handle = self.commit_entity_handle(entity);
|
let handle = self.commit_entity_handle(entity);
|
||||||
|
|
||||||
|
if annotative_mleader {
|
||||||
|
let scale_handle = self.tabs[i].scene.creation_annotation_scale_handle();
|
||||||
|
if let (Some(handle), Some(scale_handle)) = (handle, scale_handle) {
|
||||||
|
crate::scene::annotative::create_annotation_context(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
scale_handle,
|
||||||
|
);
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
|
|
@ -1717,6 +1941,10 @@ impl OpenCADStudio {
|
||||||
.as_mut()
|
.as_mut()
|
||||||
.and_then(|c| c.attreq_take_insert());
|
.and_then(|c| c.attreq_take_insert());
|
||||||
if let Some(entity) = entity {
|
if let Some(entity) = entity {
|
||||||
|
let insert_name = match &entity {
|
||||||
|
acadrust::EntityType::Insert(ins) => Some(ins.block_name.clone()),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
let label = self.history_label_from_active_cmd(i, "INSERT");
|
let label = self.history_label_from_active_cmd(i, "INSERT");
|
||||||
let delta_safe = self.delta_add_safe(i, &entity);
|
let delta_safe = self.delta_add_safe(i, &entity);
|
||||||
let pending = self.begin_undo(i, label, 1, delta_safe);
|
let pending = self.begin_undo(i, label, 1, delta_safe);
|
||||||
|
|
@ -1725,15 +1953,21 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
self.tabs[i].snap_result = None;
|
self.tabs[i].snap_result = None;
|
||||||
|
if let Some(n) = insert_name {
|
||||||
|
self.record_block_insert(&n);
|
||||||
|
}
|
||||||
self.restore_pre_cmd_tangent();
|
self.restore_pre_cmd_tangent();
|
||||||
if let Some(pd) = pending {
|
if let Some(pd) = pending {
|
||||||
self.commit_undo_delta(i, pd);
|
self.commit_undo_delta(i, pd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Inject attdefs so the command enters attr-filling mode.
|
let completed = self.tabs[i]
|
||||||
if let Some(cmd) = &mut self.tabs[i].active_cmd {
|
.active_cmd
|
||||||
cmd.attreq_set_attdefs(attdefs);
|
.as_mut()
|
||||||
|
.and_then(|cmd| cmd.attreq_set_attdefs(attdefs));
|
||||||
|
if let Some(entity) = completed {
|
||||||
|
return self.apply_cmd_result(CmdResult::CommitAndExit(entity));
|
||||||
}
|
}
|
||||||
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
let prompt = self.tabs[i].active_cmd.as_ref().map(|c| c.prompt());
|
||||||
if let Some(p) = prompt {
|
if let Some(p) = prompt {
|
||||||
|
|
@ -2917,7 +3151,6 @@ impl OpenCADStudio {
|
||||||
stretched |= mv(&mut d.definition_point);
|
stretched |= mv(&mut d.definition_point);
|
||||||
}
|
}
|
||||||
Dimension::Ordinate(d) => {
|
Dimension::Ordinate(d) => {
|
||||||
stretched |= mv(&mut d.definition_point);
|
|
||||||
stretched |= mv(&mut d.feature_location);
|
stretched |= mv(&mut d.feature_location);
|
||||||
stretched |= mv(&mut d.leader_endpoint);
|
stretched |= mv(&mut d.leader_endpoint);
|
||||||
}
|
}
|
||||||
|
|
@ -2998,80 +3231,32 @@ impl OpenCADStudio {
|
||||||
self.commit_undo_delta(i, pending);
|
self.commit_undo_delta(i, pending);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ── Solid3D creation (BOX / SPHERE / CYLINDER) ────────────────
|
|
||||||
CmdResult::CommitSolid3D { mesh_fn } => {
|
|
||||||
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
|
||||||
let pending = self.begin_undo(i, "SOLID3D", 1, true);
|
|
||||||
let entity = empty_solid3d();
|
|
||||||
let handle = self.tabs[i].scene.add_entity(entity);
|
|
||||||
if !handle.is_null() {
|
|
||||||
let name = format!("{}", handle.value());
|
|
||||||
let color = [0.6f32, 0.6, 0.8, 1.0]; // default colour; command embedded it
|
|
||||||
let _ = color; // color is captured inside mesh_fn
|
|
||||||
if let Some(mesh) = mesh_fn(name) {
|
|
||||||
let set = crate::scene::MeshLodSet::from_single(mesh);
|
|
||||||
if let Some(acadrust::EntityType::Solid3D(entity)) =
|
|
||||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
|
||||||
{
|
|
||||||
let center = set.metrics.centroid;
|
|
||||||
entity.point_of_reference = acadrust::types::Vector3::new(
|
|
||||||
center[0], center[1], center[2],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.tabs[i].scene.meshes.insert(handle, set);
|
|
||||||
}
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.command_line.push_output(crate::t!("Solid created.").as_ref());
|
|
||||||
}
|
|
||||||
if let Some(pd) = pending {
|
|
||||||
self.commit_undo_delta(i, pd);
|
|
||||||
}
|
|
||||||
self.tabs[i].active_cmd = None;
|
|
||||||
self.tabs[i].snap_result = None;
|
|
||||||
self.tabs[i].scene.clear_preview_wire();
|
|
||||||
self.restore_pre_cmd_tangent();
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── EXTRUDE ────────────────────────────────────────────────────
|
// ── EXTRUDE ────────────────────────────────────────────────────
|
||||||
CmdResult::ExtrudeEntity {
|
CmdResult::ExtrudeEntity {
|
||||||
handle,
|
handle,
|
||||||
height,
|
height,
|
||||||
color,
|
color: _,
|
||||||
} => {
|
} => {
|
||||||
if self.reject_locked_edit(i, handle) {
|
if self.reject_locked_edit(i, handle) {
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||||
use crate::scene::model::{solid_model, sweep_model};
|
use crate::scene::model::sweep_model;
|
||||||
|
|
||||||
let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned();
|
let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned();
|
||||||
if let Some(entity) = entity_opt {
|
if let Some(entity) = entity_opt {
|
||||||
// The kernel sweeps the profile into analytic surfaces —
|
if let Some(solid) = sweep_model::extruded(&entity, height) {
|
||||||
// a straight run becomes a plane and an arc a cylinder —
|
let history = crate::scene::model::solid_history::brep_op(&solid);
|
||||||
// so the solid saves as exact ACIS rather than facets.
|
|
||||||
let result = sweep_model::extruded(&entity, height as f64)
|
|
||||||
.and_then(|body| Some((solid_model::mesh_from_solid(&body, color)?, body)));
|
|
||||||
if let Some((mesh, solid)) = result {
|
|
||||||
let history = crate::scene::model::solid_history::extrusion_op(
|
|
||||||
&entity,
|
|
||||||
height as f64,
|
|
||||||
);
|
|
||||||
let pending = self.begin_undo(i, "EXTRUDE", 1, true);
|
let pending = self.begin_undo(i, "EXTRUDE", 1, true);
|
||||||
let mut s3d = empty_solid3d();
|
let created = self.add_solid_model(empty_solid3d(), solid, history);
|
||||||
if let acadrust::EntityType::Solid3D(inner) = &mut s3d {
|
if !created.is_null() {
|
||||||
inner.wires = solid_model::edge_wires(&solid);
|
self.tabs[i].dirty = true;
|
||||||
}
|
self.command_line
|
||||||
let new_handle = self.tabs[i].scene.add_entity(s3d);
|
.push_output(crate::t!("EXTRUDE: solid created.").as_ref());
|
||||||
self.tabs[i]
|
if let Some(pd) = pending {
|
||||||
.scene
|
self.commit_undo_delta(i, pd);
|
||||||
.create_solid_history(new_handle, history);
|
}
|
||||||
self.tabs[i].scene.register_solid_model(new_handle, solid);
|
|
||||||
let _ = mesh;
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.command_line.push_output(crate::t!("EXTRUDE: solid created.").as_ref());
|
|
||||||
if let Some(pd) = pending {
|
|
||||||
self.commit_undo_delta(i, pd);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.command_line.push_error(crate::t!("EXTRUDE: could not build profile. Select a closed 2D entity (Circle, LwPolyline, etc.).").as_ref());
|
self.command_line.push_error(crate::t!("EXTRUDE: could not build profile. Select a closed 2D entity (Circle, LwPolyline, etc.).").as_ref());
|
||||||
|
|
@ -3085,26 +3270,80 @@ impl OpenCADStudio {
|
||||||
self.restore_pre_cmd_tangent();
|
self.restore_pre_cmd_tangent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CmdResult::PresspullEntity {
|
||||||
|
handle,
|
||||||
|
pick,
|
||||||
|
distance,
|
||||||
|
drag,
|
||||||
|
color,
|
||||||
|
} => {
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
Some(acadrust::EntityType::Solid3D(_))
|
||||||
|
) {
|
||||||
|
let task = self.solid_face_presspull(handle, pick, distance, drag);
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
if self.reject_locked_edit(i, handle) {
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||||
|
use crate::scene::model::{solid_history, sweep_model};
|
||||||
|
|
||||||
|
let result = self.tabs[i].scene.document.get_entity(handle).and_then(|entity| {
|
||||||
|
let distance = match drag {
|
||||||
|
Some(point) => sweep_model::projected_drag(entity, pick, point)?,
|
||||||
|
None => distance,
|
||||||
|
};
|
||||||
|
sweep_model::extruded(entity, distance)
|
||||||
|
});
|
||||||
|
if let Some(solid) = result {
|
||||||
|
let pending = self.begin_undo(i, "PRESSPULL", 1, true);
|
||||||
|
let history = solid_history::brep_op(&solid);
|
||||||
|
let created = self.add_solid_model(empty_solid3d(), solid, history);
|
||||||
|
let _ = color;
|
||||||
|
if !created.is_null() {
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.command_line
|
||||||
|
.push_output(crate::t!("PRESSPULL: solid created.").as_ref());
|
||||||
|
if let Some(delta) = pending {
|
||||||
|
self.commit_undo_delta(i, delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("PRESSPULL: select a closed profile or planar solid face.")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
}
|
||||||
|
|
||||||
// ── REVOLVE ────────────────────────────────────────────────────
|
// ── REVOLVE ────────────────────────────────────────────────────
|
||||||
CmdResult::RevolveEntity {
|
CmdResult::RevolveEntity {
|
||||||
handle,
|
handle,
|
||||||
axis_start,
|
axis_start,
|
||||||
axis_end,
|
axis_end,
|
||||||
angle_deg,
|
angle_deg,
|
||||||
color,
|
color: _,
|
||||||
} => {
|
} => {
|
||||||
if self.reject_locked_edit(i, handle) {
|
if self.reject_locked_edit(i, handle) {
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||||
use crate::scene::model::{solid_model, sweep_model};
|
use crate::scene::model::sweep_model;
|
||||||
|
|
||||||
let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned();
|
let entity_opt = self.tabs[i].scene.document.get_entity(handle).cloned();
|
||||||
if let Some(entity) = entity_opt {
|
if let Some(entity) = entity_opt {
|
||||||
// A line turned about the axis sweeps into a plane, a
|
|
||||||
// cylinder or a cone, and an arc into a sphere or a
|
|
||||||
// torus, so a revolved solid keeps exact geometry too.
|
|
||||||
let result = sweep_model::revolved(
|
let result = sweep_model::revolved(
|
||||||
&entity,
|
&entity,
|
||||||
[
|
[
|
||||||
|
|
@ -3114,31 +3353,19 @@ impl OpenCADStudio {
|
||||||
],
|
],
|
||||||
[axis_end.x as f64, axis_end.y as f64, axis_end.z as f64],
|
[axis_end.x as f64, axis_end.y as f64, axis_end.z as f64],
|
||||||
(angle_deg as f64).to_radians(),
|
(angle_deg as f64).to_radians(),
|
||||||
)
|
);
|
||||||
.and_then(|body| Some((solid_model::mesh_from_solid(&body, color)?, body)));
|
if let Some(solid) = result {
|
||||||
if let Some((mesh, solid)) = result {
|
let history = crate::scene::model::solid_history::brep_op(&solid);
|
||||||
let history = crate::scene::model::solid_history::revolve_op(
|
|
||||||
&entity,
|
|
||||||
axis_start.to_array(),
|
|
||||||
axis_end.to_array(),
|
|
||||||
(angle_deg as f64).to_radians(),
|
|
||||||
);
|
|
||||||
let pending = self.begin_undo(i, "REVOLVE", 1, true);
|
let pending = self.begin_undo(i, "REVOLVE", 1, true);
|
||||||
let mut s3d = empty_solid3d();
|
let created = self.add_solid_model(empty_solid3d(), solid, history);
|
||||||
if let acadrust::EntityType::Solid3D(inner) = &mut s3d {
|
if !created.is_null() {
|
||||||
inner.wires = solid_model::edge_wires(&solid);
|
self.tabs[i].dirty = true;
|
||||||
}
|
self.command_line.push_output(
|
||||||
let new_handle = self.tabs[i].scene.add_entity(s3d);
|
crate::tf!("REVOLVE: solid created ({:.0}°).", angle_deg).as_ref(),
|
||||||
self.tabs[i]
|
);
|
||||||
.scene
|
if let Some(pd) = pending {
|
||||||
.create_solid_history(new_handle, history);
|
self.commit_undo_delta(i, pd);
|
||||||
self.tabs[i].scene.register_solid_model(new_handle, solid);
|
}
|
||||||
let _ = mesh;
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.command_line
|
|
||||||
.push_output(crate::tf!("REVOLVE: solid created ({:.0}°).", angle_deg).as_ref());
|
|
||||||
if let Some(pd) = pending {
|
|
||||||
self.commit_undo_delta(i, pd);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.command_line
|
self.command_line
|
||||||
|
|
@ -3157,7 +3384,7 @@ impl OpenCADStudio {
|
||||||
CmdResult::SweepEntity {
|
CmdResult::SweepEntity {
|
||||||
profile_handle,
|
profile_handle,
|
||||||
path_handle,
|
path_handle,
|
||||||
color,
|
color: _,
|
||||||
} => {
|
} => {
|
||||||
if self.reject_locked_edit(i, profile_handle)
|
if self.reject_locked_edit(i, profile_handle)
|
||||||
|| self.reject_locked_edit(i, path_handle)
|
|| self.reject_locked_edit(i, path_handle)
|
||||||
|
|
@ -3166,7 +3393,7 @@ impl OpenCADStudio {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||||
use crate::scene::model::sweep_model;
|
use crate::scene::model::{solid_history, sweep_model};
|
||||||
|
|
||||||
let profile_ent = self.tabs[i]
|
let profile_ent = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
|
|
@ -3174,39 +3401,21 @@ impl OpenCADStudio {
|
||||||
.get_entity(profile_handle)
|
.get_entity(profile_handle)
|
||||||
.cloned();
|
.cloned();
|
||||||
let path_ent = self.tabs[i].scene.document.get_entity(path_handle).cloned();
|
let path_ent = self.tabs[i].scene.document.get_entity(path_handle).cloned();
|
||||||
let history = profile_ent
|
|
||||||
.as_ref()
|
|
||||||
.zip(path_ent.as_ref())
|
|
||||||
.map(|(profile, path)| {
|
|
||||||
crate::scene::model::solid_history::sweep_op(profile, path)
|
|
||||||
});
|
|
||||||
let result = profile_ent
|
let result = profile_ent
|
||||||
.zip(path_ent)
|
.zip(path_ent)
|
||||||
.and_then(|(profile, path)| sweep_model::swept(&profile, &path, color));
|
.and_then(|(profile, path)| sweep_model::swept(&profile, &path));
|
||||||
|
|
||||||
if let Some(mut set) = result {
|
if let Some(solid) = result {
|
||||||
let pending = self.begin_undo(i, "SWEEP", 1, true);
|
let pending = self.begin_undo(i, "SWEEP", 1, true);
|
||||||
let mut entity = empty_solid3d();
|
let history = solid_history::brep_op(&solid);
|
||||||
if let acadrust::EntityType::Solid3D(solid) = &mut entity {
|
let created = self.add_solid_model(empty_solid3d(), solid, history);
|
||||||
let center = set.metrics.centroid;
|
if !created.is_null() {
|
||||||
solid.point_of_reference = acadrust::types::Vector3::new(
|
self.tabs[i].dirty = true;
|
||||||
center[0], center[1], center[2],
|
self.command_line
|
||||||
);
|
.push_output(crate::t!("SWEEP: solid created.").as_ref());
|
||||||
}
|
if let Some(pd) = pending {
|
||||||
let new_handle = self.tabs[i].scene.add_entity(entity);
|
self.commit_undo_delta(i, pd);
|
||||||
if let Some(history) = history {
|
}
|
||||||
self.tabs[i]
|
|
||||||
.scene
|
|
||||||
.create_solid_history(new_handle, history);
|
|
||||||
}
|
|
||||||
for mesh in &mut set.lods {
|
|
||||||
mesh.name = format!("{}", new_handle.value());
|
|
||||||
}
|
|
||||||
self.tabs[i].scene.meshes.insert(new_handle, set);
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.command_line.push_output(crate::t!("SWEEP: solid created.").as_ref());
|
|
||||||
if let Some(pd) = pending {
|
|
||||||
self.commit_undo_delta(i, pd);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.command_line.push_error(crate::t!("SWEEP: could not sweep the profile along the path.").as_ref());
|
self.command_line.push_error(crate::t!("SWEEP: could not sweep the profile along the path.").as_ref());
|
||||||
|
|
@ -3218,7 +3427,7 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── LOFT ──────────────────────────────────────────────────────
|
// ── LOFT ──────────────────────────────────────────────────────
|
||||||
CmdResult::LoftEntities { handles, color } => {
|
CmdResult::LoftEntities { handles, color: _ } => {
|
||||||
if let Some(handle) = handles
|
if let Some(handle) = handles
|
||||||
.iter()
|
.iter()
|
||||||
.find(|handle| self.tabs[i].scene.is_layer_locked(**handle))
|
.find(|handle| self.tabs[i].scene.is_layer_locked(**handle))
|
||||||
|
|
@ -3229,34 +3438,23 @@ impl OpenCADStudio {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
use crate::modules::insert::solid3d_cmds::empty_solid3d;
|
||||||
use crate::scene::model::sweep_model;
|
use crate::scene::model::{solid_history, sweep_model};
|
||||||
|
|
||||||
let profiles: Vec<acadrust::EntityType> = handles
|
let profiles: Vec<acadrust::EntityType> = handles
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned())
|
.filter_map(|handle| self.tabs[i].scene.document.get_entity(*handle).cloned())
|
||||||
.collect();
|
.collect();
|
||||||
if let Some(mut set) = sweep_model::lofted(&profiles, color) {
|
if let Some(solid) = sweep_model::lofted(&profiles) {
|
||||||
let history = crate::scene::model::solid_history::loft_op(&profiles);
|
|
||||||
let pending = self.begin_undo(i, "LOFT", 1, true);
|
let pending = self.begin_undo(i, "LOFT", 1, true);
|
||||||
let mut entity = empty_solid3d();
|
let history = solid_history::brep_op(&solid);
|
||||||
if let acadrust::EntityType::Solid3D(solid) = &mut entity {
|
let created = self.add_solid_model(empty_solid3d(), solid, history);
|
||||||
let center = set.metrics.centroid;
|
if !created.is_null() {
|
||||||
solid.point_of_reference = acadrust::types::Vector3::new(
|
self.tabs[i].dirty = true;
|
||||||
center[0], center[1], center[2],
|
self.command_line
|
||||||
);
|
.push_output(crate::t!("LOFT: solid created.").as_ref());
|
||||||
}
|
if let Some(pd) = pending {
|
||||||
let new_handle = self.tabs[i].scene.add_entity(entity);
|
self.commit_undo_delta(i, pd);
|
||||||
self.tabs[i]
|
}
|
||||||
.scene
|
|
||||||
.create_solid_history(new_handle, history);
|
|
||||||
for mesh in &mut set.lods {
|
|
||||||
mesh.name = format!("{}", new_handle.value());
|
|
||||||
}
|
|
||||||
self.tabs[i].scene.meshes.insert(new_handle, set);
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.command_line.push_output(crate::t!("LOFT: solid created.").as_ref());
|
|
||||||
if let Some(pd) = pending {
|
|
||||||
self.commit_undo_delta(i, pd);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.command_line.push_error(crate::t!("LOFT: select at least two closed profiles.").as_ref());
|
self.command_line.push_error(crate::t!("LOFT: select at least two closed profiles.").as_ref());
|
||||||
|
|
@ -3267,6 +3465,29 @@ impl OpenCADStudio {
|
||||||
self.restore_pre_cmd_tangent();
|
self.restore_pre_cmd_tangent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CmdResult::SolidEdgeBlend {
|
||||||
|
handle,
|
||||||
|
pick,
|
||||||
|
value,
|
||||||
|
fillet,
|
||||||
|
} => {
|
||||||
|
let task = self.solid_edge_blend(handle, pick, value, fillet);
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
CmdResult::SolidSubtract { bases, cutters } => {
|
||||||
|
let task = self.solid_subtract(&bases, &cutters);
|
||||||
|
self.tabs[i].active_cmd = None;
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
CmdResult::HatcheditApply {
|
CmdResult::HatcheditApply {
|
||||||
handle,
|
handle,
|
||||||
name,
|
name,
|
||||||
|
|
@ -3495,10 +3716,24 @@ impl OpenCADStudio {
|
||||||
handle,
|
handle,
|
||||||
initial,
|
initial,
|
||||||
height,
|
height,
|
||||||
|
template,
|
||||||
} => {
|
} => {
|
||||||
self.tabs[i].active_cmd = None;
|
self.tabs[i].active_cmd = None;
|
||||||
self.tabs[i].snap_result = None;
|
self.tabs[i].snap_result = None;
|
||||||
self.open_mtext_editor(pos, handle, &initial, height);
|
self.open_mtext_editor(pos, handle, &initial, height, template.map(|m| *m));
|
||||||
|
}
|
||||||
|
CmdResult::SuspendForMTextInput {
|
||||||
|
pos,
|
||||||
|
initial,
|
||||||
|
height,
|
||||||
|
} => {
|
||||||
|
self.tabs[i].suspended_cmd = self.tabs[i].active_cmd.take();
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
self.command_mtext_input = true;
|
||||||
|
self.pending_command_editor_text = None;
|
||||||
|
self.open_mtext_editor(pos, None, &initial, height, None);
|
||||||
}
|
}
|
||||||
CmdResult::OpenTextEditor {
|
CmdResult::OpenTextEditor {
|
||||||
pos,
|
pos,
|
||||||
|
|
@ -3514,6 +3749,22 @@ impl OpenCADStudio {
|
||||||
&initial,
|
&initial,
|
||||||
height,
|
height,
|
||||||
super::text_inline::TextEntityField::Text,
|
super::text_inline::TextEntityField::Text,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
CmdResult::SuspendForTextInput { pos, entity } => {
|
||||||
|
self.tabs[i].suspended_cmd = self.tabs[i].active_cmd.take();
|
||||||
|
self.tabs[i].snap_result = None;
|
||||||
|
self.tabs[i].scene.clear_preview_wire();
|
||||||
|
self.restore_pre_cmd_tangent();
|
||||||
|
let height = entity.height;
|
||||||
|
self.open_text_inline(
|
||||||
|
pos,
|
||||||
|
None,
|
||||||
|
"",
|
||||||
|
height,
|
||||||
|
super::text_inline::TextEntityField::Text,
|
||||||
|
Some(entity),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
CmdResult::EditTextEntity { handle } => {
|
CmdResult::EditTextEntity { handle } => {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ impl OpenCADStudio {
|
||||||
.document
|
.document
|
||||||
.header
|
.header
|
||||||
.paper_space_insertion_base = pt;
|
.paper_space_insertion_base = pt;
|
||||||
|
self.tabs[i].scene.persist_current_layout_state();
|
||||||
} else {
|
} else {
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
|
|
@ -330,8 +331,16 @@ impl OpenCADStudio {
|
||||||
.push_error(crate::t!("No user-defined blocks found in this drawing.").as_ref());
|
.push_error(crate::t!("No user-defined blocks found in this drawing.").as_ref());
|
||||||
} else {
|
} else {
|
||||||
use crate::modules::insert::insert_block::InsertBlockCommand;
|
use crate::modules::insert::insert_block::InsertBlockCommand;
|
||||||
let cmd = InsertBlockCommand::new(blocks);
|
let ranked = self.ranked_block_names(&blocks);
|
||||||
|
let snapshot = self.block_usage_snapshot();
|
||||||
|
let cmd = InsertBlockCommand::new_with_usage(
|
||||||
|
ranked,
|
||||||
|
snapshot,
|
||||||
|
self.cliprompt_lines.clamp(0, 50) as u8,
|
||||||
|
);
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
|
let opts = cmd.options();
|
||||||
|
self.command_line.set_step_options(opts.clone());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -343,8 +352,16 @@ impl OpenCADStudio {
|
||||||
.push_error(crate::t!("No user-defined blocks found in this drawing.").as_ref());
|
.push_error(crate::t!("No user-defined blocks found in this drawing.").as_ref());
|
||||||
} else {
|
} else {
|
||||||
use crate::modules::insert::minsert::MinsertCommand;
|
use crate::modules::insert::minsert::MinsertCommand;
|
||||||
let cmd = MinsertCommand::new(blocks);
|
let ranked = self.ranked_block_names(&blocks);
|
||||||
|
let snapshot = self.block_usage_snapshot();
|
||||||
|
let cmd = MinsertCommand::new_with_usage(
|
||||||
|
ranked,
|
||||||
|
snapshot,
|
||||||
|
self.cliprompt_lines.clamp(0, 50) as u8,
|
||||||
|
);
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
|
let opts = cmd.options();
|
||||||
|
self.command_line.set_step_options(opts.clone());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,22 @@
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
fn selected_solid(app: &OpenCADStudio, tab: usize) -> Option<acadrust::Handle> {
|
||||||
|
let scene = &app.tabs.get(tab)?.scene;
|
||||||
|
let selected = scene.selected_handles_in_order();
|
||||||
|
let [handle] = selected.as_slice() else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches!(
|
||||||
|
scene.document.get_entity(*handle),
|
||||||
|
Some(acadrust::EntityType::Solid3D(_))
|
||||||
|
) {
|
||||||
|
Some(*handle)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
pub(super) fn dispatch_dim(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
pub(super) fn dispatch_dim(&mut self, cmd: &str, i: usize) -> Option<Task<Message>> {
|
||||||
match cmd {
|
match cmd {
|
||||||
|
|
@ -31,6 +48,17 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"DIMJOGGED" | "DIMJOG" => {
|
||||||
|
use crate::modules::annotate::jogged_radius_dim::JoggedRadiusDimensionCommand;
|
||||||
|
let defaults = crate::scene::creation_style::current_dimension_defaults(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
);
|
||||||
|
let multiplier = self.tabs[i].scene.creation_annotation_multiplier();
|
||||||
|
let new_cmd = JoggedRadiusDimensionCommand::new(defaults, multiplier);
|
||||||
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
"DIMANGULAR" => {
|
"DIMANGULAR" => {
|
||||||
use crate::modules::annotate::angular_dim::AngularDimensionCommand;
|
use crate::modules::annotate::angular_dim::AngularDimensionCommand;
|
||||||
let new_cmd = AngularDimensionCommand::new();
|
let new_cmd = AngularDimensionCommand::new();
|
||||||
|
|
@ -38,6 +66,13 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"DIMARC" => {
|
||||||
|
use crate::modules::annotate::arc_length_dim::ArcLengthDimensionCommand;
|
||||||
|
let new_cmd = ArcLengthDimensionCommand::new();
|
||||||
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
|
}
|
||||||
|
|
||||||
"DIMORDINATE" => {
|
"DIMORDINATE" => {
|
||||||
use crate::modules::annotate::ordinate_dim::OrdinateDimCommand;
|
use crate::modules::annotate::ordinate_dim::OrdinateDimCommand;
|
||||||
let new_cmd = OrdinateDimCommand::new();
|
let new_cmd = OrdinateDimCommand::new();
|
||||||
|
|
@ -577,10 +612,7 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
|
|
||||||
"TOLERANCE" => {
|
"TOLERANCE" => {
|
||||||
use crate::modules::annotate::tolerance_cmd::ToleranceCommand;
|
self.open_tolerance_dialog(None);
|
||||||
let cmd = ToleranceCommand::new();
|
|
||||||
self.command_line.push_info(&cmd.prompt());
|
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"TABLE" => {
|
"TABLE" => {
|
||||||
|
|
@ -950,6 +982,20 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"SOLIDFILLET" => {
|
||||||
|
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
|
||||||
|
let command = SolidEdgeCommand::new(EdgeOperation::Fillet, selected_solid(self, i));
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
"FILLET" if selected_solid(self, i).is_some() => {
|
||||||
|
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
|
||||||
|
let command = SolidEdgeCommand::new(EdgeOperation::Fillet, selected_solid(self, i));
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
"FILLET" => {
|
"FILLET" => {
|
||||||
use crate::modules::draw::modify::fillet::FilletCommand;
|
use crate::modules::draw::modify::fillet::FilletCommand;
|
||||||
let entities: Vec<_> = self.tabs[i]
|
let entities: Vec<_> = self.tabs[i]
|
||||||
|
|
@ -1083,6 +1129,20 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"SOLIDCHAMFER" => {
|
||||||
|
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
|
||||||
|
let command = SolidEdgeCommand::new(EdgeOperation::Chamfer, selected_solid(self, i));
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
"CHAMFER" if selected_solid(self, i).is_some() => {
|
||||||
|
use crate::modules::model::edge_cmd::{EdgeOperation, SolidEdgeCommand};
|
||||||
|
let command = SolidEdgeCommand::new(EdgeOperation::Chamfer, selected_solid(self, i));
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
"CHAMFER" => {
|
"CHAMFER" => {
|
||||||
use crate::modules::draw::modify::fillet::ChamferCommand;
|
use crate::modules::draw::modify::fillet::ChamferCommand;
|
||||||
let entities: Vec<_> = self.tabs[i]
|
let entities: Vec<_> = self.tabs[i]
|
||||||
|
|
@ -1244,49 +1304,6 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DIMJOGGED <DJO> — create a jogged radius dimension on the selected
|
|
||||||
// arc/circle: a standard radius dim carrying an OCS_JOGGED XData marker
|
|
||||||
// that the geometry generator renders with a foreshortened zig-zag.
|
|
||||||
"DIMJOGGED" | "DIMJOG" => {
|
|
||||||
use acadrust::entities::{Dimension, DimensionRadius};
|
|
||||||
use acadrust::types::Vector3;
|
|
||||||
use acadrust::xdata::{ExtendedDataRecord, XDataValue};
|
|
||||||
let found =
|
|
||||||
self.tabs[i]
|
|
||||||
.scene
|
|
||||||
.selected_entities()
|
|
||||||
.iter()
|
|
||||||
.find_map(|(_, e)| match e {
|
|
||||||
acadrust::EntityType::Arc(a) => Some((a.center, a.radius)),
|
|
||||||
acadrust::EntityType::Circle(c) => Some((c.center, c.radius)),
|
|
||||||
_ => None,
|
|
||||||
});
|
|
||||||
let Some((center, radius)) = found else {
|
|
||||||
self.command_line
|
|
||||||
.push_error(crate::t!("DIMJOGGED: select an arc or circle first.").as_ref());
|
|
||||||
return None;
|
|
||||||
};
|
|
||||||
if radius <= 0.0 {
|
|
||||||
self.command_line.push_error(crate::t!("DIMJOGGED: invalid radius.").as_ref());
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let k = std::f64::consts::FRAC_1_SQRT_2;
|
|
||||||
let arc_point =
|
|
||||||
Vector3::new(center.x + radius * k, center.y + radius * k, center.z);
|
|
||||||
let mut dim = DimensionRadius::new(center, arc_point);
|
|
||||||
dim.base.common.layer = self.tabs[i].active_layer.clone();
|
|
||||||
let mut rec = ExtendedDataRecord::new("OCS_JOGGED");
|
|
||||||
rec.add_value(XDataValue::String("1".to_string()));
|
|
||||||
dim.base.common.extended_data.add_record(rec);
|
|
||||||
self.push_undo_snapshot(i, "DIMJOGGED");
|
|
||||||
self.tabs[i]
|
|
||||||
.scene
|
|
||||||
.add_entity(acadrust::EntityType::Dimension(Dimension::Radius(dim)));
|
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.command_line
|
|
||||||
.push_output(crate::t!("DIMJOGGED: created a jogged radius dimension.").as_ref());
|
|
||||||
}
|
|
||||||
|
|
||||||
// ARCTEXT <text> — lay the text out as one Text entity per character
|
// ARCTEXT <text> — lay the text out as one Text entity per character
|
||||||
// along the selected arc, each rotated to follow the tangent.
|
// along the selected arc, each rotated to follow the tangent.
|
||||||
"ARCTEXT" => {
|
"ARCTEXT" => {
|
||||||
|
|
|
||||||
|
|
@ -12,60 +12,327 @@ impl OpenCADStudio {
|
||||||
.push_output(crate::t!("PAN: drag with the left mouse button. Press Esc to exit.").as_ref());
|
.push_output(crate::t!("PAN: drag with the left mouse button. Press Esc to exit.").as_ref());
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── TABLE cell editing ─────────────────────────────────────────────
|
// ── TABLE structure and cell editing ───────────────────────────────
|
||||||
// TABLE CELL <row> <col> <text> — set text for a cell in the selected Table
|
|
||||||
cmd if cmd.starts_with("TABLE ") => {
|
cmd if cmd.starts_with("TABLE ") => {
|
||||||
let rest = cmd.trim_start_matches("TABLE").trim();
|
let rest = cmd.trim_start_matches("TABLE").trim();
|
||||||
let sub_up = rest.split_whitespace().next().unwrap_or("").to_uppercase();
|
#[derive(Clone)]
|
||||||
if sub_up == "CELL" {
|
enum TableAction {
|
||||||
let parts: Vec<&str> = rest.splitn(4, char::is_whitespace).collect();
|
Cell(usize, usize, String),
|
||||||
// parts: ["CELL", "<row>", "<col>", "<text>"]
|
InsertRow(usize),
|
||||||
let row_res = parts.get(1).and_then(|s| s.parse::<usize>().ok());
|
DeleteRow(usize),
|
||||||
let col_res = parts.get(2).and_then(|s| s.parse::<usize>().ok());
|
InsertColumn(usize),
|
||||||
let text = parts.get(3).copied().unwrap_or("");
|
DeleteColumn(usize),
|
||||||
match (row_res, col_res) {
|
Merge(usize, usize, usize, usize),
|
||||||
(Some(row), Some(col)) => {
|
Unmerge(usize, usize),
|
||||||
let selected_handles: Vec<acadrust::Handle> = self.tabs[i]
|
RowHeight(usize, f64),
|
||||||
.scene
|
ColumnWidth(usize, f64),
|
||||||
.selected_entities()
|
Lock(usize, usize, bool),
|
||||||
.iter()
|
Block(usize, usize, String),
|
||||||
.map(|(h, _)| *h)
|
Formula(usize, usize, String),
|
||||||
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
Field(usize, usize, acadrust::Handle),
|
||||||
.collect();
|
}
|
||||||
let mut found = false;
|
let words: Vec<&str> = rest.split_whitespace().collect();
|
||||||
for sh in &selected_handles {
|
let integer = |index: usize| {
|
||||||
if let Some(acadrust::EntityType::Table(tbl)) = self.tabs[i]
|
words.get(index).and_then(|value| value.parse::<usize>().ok())
|
||||||
.scene
|
};
|
||||||
.document
|
let real = |index: usize| {
|
||||||
.entities_mut()
|
words.get(index).and_then(|value| value.parse::<f64>().ok())
|
||||||
.find(|e| e.common().handle == *sh)
|
};
|
||||||
{
|
let action = match words.first().map(|word| word.to_ascii_uppercase()).as_deref() {
|
||||||
if tbl.set_cell_text(row, col, text) {
|
Some("CELL") => {
|
||||||
found = true;
|
let parts: Vec<&str> = rest.splitn(4, char::is_whitespace).collect();
|
||||||
}
|
match (integer(1), integer(2)) {
|
||||||
}
|
(Some(row), Some(column)) => Some(TableAction::Cell(
|
||||||
}
|
row,
|
||||||
if found {
|
column,
|
||||||
self.push_undo_snapshot(i, "TABLE CELL");
|
parts.get(3).copied().unwrap_or("").to_string(),
|
||||||
self.tabs[i].dirty = true;
|
)),
|
||||||
self.command_line.push_output(crate::tf!(
|
_ => None,
|
||||||
"TABLE CELL: set [{row},{col}] = \"{text}\"."
|
|
||||||
).as_ref());
|
|
||||||
} else {
|
|
||||||
self.command_line.push_error(
|
|
||||||
crate::t!("TABLE CELL: select a Table entity first, or row/col out of range.").as_ref()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.command_line
|
|
||||||
.push_info(crate::t!("Usage: TABLE CELL <row> <col> <text>").as_ref());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
Some("INSERTROW") => integer(1).map(TableAction::InsertRow),
|
||||||
|
Some("DELETEROW") => integer(1).map(TableAction::DeleteRow),
|
||||||
|
Some("INSERTCOLUMN") | Some("INSERTCOL") => {
|
||||||
|
integer(1).map(TableAction::InsertColumn)
|
||||||
|
}
|
||||||
|
Some("DELETECOLUMN") | Some("DELETECOL") => {
|
||||||
|
integer(1).map(TableAction::DeleteColumn)
|
||||||
|
}
|
||||||
|
Some("MERGE") => match (integer(1), integer(2), integer(3), integer(4)) {
|
||||||
|
(Some(r1), Some(c1), Some(r2), Some(c2)) => {
|
||||||
|
Some(TableAction::Merge(r1, c1, r2, c2))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Some("UNMERGE") => match (integer(1), integer(2)) {
|
||||||
|
(Some(row), Some(column)) => Some(TableAction::Unmerge(row, column)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Some("ROWHEIGHT") => match (integer(1), real(2)) {
|
||||||
|
(Some(row), Some(height)) if height > 0.0 => {
|
||||||
|
Some(TableAction::RowHeight(row, height))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Some("COLUMNWIDTH") | Some("COLWIDTH") => match (integer(1), real(2)) {
|
||||||
|
(Some(column), Some(width)) if width > 0.0 => {
|
||||||
|
Some(TableAction::ColumnWidth(column, width))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Some("LOCK") => match (integer(1), integer(2), words.get(3)) {
|
||||||
|
(Some(row), Some(column), Some(value)) => Some(TableAction::Lock(
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
!value.eq_ignore_ascii_case("OFF"),
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
Some("BLOCK") => {
|
||||||
|
let parts: Vec<&str> = rest.splitn(4, char::is_whitespace).collect();
|
||||||
|
match (integer(1), integer(2), parts.get(3)) {
|
||||||
|
(Some(row), Some(column), Some(name)) if !name.trim().is_empty() => {
|
||||||
|
Some(TableAction::Block(row, column, name.trim().to_string()))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("FORMULA") => {
|
||||||
|
let parts: Vec<&str> = rest.splitn(4, char::is_whitespace).collect();
|
||||||
|
match (integer(1), integer(2), parts.get(3)) {
|
||||||
|
(Some(row), Some(column), Some(expression))
|
||||||
|
if !expression.trim().is_empty() =>
|
||||||
|
{
|
||||||
|
Some(TableAction::Formula(
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
expression.trim().to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some("FIELD") => match (integer(1), integer(2), words.get(3)) {
|
||||||
|
(Some(row), Some(column), Some(value)) => {
|
||||||
|
u64::from_str_radix(value.trim_start_matches("0x"), 16)
|
||||||
|
.ok()
|
||||||
|
.map(|handle| {
|
||||||
|
TableAction::Field(
|
||||||
|
row,
|
||||||
|
column,
|
||||||
|
acadrust::Handle::new(handle),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let Some(action) = action else {
|
||||||
self.command_line.push_info(
|
self.command_line.push_info(
|
||||||
"Usage: TABLE (creates new table) or TABLE CELL <row> <col> <text>",
|
crate::t!("Usage: TABLE CELL | INSERTROW | DELETEROW | INSERTCOL | DELETECOL | MERGE | UNMERGE | ROWHEIGHT | COLWIDTH | LOCK | BLOCK | FORMULA | FIELD").as_ref(),
|
||||||
);
|
);
|
||||||
|
return Some(Task::none());
|
||||||
|
};
|
||||||
|
let selected_handles: Vec<acadrust::Handle> = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.selected_entities()
|
||||||
|
.iter()
|
||||||
|
.map(|(handle, _)| *handle)
|
||||||
|
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||||
|
.collect();
|
||||||
|
if selected_handles.is_empty() {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("TABLE: select a table first.").as_ref());
|
||||||
|
return Some(Task::none());
|
||||||
|
}
|
||||||
|
let block_handle = if let TableAction::Block(_, _, name) = &action {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|record| record.name.eq_ignore_ascii_case(name))
|
||||||
|
.map(|record| record.handle)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
if matches!(action, TableAction::Block(_, _, _)) && block_handle.is_none() {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("TABLE: block definition not found.").as_ref(),
|
||||||
|
);
|
||||||
|
return Some(Task::none());
|
||||||
|
}
|
||||||
|
if let TableAction::Field(_, _, field_handle) = &action {
|
||||||
|
if !self.tabs[i].scene.document.fields.contains_key(field_handle) {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("TABLE: field handle not found.").as_ref(),
|
||||||
|
);
|
||||||
|
return Some(Task::none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "TABLE EDIT");
|
||||||
|
let mut changed = false;
|
||||||
|
for handle in &selected_handles {
|
||||||
|
let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(*handle)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match &action {
|
||||||
|
TableAction::Cell(row, column, text) => {
|
||||||
|
if let Some(cell) = table.cell_mut(*row, *column) {
|
||||||
|
use acadrust::entities::table::CellStateFlags;
|
||||||
|
if !cell.state.intersects(
|
||||||
|
CellStateFlags::CONTENT_LOCKED
|
||||||
|
| CellStateFlags::CONTENT_READ_ONLY,
|
||||||
|
) {
|
||||||
|
cell.set_text(text);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableAction::InsertRow(row) if *row <= table.row_count() => {
|
||||||
|
let height = table
|
||||||
|
.rows
|
||||||
|
.get((*row).min(table.row_count().saturating_sub(1)))
|
||||||
|
.map(|row| row.height)
|
||||||
|
.unwrap_or(0.5);
|
||||||
|
table.insert_row(*row);
|
||||||
|
table.set_row_height(*row, height);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::DeleteRow(row)
|
||||||
|
if table.row_count() > 1 && *row < table.row_count() =>
|
||||||
|
{
|
||||||
|
table.remove_row(*row);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::InsertColumn(column) if *column <= table.column_count() => {
|
||||||
|
let width = table
|
||||||
|
.columns
|
||||||
|
.get((*column).min(table.column_count().saturating_sub(1)))
|
||||||
|
.map(|column| column.width)
|
||||||
|
.unwrap_or(2.0);
|
||||||
|
table.insert_column(*column, width);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::DeleteColumn(column)
|
||||||
|
if table.column_count() > 1 && *column < table.column_count() =>
|
||||||
|
{
|
||||||
|
table.remove_column(*column);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::Merge(r1, c1, r2, c2)
|
||||||
|
if *r1 < table.row_count()
|
||||||
|
&& *r2 < table.row_count()
|
||||||
|
&& *c1 < table.column_count()
|
||||||
|
&& *c2 < table.column_count() =>
|
||||||
|
{
|
||||||
|
table.merge_cells(acadrust::entities::table::CellRange::new(
|
||||||
|
(*r1).min(*r2),
|
||||||
|
(*c1).min(*c2),
|
||||||
|
(*r1).max(*r2),
|
||||||
|
(*c1).max(*c2),
|
||||||
|
));
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::Unmerge(row, column) => {
|
||||||
|
table.unmerge_cell(*row, *column);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::RowHeight(row, height) if *row < table.row_count() => {
|
||||||
|
table.set_row_height(*row, *height);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::ColumnWidth(column, width)
|
||||||
|
if *column < table.column_count() =>
|
||||||
|
{
|
||||||
|
table.set_column_width(*column, *width);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
TableAction::Lock(row, column, locked) => {
|
||||||
|
if let Some(cell) = table.cell_mut(*row, *column) {
|
||||||
|
use acadrust::entities::table::CellStateFlags;
|
||||||
|
cell.state.set(CellStateFlags::CONTENT_LOCKED, *locked);
|
||||||
|
cell.state.set(CellStateFlags::FORMAT_LOCKED, *locked);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableAction::Block(row, column, _) => {
|
||||||
|
if let (Some(handle), Some(cell)) =
|
||||||
|
(block_handle, table.cell_mut(*row, *column))
|
||||||
|
{
|
||||||
|
use acadrust::entities::table::{
|
||||||
|
CellContent, CellStateFlags, CellType,
|
||||||
|
};
|
||||||
|
if !cell.state.intersects(
|
||||||
|
CellStateFlags::CONTENT_LOCKED
|
||||||
|
| CellStateFlags::CONTENT_READ_ONLY,
|
||||||
|
) {
|
||||||
|
cell.contents.clear();
|
||||||
|
cell.contents.push(CellContent::block(handle));
|
||||||
|
cell.cell_type = CellType::Block;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableAction::Formula(row, column, expression) => {
|
||||||
|
if let Some(cell) = table.cell_mut(*row, *column) {
|
||||||
|
use acadrust::entities::table::CellStateFlags;
|
||||||
|
if !cell.state.intersects(
|
||||||
|
CellStateFlags::CONTENT_LOCKED
|
||||||
|
| CellStateFlags::CONTENT_READ_ONLY,
|
||||||
|
) {
|
||||||
|
let formula = if expression.starts_with('=') {
|
||||||
|
expression.clone()
|
||||||
|
} else {
|
||||||
|
format!("={expression}")
|
||||||
|
};
|
||||||
|
cell.set_text(&formula);
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TableAction::Field(row, column, field_handle) => {
|
||||||
|
let mut attached = false;
|
||||||
|
if let Some(cell) = table.cell_mut(*row, *column) {
|
||||||
|
use acadrust::entities::table::{
|
||||||
|
CellContent, CellStateFlags, CellType,
|
||||||
|
};
|
||||||
|
if !cell.state.intersects(
|
||||||
|
CellStateFlags::CONTENT_LOCKED
|
||||||
|
| CellStateFlags::CONTENT_READ_ONLY,
|
||||||
|
) {
|
||||||
|
let mut content = CellContent::text("");
|
||||||
|
content.field_handle = Some(*field_handle);
|
||||||
|
cell.contents.clear();
|
||||||
|
cell.contents.push(content);
|
||||||
|
cell.cell_type = CellType::Text;
|
||||||
|
attached = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if attached {
|
||||||
|
if !table.field_handles.contains(field_handle) {
|
||||||
|
table.field_handles.push(*field_handle);
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if changed {
|
||||||
|
self.invalidate_property_targets(i, &selected_handles);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
self.command_line
|
||||||
|
.push_output(crate::t!("TABLE: edit applied.").as_ref());
|
||||||
|
} else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!(
|
||||||
|
"TABLE: row/column is out of range or the cell is locked."
|
||||||
|
).as_ref());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,31 +733,55 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BOX / SPHERE / CYLINDER / CONE / WEDGE / TORUS are handled by the
|
|
||||||
// Model-tab primitive command above (with the kernel boolean caching).
|
|
||||||
|
|
||||||
// ── EXTRUDE ────────────────────────────────────────────────────
|
// ── EXTRUDE ────────────────────────────────────────────────────
|
||||||
// PRESSPULL on a closed boundary creates a solid by extruding it to a
|
"EXTRUDE" | "THICKEN" => {
|
||||||
// height — the same operation as EXTRUDE. THICKEN turns a closed planar
|
|
||||||
// profile into a solid of the given thickness, which is also an extrude.
|
|
||||||
"EXTRUDE" | "PRESSPULL" | "THICKEN" => {
|
|
||||||
use crate::modules::insert::solid3d_cmds::ExtrudeCommand;
|
use crate::modules::insert::solid3d_cmds::ExtrudeCommand;
|
||||||
// If a single entity is already selected, skip the pick step.
|
// If a single entity is already selected, skip the pick step.
|
||||||
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
|
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
|
||||||
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||||
if selected.len() == 1 {
|
if selected.len() == 1 {
|
||||||
let handle = selected[0].0;
|
let handle = selected[0].0;
|
||||||
let mut cmd = ExtrudeCommand::new(color);
|
let mut cmd = ExtrudeCommand::new_named(cmd, color);
|
||||||
cmd.on_entity_pick(handle, glam::DVec3::ZERO);
|
if let Some(curve) = crate::entities::curve::entity_curve(selected[0].1) {
|
||||||
|
cmd.set_entity_pick_direction(
|
||||||
|
curve.plane.normal().map(glam::DVec3::from_array),
|
||||||
|
);
|
||||||
|
cmd.on_entity_pick(
|
||||||
|
handle,
|
||||||
|
glam::DVec3::from_array(curve.plane.origin),
|
||||||
|
);
|
||||||
|
}
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
} else {
|
} else {
|
||||||
let cmd = ExtrudeCommand::new(color);
|
let cmd = ExtrudeCommand::new_named(cmd, color);
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"PRESSPULL" => {
|
||||||
|
use crate::modules::insert::solid3d_cmds::PresspullCommand;
|
||||||
|
let selected: Vec<_> = self.tabs[i].scene.selected_entities().into_iter().collect();
|
||||||
|
let color = self.tabs[i].scene.layer_color(&self.tabs[i].active_layer);
|
||||||
|
let mut command = PresspullCommand::new(color);
|
||||||
|
if selected.len() == 1
|
||||||
|
&& !matches!(selected[0].1, acadrust::EntityType::Solid3D(_))
|
||||||
|
{
|
||||||
|
if let Some(curve) = crate::entities::curve::entity_curve(selected[0].1) {
|
||||||
|
command.set_entity_pick_direction(
|
||||||
|
curve.plane.normal().map(glam::DVec3::from_array),
|
||||||
|
);
|
||||||
|
command.on_entity_pick(
|
||||||
|
selected[0].0,
|
||||||
|
glam::DVec3::from_array(curve.plane.origin),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
|
||||||
// ── REVOLVE ────────────────────────────────────────────────────
|
// ── REVOLVE ────────────────────────────────────────────────────
|
||||||
"REVOLVE" => {
|
"REVOLVE" => {
|
||||||
use crate::modules::insert::solid3d_cmds::RevolveCommand;
|
use crate::modules::insert::solid3d_cmds::RevolveCommand;
|
||||||
|
|
@ -1144,8 +1435,7 @@ impl OpenCADStudio {
|
||||||
return Some(Task::done(Message::AnnoObjectScaleOpen));
|
return Some(Task::done(Message::AnnoObjectScaleOpen));
|
||||||
}
|
}
|
||||||
|
|
||||||
// DATALINK <path.csv> — import a CSV file into a table placed at the
|
// DATALINK <path.csv> — create a persistent linked table.
|
||||||
// origin (one-time import; a live re-reading link is future work).
|
|
||||||
"DATALINK" => {
|
"DATALINK" => {
|
||||||
use crate::command::ValuePromptCommand;
|
use crate::command::ValuePromptCommand;
|
||||||
let c = ValuePromptCommand::new("DATALINK", "DATALINK path to the .csv file:");
|
let c = ValuePromptCommand::new("DATALINK", "DATALINK path to the .csv file:");
|
||||||
|
|
@ -1156,17 +1446,13 @@ impl OpenCADStudio {
|
||||||
let path = cmd.trim_start_matches("DATALINK").trim();
|
let path = cmd.trim_start_matches("DATALINK").trim();
|
||||||
if path.is_empty() {
|
if path.is_empty() {
|
||||||
self.command_line.push_info(
|
self.command_line.push_info(
|
||||||
"Usage: DATALINK <path-to-.csv> — imports the CSV into a table at the origin.",
|
"Usage: DATALINK <path-to-.csv>",
|
||||||
);
|
);
|
||||||
return Some(Task::none());
|
return Some(Task::none());
|
||||||
}
|
}
|
||||||
match std::fs::read_to_string(path) {
|
match std::fs::read_to_string(path) {
|
||||||
Ok(text) => {
|
Ok(text) => {
|
||||||
let rows_data: Vec<Vec<String>> = text
|
let rows_data = parse_csv_table(&text);
|
||||||
.lines()
|
|
||||||
.filter(|l| !l.trim().is_empty())
|
|
||||||
.map(|line| line.split(',').map(|s| s.trim().to_string()).collect())
|
|
||||||
.collect();
|
|
||||||
let nrows = rows_data.len();
|
let nrows = rows_data.len();
|
||||||
let ncols = rows_data.iter().map(|r| r.len()).max().unwrap_or(0);
|
let ncols = rows_data.iter().map(|r| r.len()).max().unwrap_or(0);
|
||||||
if nrows == 0 || ncols == 0 {
|
if nrows == 0 || ncols == 0 {
|
||||||
|
|
@ -1186,14 +1472,27 @@ impl OpenCADStudio {
|
||||||
table.set_cell_text(r, c, cell);
|
table.set_cell_text(r, c, cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.push_undo_snapshot(i, "DATALINK");
|
let doc = &self.tabs[i].scene.document;
|
||||||
self.tabs[i]
|
let current_style = doc.header.current_table_style_name.clone();
|
||||||
.scene
|
table.table_style_handle = doc.objects.iter().find_map(|(handle, object)| {
|
||||||
.add_entity_clone(acadrust::EntityType::Table(table));
|
match object {
|
||||||
self.tabs[i].dirty = true;
|
acadrust::objects::ObjectType::TableStyle(style)
|
||||||
self.command_line.push_output(crate::tf!(
|
if style.name.eq_ignore_ascii_case(¤t_style) =>
|
||||||
"DATALINK: imported {nrows}×{ncols} cells into a table at the origin."
|
{
|
||||||
).as_ref());
|
Some(*handle)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let link_path = std::fs::canonicalize(path)
|
||||||
|
.map(|value| value.to_string_lossy().into_owned())
|
||||||
|
.unwrap_or_else(|_| path.to_string());
|
||||||
|
let command = crate::modules::annotate::data_link::DataLinkPlaceCommand::new(
|
||||||
|
table, &link_path,
|
||||||
|
);
|
||||||
|
self.command_line
|
||||||
|
.push_info(&crate::command::CadCommand::prompt(&command));
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
self.command_line
|
self.command_line
|
||||||
|
|
@ -1202,6 +1501,151 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmd if cmd == "DATALINKUPDATE" || cmd.starts_with("DATALINKUPDATE ") => {
|
||||||
|
let write_back = cmd
|
||||||
|
.trim_start_matches("DATALINKUPDATE")
|
||||||
|
.trim()
|
||||||
|
.eq_ignore_ascii_case("WRITE");
|
||||||
|
let selected: Vec<acadrust::Handle> = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.selected_entities()
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(handle, entity)| {
|
||||||
|
matches!(entity, acadrust::EntityType::Table(_)).then_some(handle)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let table_handles = if selected.is_empty() {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.entities()
|
||||||
|
.filter_map(|entity| {
|
||||||
|
matches!(entity, acadrust::EntityType::Table(_))
|
||||||
|
.then_some(entity.common().handle)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
} else {
|
||||||
|
selected
|
||||||
|
};
|
||||||
|
let mut jobs = Vec::new();
|
||||||
|
for handle in &table_handles {
|
||||||
|
let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity(*handle)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let link_handle = table
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.flat_map(|row| row.cells.iter())
|
||||||
|
.find_map(|cell| cell.data_link_handle);
|
||||||
|
let Some(link_handle) = link_handle else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let path = match self.tabs[i].scene.document.objects.get(&link_handle) {
|
||||||
|
Some(acadrust::objects::ObjectType::ClassObject(object)) => {
|
||||||
|
match &object.data {
|
||||||
|
acadrust::objects::ClassObjectData::DataLink(link) => {
|
||||||
|
Some(link.connection_string.clone())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(path) = path {
|
||||||
|
jobs.push((*handle, link_handle, path));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if jobs.is_empty() {
|
||||||
|
self.command_line
|
||||||
|
.push_error("DATALINKUPDATE: no linked tables found.");
|
||||||
|
return Some(Task::none());
|
||||||
|
}
|
||||||
|
if write_back {
|
||||||
|
let mut written = 0usize;
|
||||||
|
for (table_handle, _, path) in &jobs {
|
||||||
|
let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity(*table_handle)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let csv = table_to_csv(table);
|
||||||
|
if std::fs::write(path, csv).is_ok() {
|
||||||
|
written += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.command_line.push_output(
|
||||||
|
crate::tf!("DATALINKUPDATE: wrote {} linked source(s).", written).as_ref(),
|
||||||
|
);
|
||||||
|
return Some(Task::none());
|
||||||
|
}
|
||||||
|
let updates: Vec<_> = jobs
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(table_handle, link_handle, path)| {
|
||||||
|
std::fs::read_to_string(&path)
|
||||||
|
.ok()
|
||||||
|
.map(|text| (table_handle, link_handle, parse_csv_table(&text)))
|
||||||
|
})
|
||||||
|
.filter(|(_, _, rows)| !rows.is_empty())
|
||||||
|
.collect();
|
||||||
|
if updates.is_empty() {
|
||||||
|
self.command_line
|
||||||
|
.push_error("DATALINKUPDATE: linked sources could not be read.");
|
||||||
|
return Some(Task::none());
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "DATALINKUPDATE");
|
||||||
|
use acadrust::entities::table::CellStateFlags;
|
||||||
|
let mut changed_handles = Vec::new();
|
||||||
|
for (table_handle, link_handle, rows) in updates {
|
||||||
|
let columns = rows.iter().map(Vec::len).max().unwrap_or(0);
|
||||||
|
let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(table_handle)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
while table.row_count() < rows.len() {
|
||||||
|
table.add_row();
|
||||||
|
}
|
||||||
|
while table.row_count() > rows.len() {
|
||||||
|
table.remove_row(table.row_count() - 1);
|
||||||
|
}
|
||||||
|
while table.column_count() < columns {
|
||||||
|
let width = table.columns.last().map(|column| column.width).unwrap_or(2.0);
|
||||||
|
table.add_column(width);
|
||||||
|
}
|
||||||
|
while table.column_count() > columns {
|
||||||
|
table.remove_column(table.column_count() - 1);
|
||||||
|
}
|
||||||
|
for row in 0..rows.len() {
|
||||||
|
for column in 0..columns {
|
||||||
|
if let Some(cell) = table.cell_mut(row, column) {
|
||||||
|
cell.set_text(
|
||||||
|
rows[row].get(column).map(String::as_str).unwrap_or(""),
|
||||||
|
);
|
||||||
|
cell.has_linked_data = true;
|
||||||
|
cell.data_link_handle = Some(link_handle);
|
||||||
|
cell.data_link_rows = rows.len() as i32;
|
||||||
|
cell.data_link_columns = columns as i32;
|
||||||
|
cell.state.insert(
|
||||||
|
CellStateFlags::LINKED
|
||||||
|
| CellStateFlags::CONTENT_LOCKED
|
||||||
|
| CellStateFlags::FORMAT_LOCKED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed_handles.push(table_handle);
|
||||||
|
}
|
||||||
|
self.invalidate_property_targets(i, &changed_handles);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
self.command_line.push_output(
|
||||||
|
crate::tf!("DATALINKUPDATE: updated {} linked table(s).", changed_handles.len())
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// LANDXMLIMPORT <path> — import survey points (LandXML <CgPoint>
|
// LANDXMLIMPORT <path> — import survey points (LandXML <CgPoint>
|
||||||
// elements) as Point objects. Reads the coordinate text content
|
// elements) as Point objects. Reads the coordinate text content
|
||||||
// (northing easting elevation) → Point at (easting, northing, elev).
|
// (northing easting elevation) → Point at (easting, northing, elev).
|
||||||
|
|
@ -1296,6 +1740,68 @@ fn parse_landxml_cgpoints(xml: &str) -> Vec<[f64; 3]> {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_csv_table(text: &str) -> Vec<Vec<String>> {
|
||||||
|
let mut rows = Vec::new();
|
||||||
|
let mut row = Vec::new();
|
||||||
|
let mut field = String::new();
|
||||||
|
let mut quoted = false;
|
||||||
|
let mut chars = text.chars().peekable();
|
||||||
|
while let Some(ch) = chars.next() {
|
||||||
|
match ch {
|
||||||
|
'"' if quoted && chars.peek() == Some(&'"') => {
|
||||||
|
field.push('"');
|
||||||
|
chars.next();
|
||||||
|
}
|
||||||
|
'"' => quoted = !quoted,
|
||||||
|
',' if !quoted => row.push(std::mem::take(&mut field)),
|
||||||
|
'\n' if !quoted => {
|
||||||
|
if field.ends_with('\r') {
|
||||||
|
field.pop();
|
||||||
|
}
|
||||||
|
row.push(std::mem::take(&mut field));
|
||||||
|
if row.iter().any(|value| !value.is_empty()) {
|
||||||
|
rows.push(std::mem::take(&mut row));
|
||||||
|
} else {
|
||||||
|
row.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => field.push(ch),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if field.ends_with('\r') {
|
||||||
|
field.pop();
|
||||||
|
}
|
||||||
|
if !field.is_empty() || !row.is_empty() {
|
||||||
|
row.push(field);
|
||||||
|
if row.iter().any(|value| !value.is_empty()) {
|
||||||
|
rows.push(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows
|
||||||
|
}
|
||||||
|
|
||||||
|
fn table_to_csv(table: &acadrust::entities::Table) -> String {
|
||||||
|
fn escape(value: &str) -> String {
|
||||||
|
if value.contains([',', '"', '\r', '\n']) {
|
||||||
|
format!("\"{}\"", value.replace('"', "\"\""))
|
||||||
|
} else {
|
||||||
|
value.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.map(|row| {
|
||||||
|
row.cells
|
||||||
|
.iter()
|
||||||
|
.map(|cell| escape(cell.text_value()))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(",")
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\r\n")
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::app::OpenCADStudio;
|
use crate::app::OpenCADStudio;
|
||||||
|
|
|
||||||
|
|
@ -925,21 +925,66 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Model commands (3D primitives) ─────────────────────────────
|
// ── Model commands (3D primitives) ─────────────────────────────
|
||||||
"BOX" | "WEDGE" | "CYLINDER" | "CONE" | "SPHERE" | "TORUS" => {
|
"BOX" | "WEDGE" | "CYLINDER" | "CONE" | "SPHERE" | "PYRAMID" | "PYR"
|
||||||
|
| "TORUS" => {
|
||||||
use crate::modules::model::primitive_cmd::PrimitiveCommand;
|
use crate::modules::model::primitive_cmd::PrimitiveCommand;
|
||||||
let new_cmd = PrimitiveCommand::new(cmd);
|
let new_cmd = PrimitiveCommand::new(cmd);
|
||||||
self.command_line.push_info(&new_cmd.prompt());
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Design commands (solid booleans) ───────────────────────────
|
// ── Solid booleans ─────────────────────────────────────────────
|
||||||
"UNION" | "SUBTRACT" | "INTERSECT" => {
|
"UNION" | "INTERSECT" => {
|
||||||
use crate::modules::model::boolean_cmd::BoolOp;
|
use crate::modules::model::boolean_cmd::BoolOp;
|
||||||
if let Some(op) = BoolOp::from_id(cmd) {
|
if let Some(op) = BoolOp::from_id(cmd) {
|
||||||
return Some(self.solid_boolean(op));
|
let solid_count = {
|
||||||
|
let scene = &self.tabs[i].scene;
|
||||||
|
scene
|
||||||
|
.selected_handles_in_order()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| !scene.is_layer_locked(*handle))
|
||||||
|
.filter(|handle| {
|
||||||
|
matches!(
|
||||||
|
scene.document.get_entity(*handle),
|
||||||
|
Some(acadrust::EntityType::Solid3D(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.take(2)
|
||||||
|
.count()
|
||||||
|
};
|
||||||
|
if solid_count < 2 {
|
||||||
|
use crate::modules::draw::select::SelectObjectsCommand;
|
||||||
|
let selection = SelectObjectsCommand::new(cmd);
|
||||||
|
self.command_line.push_info(&selection.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(selection));
|
||||||
|
} else {
|
||||||
|
return Some(self.solid_boolean(op));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"SUBTRACT" => {
|
||||||
|
use crate::modules::model::boolean_cmd::SubtractCommand;
|
||||||
|
let bases = {
|
||||||
|
let scene = &self.tabs[i].scene;
|
||||||
|
scene
|
||||||
|
.selected_handles_in_order()
|
||||||
|
.into_iter()
|
||||||
|
.filter(|handle| !scene.is_layer_locked(*handle))
|
||||||
|
.filter(|handle| {
|
||||||
|
matches!(
|
||||||
|
scene.document.get_entity(*handle),
|
||||||
|
Some(acadrust::EntityType::Solid3D(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
|
let subtract = SubtractCommand::new(bases);
|
||||||
|
self.command_line.push_info(&subtract.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(subtract));
|
||||||
|
}
|
||||||
|
|
||||||
// INTERFERE — non-destructive intersect: solid from the overlap.
|
// INTERFERE — non-destructive intersect: solid from the overlap.
|
||||||
"INTERFERE" => {
|
"INTERFERE" => {
|
||||||
return Some(self.solid_interfere());
|
return Some(self.solid_interfere());
|
||||||
|
|
@ -1033,17 +1078,7 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PYRAMID <radius> <height> [sides] — create an n-sided pyramid mesh.
|
// PYRAMID <radius> <height> [sides] keeps the direct numeric form.
|
||||||
"PYRAMID" | "PYR" => {
|
|
||||||
use crate::command::TwoValuePromptCommand;
|
|
||||||
let c = TwoValuePromptCommand::new(
|
|
||||||
"PYRAMID",
|
|
||||||
"PYRAMID base radius:",
|
|
||||||
"PYRAMID height (add sides by typing a 3rd number):",
|
|
||||||
);
|
|
||||||
self.command_line.push_info(&c.prompt());
|
|
||||||
self.tabs[i].active_cmd = Some(Box::new(c));
|
|
||||||
}
|
|
||||||
cmd if cmd.starts_with("PYRAMID ") || cmd.starts_with("PYR ") => {
|
cmd if cmd.starts_with("PYRAMID ") || cmd.starts_with("PYR ") => {
|
||||||
let nums: Vec<f64> = cmd
|
let nums: Vec<f64> = cmd
|
||||||
.split_whitespace()
|
.split_whitespace()
|
||||||
|
|
@ -1233,11 +1268,16 @@ impl OpenCADStudio {
|
||||||
// ── Annotate commands ──────────────────────────────────────────
|
// ── Annotate commands ──────────────────────────────────────────
|
||||||
"TEXT" => {
|
"TEXT" => {
|
||||||
use crate::modules::annotate::text::TextCommand;
|
use crate::modules::annotate::text::TextCommand;
|
||||||
let height = crate::scene::creation_style::current_text_defaults(
|
let (defaults, styles, annotation_multiplier) = {
|
||||||
&self.tabs[i].scene.document,
|
let scene = &self.tabs[i].scene;
|
||||||
)
|
let annotation_multiplier = scene.creation_annotation_multiplier();
|
||||||
.height;
|
let defaults =
|
||||||
let new_cmd = TextCommand::with_height(height);
|
crate::scene::creation_style::current_text_defaults(&scene.document);
|
||||||
|
let styles = scene.document.text_styles.iter().cloned().collect();
|
||||||
|
(defaults, styles, annotation_multiplier)
|
||||||
|
};
|
||||||
|
let new_cmd =
|
||||||
|
TextCommand::with_defaults(defaults, styles, annotation_multiplier);
|
||||||
self.command_line.push_info(&new_cmd.prompt());
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
@ -1272,7 +1312,13 @@ impl OpenCADStudio {
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
)
|
)
|
||||||
.height;
|
.height;
|
||||||
let new_cmd = MTextCommand::with_height(height);
|
let style = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.current_text_style_name
|
||||||
|
.clone();
|
||||||
|
let new_cmd = MTextCommand::with_defaults(height, style);
|
||||||
self.command_line.push_info(&new_cmd.prompt());
|
self.command_line.push_info(&new_cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
self.tabs[i].active_cmd = Some(Box::new(new_cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -943,7 +943,12 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
"ALIGN" => {
|
"ALIGN" => {
|
||||||
use crate::modules::draw::modify::align::AlignCommand;
|
use crate::modules::draw::modify::align::AlignCommand;
|
||||||
let cmd = AlignCommand::new();
|
|
||||||
|
let selected: Vec<acadrust::Handle> =
|
||||||
|
self.tabs[i].scene.selected.iter().copied().collect();
|
||||||
|
|
||||||
|
let cmd = AlignCommand::with_selection(selected);
|
||||||
|
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -827,30 +827,8 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A viewport frames the model, so its framing is measured in model
|
// Model framing follows model units; sheet framing stays in paper units.
|
||||||
// units and has to follow them. Left alone, every layout would suddenly
|
let sheets = self.tabs[i].scene.sheet_viewport_handles();
|
||||||
// look at a region a thousand times too large. The paper side of the
|
|
||||||
// ratio is untouched — the sheet is still the same sheet — so the ratio
|
|
||||||
// itself moves the other way.
|
|
||||||
//
|
|
||||||
// `transform_viewport` covers the rectangle and the target but not the
|
|
||||||
// framing, so the framing is done here for every viewport, and the
|
|
||||||
// target only for the ones the scale above did not already reach.
|
|
||||||
// Not every viewport frames the model, though. Each layout's sheet
|
|
||||||
// viewport frames the sheet — its view is the paper, measured in paper
|
|
||||||
// units — so scaling it by the model's factor would leave the drawing
|
|
||||||
// frame the same size as before while the view of it changed by a
|
|
||||||
// thousand, which is the boundary appearing to break.
|
|
||||||
let sheets: std::collections::HashSet<acadrust::Handle> = self.tabs[i]
|
|
||||||
.scene
|
|
||||||
.document
|
|
||||||
.objects
|
|
||||||
.values()
|
|
||||||
.filter_map(|object| match object {
|
|
||||||
acadrust::objects::ObjectType::Layout(layout) => Some(layout.viewport),
|
|
||||||
_ => None,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let scaled: std::collections::HashSet<acadrust::Handle> = handles.iter().copied().collect();
|
let scaled: std::collections::HashSet<acadrust::Handle> = handles.iter().copied().collect();
|
||||||
let mut reframed = 0usize;
|
let mut reframed = 0usize;
|
||||||
for entity in self.tabs[i].scene.document.entities_mut() {
|
for entity in self.tabs[i].scene.document.entities_mut() {
|
||||||
|
|
@ -858,14 +836,7 @@ impl OpenCADStudio {
|
||||||
let acadrust::entities::EntityType::Viewport(vp) = entity else {
|
let acadrust::entities::EntityType::Viewport(vp) = entity else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
// Only a paper-space viewport can be a sheet — the scale above
|
let is_sheet = !scaled.contains(&handle) && sheets.contains(&handle);
|
||||||
// reached everything in model space, and what it reached frames the
|
|
||||||
// model by definition. Among the rest, the layout names its sheet
|
|
||||||
// outright, and a file that arrives without that link still gives
|
|
||||||
// itself away by sitting at the paper origin, where only the sheet
|
|
||||||
// sits.
|
|
||||||
let is_sheet = !scaled.contains(&handle)
|
|
||||||
&& (sheets.contains(&handle) || !crate::scene::Scene::is_content_viewport(vp));
|
|
||||||
if is_sheet {
|
if is_sheet {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ mod view;
|
||||||
// `DrawOrderRefCommand` lives in the `view` family file but is referenced by
|
// `DrawOrderRefCommand` lives in the `view` family file but is referenced by
|
||||||
// path (`commands::DrawOrderRefCommand`) from `update.rs`, so re-export it at
|
// path (`commands::DrawOrderRefCommand`) from `update.rs`, so re-export it at
|
||||||
// the module root to keep that path valid.
|
// the module root to keep that path valid.
|
||||||
pub(crate) use view::DrawOrderRefCommand;
|
pub(crate) use view::DrawOrderCommand;
|
||||||
|
|
||||||
impl OpenCADStudio {
|
impl OpenCADStudio {
|
||||||
/// First `"{prefix}{n}"` (n ≥ 1) not already used by a block record in the
|
/// First `"{prefix}{n}"` (n ≥ 1) not already used by a block record in the
|
||||||
|
|
@ -390,6 +390,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||||
"OBJECTSCALE",
|
"OBJECTSCALE",
|
||||||
// Import CSV into a table + LandXML survey points.
|
// Import CSV into a table + LandXML survey points.
|
||||||
"DATALINK",
|
"DATALINK",
|
||||||
|
"DATALINKUPDATE",
|
||||||
"LANDXMLIMPORT",
|
"LANDXMLIMPORT",
|
||||||
// Keyboard-shortcut (CUI) export / import.
|
// Keyboard-shortcut (CUI) export / import.
|
||||||
"CUIEXPORT",
|
"CUIEXPORT",
|
||||||
|
|
@ -397,9 +398,11 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||||
"CUILOAD",
|
"CUILOAD",
|
||||||
// Save every open drawing.
|
// Save every open drawing.
|
||||||
"SAVEALL",
|
"SAVEALL",
|
||||||
// Draw-order: all text/dims to front or back.
|
// Draw-order: all text/dims to front or back; hatches to back.
|
||||||
"TEXTTOFRONT",
|
"TEXTTOFRONT",
|
||||||
"TEXTTOBACK",
|
"TEXTTOBACK",
|
||||||
|
"HATCHTOBACK",
|
||||||
|
"HB",
|
||||||
// Criteria-based selection (same as QSELECT) + copy with picked base.
|
// Criteria-based selection (same as QSELECT) + copy with picked base.
|
||||||
"FILTER",
|
"FILTER",
|
||||||
"FI",
|
"FI",
|
||||||
|
|
@ -530,6 +533,7 @@ inventory::submit!(crate::command::CommandRegistration {
|
||||||
"SKETCHINC",
|
"SKETCHINC",
|
||||||
"SKPOLY",
|
"SKPOLY",
|
||||||
"SKTOLERANCE",
|
"SKTOLERANCE",
|
||||||
|
"CLIPROMPTLINES",
|
||||||
// Reset selected entities' overrides to follow their layer.
|
// Reset selected entities' overrides to follow their layer.
|
||||||
"SETBYLAYER",
|
"SETBYLAYER",
|
||||||
// Remove duplicate objects; set drawing base point; audit integrity;
|
// Remove duplicate objects; set drawing base point; audit integrity;
|
||||||
|
|
|
||||||
|
|
@ -412,14 +412,7 @@ impl OpenCADStudio {
|
||||||
vec![]
|
vec![]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Blocks: everything the layout-reachability walk above did
|
// Remove unreachable blocks but retain live xrefs and space containers.
|
||||||
// NOT mark live. That covers unreferenced named blocks,
|
|
||||||
// leftover anonymous blocks (*U hatch/unnamed, orphaned *D
|
|
||||||
// dimension, *T table, dynamic-block variants — AutoCAD drops
|
|
||||||
// these too), AND whole dead subgraphs a detached xref leaves
|
|
||||||
// behind. Live xrefs stay (an attached xref's blocks are
|
|
||||||
// reached from the layout that inserts them); the *Model_Space
|
|
||||||
// / *Paper_Space containers are never removed.
|
|
||||||
let block_remove: Vec<String> = if do_blocks {
|
let block_remove: Vec<String> = if do_blocks {
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
|
|
@ -494,15 +487,7 @@ impl OpenCADStudio {
|
||||||
n_blocks += block_remove.len();
|
n_blocks += block_remove.len();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw-order cleanup. A SortEntitiesTable lists a block's
|
// Remove draw-order tables whose owning blocks are gone.
|
||||||
// entities in draw order; purging the block drops the entities
|
|
||||||
// and the block record, but this object lingers, still holding
|
|
||||||
// the (now dangling) handle of every deleted entity. On a
|
|
||||||
// detached-xref purge that is hundreds of thousands of stale
|
|
||||||
// handles — megabytes of dead weight the save re-emits, which is
|
|
||||||
// why an OCS-purged file stayed far larger than AutoCAD's.
|
|
||||||
// Drop every SortEntitiesTable whose owning block is gone
|
|
||||||
// (AutoCAD's "orphaned data").
|
|
||||||
let mut n_sortents = 0usize;
|
let mut n_sortents = 0usize;
|
||||||
if do_blocks {
|
if do_blocks {
|
||||||
let live_blocks: rustc_hash::FxHashSet<acadrust::Handle> = self.tabs[i]
|
let live_blocks: rustc_hash::FxHashSet<acadrust::Handle> = self.tabs[i]
|
||||||
|
|
@ -879,6 +864,7 @@ impl OpenCADStudio {
|
||||||
| "CURSORTYPE"
|
| "CURSORTYPE"
|
||||||
| "SNAPANG"
|
| "SNAPANG"
|
||||||
| "TEXTFILL"
|
| "TEXTFILL"
|
||||||
|
| "CLIPROMPTLINES"
|
||||||
| "ATTREQ"
|
| "ATTREQ"
|
||||||
| "ATTDIA"
|
| "ATTDIA"
|
||||||
| "DIMASSOC"
|
| "DIMASSOC"
|
||||||
|
|
@ -935,6 +921,8 @@ impl OpenCADStudio {
|
||||||
| "SKETCHINC"
|
| "SKETCHINC"
|
||||||
| "SKPOLY"
|
| "SKPOLY"
|
||||||
| "SKTOLERANCE"
|
| "SKTOLERANCE"
|
||||||
|
| "DONUTID"
|
||||||
|
| "DONUTOD"
|
||||||
| "CENTEREXE"
|
| "CENTEREXE"
|
||||||
| "CENTERLAYER"
|
| "CENTERLAYER"
|
||||||
| "CENTERLTYPE"
|
| "CENTERLTYPE"
|
||||||
|
|
@ -965,7 +953,7 @@ impl OpenCADStudio {
|
||||||
let value = it.next().map(|s| s.trim().to_string());
|
let value = it.next().map(|s| s.trim().to_string());
|
||||||
if name.is_empty() || name == "?" {
|
if name.is_empty() || name == "?" {
|
||||||
self.command_line.push_info(
|
self.command_line.push_info(
|
||||||
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
"SETVAR: LTSCALE CELTSCALE PDMODE PDSIZE TEXTSIZE ORTHOMODE FILLMODE MIRRTEXT FRAME IMAGEFRAME PDFFRAME WIPEOUTFRAME XCLIPFRAME POINTCLOUDCLIPFRAME ZOOMWHEEL ZOOMFACTOR CURSORSIZE PICKBOX CURSORTYPE SNAPANG TEXTFILL CLIPROMPTLINES ATTREQ ATTDIA DIMASSOC ANGBASE ANGDIR SKETCHINC SKPOLY SKTOLERANCE DONUTID DONUTOD CENTEREXE CENTERLAYER CENTERLTYPE CENTERLTSCALE CENTERLTYPEFILE CENTERCROSSSIZE CENTERCROSSGAP CENTERMARKEXE | CLAYER CELTYPE TEXTSTYLE (read-only)",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
let frame_kind = crate::scene::frame::kind_for_name(&name);
|
let frame_kind = crate::scene::frame::kind_for_name(&name);
|
||||||
|
|
@ -1081,6 +1069,49 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
return Some(self.finish_dispatch(cmd));
|
return Some(self.finish_dispatch(cmd));
|
||||||
}
|
}
|
||||||
|
if matches!(name.as_str(), "DONUTID" | "DONUTOD") {
|
||||||
|
let current = if name == "DONUTID" {
|
||||||
|
crate::modules::draw::defaults::get_donut_inner_diameter()
|
||||||
|
} else {
|
||||||
|
crate::modules::draw::defaults::get_donut_outer_diameter()
|
||||||
|
};
|
||||||
|
if let Some(value) = &value {
|
||||||
|
let parsed = value
|
||||||
|
.trim()
|
||||||
|
.replace(',', ".")
|
||||||
|
.parse::<f64>()
|
||||||
|
.ok()
|
||||||
|
.filter(|number| number.is_finite());
|
||||||
|
let valid = parsed.filter(|number| {
|
||||||
|
if name == "DONUTID" {
|
||||||
|
*number >= 0.0
|
||||||
|
} else {
|
||||||
|
*number > 0.0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if let Some(number) = valid {
|
||||||
|
if name == "DONUTID" {
|
||||||
|
crate::modules::draw::defaults::set_donut_inner_diameter(number);
|
||||||
|
} else {
|
||||||
|
crate::modules::draw::defaults::set_donut_outer_diameter(number);
|
||||||
|
}
|
||||||
|
self.command_line
|
||||||
|
.push_output(&format!("{name} = {number}"));
|
||||||
|
} else {
|
||||||
|
self.command_line.push_error(if name == "DONUTID" {
|
||||||
|
"SETVAR: DONUTID requires a finite value greater than or equal to zero."
|
||||||
|
} else {
|
||||||
|
"SETVAR: DONUTOD requires a finite value greater than zero."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.command_line.push_output(crate::tf!(
|
||||||
|
"Enter new value for {name} <{current}>:"
|
||||||
|
).as_ref());
|
||||||
|
self.pending_setvar = Some(name.clone());
|
||||||
|
}
|
||||||
|
return Some(self.finish_dispatch(cmd));
|
||||||
|
}
|
||||||
// Parse a boolean given as 0/1 or ON/OFF.
|
// Parse a boolean given as 0/1 or ON/OFF.
|
||||||
let parse_bool = |s: &str| match s.to_uppercase().as_str() {
|
let parse_bool = |s: &str| match s.to_uppercase().as_str() {
|
||||||
"1" | "ON" | "TRUE" => Some(true),
|
"1" | "ON" | "TRUE" => Some(true),
|
||||||
|
|
@ -1268,6 +1299,20 @@ impl OpenCADStudio {
|
||||||
false,
|
false,
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
"CLIPROMPTLINES" => match &value {
|
||||||
|
Some(v) => match v.parse::<i32>() {
|
||||||
|
Ok(n) if (0..=50).contains(&n) => {
|
||||||
|
self.cliprompt_lines = n;
|
||||||
|
self.command_line.set_cliprompt_lines(n as u8);
|
||||||
|
Ok((format!("CLIPROMPTLINES = {n}"), true))
|
||||||
|
}
|
||||||
|
_ => Err("SETVAR: integer from 0 to 50 required.".into()),
|
||||||
|
},
|
||||||
|
None => Ok((
|
||||||
|
format!("CLIPROMPTLINES = {}", self.cliprompt_lines),
|
||||||
|
false,
|
||||||
|
)),
|
||||||
|
},
|
||||||
"ATTREQ" => match &value {
|
"ATTREQ" => match &value {
|
||||||
Some(v) => parse_bool(v)
|
Some(v) => parse_bool(v)
|
||||||
.map(|b| {
|
.map(|b| {
|
||||||
|
|
@ -1844,11 +1889,18 @@ impl OpenCADStudio {
|
||||||
| "PICKBOX"
|
| "PICKBOX"
|
||||||
| "CURSORTYPE"
|
| "CURSORTYPE"
|
||||||
| "SNAPANG"
|
| "SNAPANG"
|
||||||
|
| "CLIPROMPTLINES"
|
||||||
) {
|
) {
|
||||||
self.persist_settings_if_changed();
|
self.persist_settings_if_changed();
|
||||||
} else {
|
} else {
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
|
if name == "FILLMODE" {
|
||||||
|
self.tabs[i].scene.bump_geometry();
|
||||||
|
}
|
||||||
|
if matches!(name.as_str(), "PSLTSCALE" | "PLIMCHECK") {
|
||||||
|
self.tabs[i].scene.persist_current_layout_state();
|
||||||
|
}
|
||||||
self.command_line.push_output(&msg);
|
self.command_line.push_output(&msg);
|
||||||
} else {
|
} else {
|
||||||
// Queried with no value (`changed == false`):
|
// Queried with no value (`changed == false`):
|
||||||
|
|
|
||||||
|
|
@ -195,10 +195,6 @@ pub(super) struct DocumentTab {
|
||||||
pub(super) active_mleader_style: String,
|
pub(super) active_mleader_style: String,
|
||||||
/// Last camera_generation value written back to the document.
|
/// Last camera_generation value written back to the document.
|
||||||
pub(super) last_synced_camera_gen: u64,
|
pub(super) last_synced_camera_gen: u64,
|
||||||
/// Render-state key of `scene.document.preview`. Matching saves reuse the
|
|
||||||
/// encoded DWG thumbnail instead of rescanning every resident wire.
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub(super) thumbnail_cache_key: Option<super::ThumbnailCacheKey>,
|
|
||||||
/// Sentinel "Welcome / Start" tab. Always at index 0 when present.
|
/// Sentinel "Welcome / Start" tab. Always at index 0 when present.
|
||||||
/// Cannot be closed; the viewport area renders a welcome page instead
|
/// Cannot be closed; the viewport area renders a welcome page instead
|
||||||
/// of the model-space shader. The scene is still constructed so the
|
/// of the model-space shader. The scene is still constructed so the
|
||||||
|
|
@ -260,12 +256,11 @@ impl DocumentTab {
|
||||||
.unwrap_or(glam::DVec3::ZERO)
|
.unwrap_or(glam::DVec3::ZERO)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// True when the active pane edits **model-space** geometry: the Model tab,
|
/// True when the active pane has a drawing coordinate plane.
|
||||||
/// or inside a floating viewport (MSPACE). The UCS applies in both; plain
|
|
||||||
/// paper space (no active viewport) is excluded. Single predicate so every
|
|
||||||
/// UCS-aware system shares one rule. [[feedback_shared_infra]]
|
|
||||||
pub(super) fn editing_model_space(&self) -> bool {
|
pub(super) fn editing_model_space(&self) -> bool {
|
||||||
self.scene.current_layout == "Model" || self.scene.active_viewport.is_some()
|
self.scene.current_layout == "Model"
|
||||||
|
|| self.scene.active_viewport.is_some()
|
||||||
|
|| self.active_ucs.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The document's saved model-space UCS (header), as a `Ucs`. `None` when it
|
/// The document's saved model-space UCS (header), as a `Ucs`. `None` when it
|
||||||
|
|
@ -325,9 +320,51 @@ impl DocumentTab {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn ucs_from_layout(&self) -> Option<Ucs> {
|
||||||
|
let layout = self.scene.document.objects.values().find_map(|object| {
|
||||||
|
let acadrust::objects::ObjectType::Layout(layout) = object else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
(layout.name == self.scene.current_layout).then_some(layout)
|
||||||
|
})?;
|
||||||
|
let mut ucs = self
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.ucss
|
||||||
|
.iter()
|
||||||
|
.find(|ucs| ucs.handle == layout.named_ucs)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| Ucs::new("*PAPERUCS*"));
|
||||||
|
ucs.origin = acadrust::types::Vector3::new(
|
||||||
|
layout.ucs_origin.0,
|
||||||
|
layout.ucs_origin.1,
|
||||||
|
layout.ucs_origin.2,
|
||||||
|
);
|
||||||
|
ucs.x_axis = acadrust::types::Vector3::new(
|
||||||
|
layout.ucs_x_axis.0,
|
||||||
|
layout.ucs_x_axis.1,
|
||||||
|
layout.ucs_x_axis.2,
|
||||||
|
);
|
||||||
|
ucs.y_axis = acadrust::types::Vector3::new(
|
||||||
|
layout.ucs_y_axis.0,
|
||||||
|
layout.ucs_y_axis.1,
|
||||||
|
layout.ucs_y_axis.2,
|
||||||
|
);
|
||||||
|
ucs.elevation = layout.elevation;
|
||||||
|
ucs.ortho_type = layout.ucs_ortho_type;
|
||||||
|
ucs.named_ucs_handle = layout.named_ucs;
|
||||||
|
ucs.base_ucs_handle = layout.base_ucs;
|
||||||
|
let is_world = layout.named_ucs.is_null()
|
||||||
|
&& layout.base_ucs.is_null()
|
||||||
|
&& layout.ucs_ortho_type == 0
|
||||||
|
&& layout.elevation.abs() <= f64::EPSILON
|
||||||
|
&& super::helpers::UcsXform::from_ucs(&ucs).is_identity();
|
||||||
|
(!is_world).then_some(ucs)
|
||||||
|
}
|
||||||
|
|
||||||
/// Set `active_ucs` to the UCS of the *current pane*: the entered viewport's
|
/// Set `active_ucs` to the UCS of the *current pane*: the entered viewport's
|
||||||
/// own per-viewport UCS, the model header UCS in the Model tab, or none in
|
/// own per-viewport UCS, the model header UCS, or the layout UCS. Keeps the
|
||||||
/// plain paper space. Keeps the ViewCube in lock-step. Call on every pane
|
/// ViewCube in lock-step. Call on every pane
|
||||||
/// change (enter/exit viewport, layout / tab switch, load) so one field
|
/// change (enter/exit viewport, layout / tab switch, load) so one field
|
||||||
/// drives all UCS-aware systems regardless of where editing happens.
|
/// drives all UCS-aware systems regardless of where editing happens.
|
||||||
pub(super) fn refresh_active_ucs(&mut self) {
|
pub(super) fn refresh_active_ucs(&mut self) {
|
||||||
|
|
@ -338,15 +375,52 @@ impl DocumentTab {
|
||||||
} else if self.scene.current_layout == "Model" {
|
} else if self.scene.current_layout == "Model" {
|
||||||
self.model_ucs_from_header()
|
self.model_ucs_from_header()
|
||||||
} else {
|
} else {
|
||||||
None
|
self.ucs_from_layout()
|
||||||
};
|
};
|
||||||
|
if self.active_block_edit.is_none()
|
||||||
|
&& self.scene.active_viewport.is_none()
|
||||||
|
&& self.scene.current_layout != "Model"
|
||||||
|
{
|
||||||
|
self.sync_paper_ucs_header();
|
||||||
|
}
|
||||||
self.sync_ucs_to_scene();
|
self.sync_ucs_to_scene();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sync_paper_ucs_header(&mut self) {
|
||||||
|
use acadrust::types::Vector3;
|
||||||
|
let header = &mut self.scene.document.header;
|
||||||
|
match &self.active_ucs {
|
||||||
|
Some(ucs) => {
|
||||||
|
header.paper_space_ucs_origin = ucs.origin;
|
||||||
|
header.paper_space_ucs_x_axis = ucs.x_axis;
|
||||||
|
header.paper_space_ucs_y_axis = ucs.y_axis;
|
||||||
|
header.paper_elevation = ucs.elevation;
|
||||||
|
header.paper_space_ucs_name = if (ucs.named_ucs_handle.is_valid()
|
||||||
|
|| ucs.handle.is_valid())
|
||||||
|
&& !ucs.name.starts_with('*')
|
||||||
|
{
|
||||||
|
ucs.name.clone()
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
header.paper_ucs_ortho_ref = ucs.base_ucs_handle;
|
||||||
|
header.paper_ucs_ortho_view = ucs.ortho_type;
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
header.paper_space_ucs_origin = Vector3::ZERO;
|
||||||
|
header.paper_space_ucs_x_axis = Vector3::UNIT_X;
|
||||||
|
header.paper_space_ucs_y_axis = Vector3::UNIT_Y;
|
||||||
|
header.paper_elevation = 0.0;
|
||||||
|
header.paper_space_ucs_name.clear();
|
||||||
|
header.paper_ucs_ortho_ref = Handle::NULL;
|
||||||
|
header.paper_ucs_ortho_view = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Persist `active_ucs` back to its pane's storage so it round-trips: the
|
/// Persist `active_ucs` back to its pane's storage so it round-trips: the
|
||||||
/// entered viewport's per-viewport UCS fields, or the document header's
|
/// entered viewport's per-viewport UCS fields, or the document header's
|
||||||
/// model-space UCS in the Model tab. No-op in plain paper space. Call after
|
/// model-space UCS in the Model tab, or the layout UCS. Call after a change.
|
||||||
/// any UCS change.
|
|
||||||
pub(super) fn persist_active_ucs(&mut self) {
|
pub(super) fn persist_active_ucs(&mut self) {
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
if let Some(index) = self.active_block_edit {
|
if let Some(index) = self.active_block_edit {
|
||||||
|
|
@ -389,6 +463,48 @@ impl DocumentTab {
|
||||||
h.model_space_ucs_name.clear();
|
h.model_space_ucs_name.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let (origin, x_axis, y_axis, elevation, ortho, named, base) =
|
||||||
|
match &self.active_ucs {
|
||||||
|
Some(ucs) => (
|
||||||
|
ucs.origin,
|
||||||
|
ucs.x_axis,
|
||||||
|
ucs.y_axis,
|
||||||
|
ucs.elevation,
|
||||||
|
ucs.ortho_type,
|
||||||
|
if ucs.named_ucs_handle.is_valid() {
|
||||||
|
ucs.named_ucs_handle
|
||||||
|
} else {
|
||||||
|
ucs.handle
|
||||||
|
},
|
||||||
|
ucs.base_ucs_handle,
|
||||||
|
),
|
||||||
|
None => (
|
||||||
|
Vector3::ZERO,
|
||||||
|
Vector3::UNIT_X,
|
||||||
|
Vector3::UNIT_Y,
|
||||||
|
0.0,
|
||||||
|
0,
|
||||||
|
Handle::NULL,
|
||||||
|
Handle::NULL,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
for object in self.scene.document.objects.values_mut() {
|
||||||
|
let acadrust::objects::ObjectType::Layout(layout) = object else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if layout.name == self.scene.current_layout {
|
||||||
|
layout.ucs_origin = (origin.x, origin.y, origin.z);
|
||||||
|
layout.ucs_x_axis = (x_axis.x, x_axis.y, x_axis.z);
|
||||||
|
layout.ucs_y_axis = (y_axis.x, y_axis.y, y_axis.z);
|
||||||
|
layout.elevation = elevation;
|
||||||
|
layout.ucs_ortho_type = ortho;
|
||||||
|
layout.named_ucs = named;
|
||||||
|
layout.base_ucs = base;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.sync_paper_ucs_header();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -486,8 +602,6 @@ impl DocumentTab {
|
||||||
active_block_edit: None,
|
active_block_edit: None,
|
||||||
active_mleader_style: "Standard".to_string(),
|
active_mleader_style: "Standard".to_string(),
|
||||||
last_synced_camera_gen: 0,
|
last_synced_camera_gen: 0,
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
thumbnail_cache_key: None,
|
|
||||||
is_start: false,
|
is_start: false,
|
||||||
pan_mode: false,
|
pan_mode: false,
|
||||||
orbit_mode: false,
|
orbit_mode: false,
|
||||||
|
|
|
||||||
|
|
@ -143,15 +143,22 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let active_mleader = self.tabs[i].active_mleader_style.clone();
|
let current_mleader =
|
||||||
let active_mleader = if mleader_names.contains(&active_mleader) {
|
doc.header.current_mleader_style_name.clone();
|
||||||
active_mleader
|
|
||||||
} else {
|
let active_mleader =
|
||||||
mleader_names
|
mleader_names
|
||||||
.first()
|
.iter()
|
||||||
|
.find(|name| {
|
||||||
|
name.eq_ignore_ascii_case(
|
||||||
|
¤t_mleader
|
||||||
|
)
|
||||||
|
})
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default()
|
.or_else(|| {
|
||||||
};
|
mleader_names.first().cloned()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
let table_names: Vec<String> = doc
|
let table_names: Vec<String> = doc
|
||||||
.objects
|
.objects
|
||||||
|
|
|
||||||
133
src/app/mod.rs
|
|
@ -22,6 +22,7 @@ pub(crate) mod settings;
|
||||||
mod shortcuts;
|
mod shortcuts;
|
||||||
mod style_ops;
|
mod style_ops;
|
||||||
mod text_inline;
|
mod text_inline;
|
||||||
|
mod tolerance_dialog;
|
||||||
mod update;
|
mod update;
|
||||||
mod view;
|
mod view;
|
||||||
mod visibility;
|
mod visibility;
|
||||||
|
|
@ -399,6 +400,8 @@ pub(super) struct OpenCADStudio {
|
||||||
layer_translator: Option<crate::ui::window::layer_translator::State>,
|
layer_translator: Option<crate::ui::window::layer_translator::State>,
|
||||||
/// Working copy of the Drawing Units dialog; `None` while it is closed.
|
/// Working copy of the Drawing Units dialog; `None` while it is closed.
|
||||||
drawing_units: Option<crate::ui::window::drawing_units::State>,
|
drawing_units: Option<crate::ui::window::drawing_units::State>,
|
||||||
|
/// Working copy of the structured feature-control-frame editor.
|
||||||
|
geometric_tolerance: Option<crate::ui::window::geometric_tolerance::State>,
|
||||||
/// PICKDRAG (#226): false (default) = press-drag lassoes; true =
|
/// PICKDRAG (#226): false (default) = press-drag lassoes; true =
|
||||||
/// press-drag draws a rectangle marquee.
|
/// press-drag draws a rectangle marquee.
|
||||||
pick_drag_rect: bool,
|
pick_drag_rect: bool,
|
||||||
|
|
@ -477,6 +480,18 @@ pub(super) struct OpenCADStudio {
|
||||||
/// The `a` channel is always 1.0.
|
/// The `a` channel is always 1.0.
|
||||||
default_bg_color: Option<[f32; 4]>,
|
default_bg_color: Option<[f32; 4]>,
|
||||||
default_paper_bg_color: Option<[f32; 4]>,
|
default_paper_bg_color: Option<[f32; 4]>,
|
||||||
|
/// CLIPROMPTLINES: how many temporary prompt lines for a single command
|
||||||
|
/// are displayed above the command window (0–50, Registry, default 3).
|
||||||
|
cliprompt_lines: i32,
|
||||||
|
/// MRU list of block names inserted via INSERT, most recent first, capped to 20.
|
||||||
|
block_mru: Vec<String>,
|
||||||
|
/// Insertion frequency per block name (uppercase key → count), capped.
|
||||||
|
block_freq: std::collections::HashMap<String, u32>,
|
||||||
|
/// Last time block-usage was flushed to disk (debounce per 2.4).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
block_usage_last_persist: Option<std::time::Instant>,
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
block_usage_last_persist: Option<()>,
|
||||||
/// `true` after a bare `VPORTS` in model space — the next command-line
|
/// `true` after a bare `VPORTS` in model space — the next command-line
|
||||||
/// entry is treated as the tiled-config option (SIngle/2H/2V/4).
|
/// entry is treated as the tiled-config option (SIngle/2H/2V/4).
|
||||||
awaiting_vports: bool,
|
awaiting_vports: bool,
|
||||||
|
|
@ -608,6 +623,12 @@ pub(super) struct OpenCADStudio {
|
||||||
/// OS window Id for the floating Layer Properties Manager (None when closed).
|
/// OS window Id for the floating Layer Properties Manager (None when closed).
|
||||||
/// OS window Id of the primary application window.
|
/// OS window Id of the primary application window.
|
||||||
main_window: Option<window::Id>,
|
main_window: Option<window::Id>,
|
||||||
|
/// Hides drawing UI overlays for one thumbnail capture frame.
|
||||||
|
thumbnail_capture_clean: bool,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pending_native_thumbnail_save: Option<PendingNativeThumbnailSave>,
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pending_web_thumbnail_save: Option<PendingWebThumbnailSave>,
|
||||||
// ── Floating panel windows ────────────────────────────────────────────
|
// ── Floating panel windows ────────────────────────────────────────────
|
||||||
/// Active `iced_aw` colour picker: destination plus its initial colour.
|
/// Active `iced_aw` colour picker: destination plus its initial colour.
|
||||||
color_pick_target: Option<(ColorPickTarget, AcadColor)>,
|
color_pick_target: Option<(ColorPickTarget, AcadColor)>,
|
||||||
|
|
@ -673,6 +694,10 @@ pub(super) struct OpenCADStudio {
|
||||||
/// keep their manifest listed but drop their ribbon tab and command
|
/// keep their manifest listed but drop their ribbon tab and command
|
||||||
/// dispatch. Persisted via [`settings::UserSettings::disabled_plugins`].
|
/// dispatch. Persisted via [`settings::UserSettings::disabled_plugins`].
|
||||||
disabled_plugins: rustc_hash::FxHashSet<String>,
|
disabled_plugins: rustc_hash::FxHashSet<String>,
|
||||||
|
/// `(tab id, selection fingerprint)` last broadcast to V4 plugins, so
|
||||||
|
/// `SelectionChangedV4` fires once per real change rather than per message.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
last_plugin_selection: Option<(u64, u64)>,
|
||||||
/// External add-on packages found in the plugins folder, refreshed when the
|
/// External add-on packages found in the plugins folder, refreshed when the
|
||||||
/// Plugin Manager opens.
|
/// Plugin Manager opens.
|
||||||
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
|
external_plugins: Vec<crate::plugin::external::ExternalPlugin>,
|
||||||
|
|
@ -758,6 +783,9 @@ pub(super) struct OpenCADStudio {
|
||||||
ctrl_down: bool,
|
ctrl_down: bool,
|
||||||
/// Open in-place MText editor (toolbar + text area + live preview), if any.
|
/// Open in-place MText editor (toolbar + text area + live preview), if any.
|
||||||
mtext_editor: Option<mtext_editor::MTextEditorState>,
|
mtext_editor: Option<mtext_editor::MTextEditorState>,
|
||||||
|
/// Return rich text to a suspended drawing command.
|
||||||
|
command_mtext_input: bool,
|
||||||
|
pending_command_editor_text: Option<String>,
|
||||||
/// Open in-place single-line TEXT editor (plain text-entry box), if any.
|
/// Open in-place single-line TEXT editor (plain text-entry box), if any.
|
||||||
text_inline: Option<text_inline::TextInlineState>,
|
text_inline: Option<text_inline::TextInlineState>,
|
||||||
/// Cursor-anchored one-shot snap override menu (Shift+RMB): the canvas
|
/// Cursor-anchored one-shot snap override menu (Shift+RMB): the canvas
|
||||||
|
|
@ -789,16 +817,21 @@ pub(super) struct OpenCADStudio {
|
||||||
/// Snapshot of the dialog's settings taken when it opened, restored by the
|
/// Snapshot of the dialog's settings taken when it opened, restored by the
|
||||||
/// `<previous>` list entry.
|
/// `<previous>` list entry.
|
||||||
plot_prev: Option<crate::ui::window::plot::PlotDialogState>,
|
plot_prev: Option<crate::ui::window::plot::PlotDialogState>,
|
||||||
|
/// Full source settings behind the fields currently shown in Plot.
|
||||||
|
plot_setup_template: Option<acadrust::objects::PlotSettings>,
|
||||||
/// Paper layouts shown by Print All, in tab order with their selection.
|
/// Paper layouts shown by Print All, in tab order with their selection.
|
||||||
print_all_layouts: Vec<(String, bool)>,
|
print_all_layouts: Vec<(String, bool)>,
|
||||||
/// True while the Plot dialog is editing settings for Print All.
|
/// True while the Plot dialog is editing settings for Print All.
|
||||||
print_all_options: bool,
|
print_all_options: bool,
|
||||||
|
/// True when Print All should override each layout's page setup.
|
||||||
|
print_all_settings_override: bool,
|
||||||
/// Settings restored when the Print All options dialog is cancelled.
|
/// Settings restored when the Print All options dialog is cancelled.
|
||||||
print_all_options_prev: Option<crate::ui::window::plot::PlotDialogState>,
|
print_all_options_prev: Option<crate::ui::window::plot::PlotDialogState>,
|
||||||
/// Plot style restored together with cancelled Print All options.
|
/// Plot style restored together with cancelled Print All options.
|
||||||
print_all_plot_style_prev: Option<Option<crate::io::plot_style::PlotStyleTable>>,
|
print_all_plot_style_prev: Option<Option<crate::io::plot_style::PlotStyleTable>>,
|
||||||
/// Plot window restored together with cancelled Print All options.
|
/// Plot window restored together with cancelled Print All options.
|
||||||
print_all_plot_window_prev: Option<Option<(f64, f64, f64, f64)>>,
|
print_all_plot_window_prev: Option<Option<(f64, f64, f64, f64)>>,
|
||||||
|
print_all_plot_setup_prev: Option<Option<acadrust::objects::PlotSettings>>,
|
||||||
|
|
||||||
// ── Plot Style Table ──────────────────────────────────────────────────
|
// ── Plot Style Table ──────────────────────────────────────────────────
|
||||||
/// Currently loaded CTB/STB table (None = no override).
|
/// Currently loaded CTB/STB table (None = no override).
|
||||||
|
|
@ -1135,6 +1168,28 @@ pub(super) enum SaveContinuation {
|
||||||
Quit,
|
Quit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(super) struct PendingNativeThumbnailSave {
|
||||||
|
tab_id: u64,
|
||||||
|
path: PathBuf,
|
||||||
|
version: acadrust::DxfVersion,
|
||||||
|
purpose: SavePurpose,
|
||||||
|
continuation: SaveContinuation,
|
||||||
|
set_current_path: bool,
|
||||||
|
check_external_change: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pub(super) struct PendingWebThumbnailSave {
|
||||||
|
tab_id: u64,
|
||||||
|
filename: String,
|
||||||
|
ext: String,
|
||||||
|
version: acadrust::DxfVersion,
|
||||||
|
bounds: iced::Rectangle,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub(super) struct PendingSaveFailure {
|
pub(super) struct PendingSaveFailure {
|
||||||
|
|
@ -1158,16 +1213,6 @@ pub(super) struct PendingExternalChange {
|
||||||
set_current_path: bool,
|
set_current_path: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
pub(super) struct ThumbnailCacheKey {
|
|
||||||
epoch: u64,
|
|
||||||
camera_generation: u64,
|
|
||||||
bg_color: [u32; 4],
|
|
||||||
png: bool,
|
|
||||||
viewport: [u32; 2],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub struct SaveOutcome {
|
pub struct SaveOutcome {
|
||||||
|
|
@ -1182,7 +1227,6 @@ pub struct SaveOutcome {
|
||||||
set_current_path: bool,
|
set_current_path: bool,
|
||||||
purpose: SavePurpose,
|
purpose: SavePurpose,
|
||||||
continuation: SaveContinuation,
|
continuation: SaveContinuation,
|
||||||
thumbnail_key: Option<ThumbnailCacheKey>,
|
|
||||||
refreshed_preview: Option<Option<acadrust::Preview>>,
|
refreshed_preview: Option<Option<acadrust::Preview>>,
|
||||||
result: Result<(), crate::io::SaveFailure>,
|
result: Result<(), crate::io::SaveFailure>,
|
||||||
}
|
}
|
||||||
|
|
@ -1205,6 +1249,8 @@ pub enum ColorPickTarget {
|
||||||
Properties,
|
Properties,
|
||||||
/// Selected entities' background colour (hatch / MTEXT background row).
|
/// Selected entities' background colour (hatch / MTEXT background row).
|
||||||
PropertiesBg,
|
PropertiesBg,
|
||||||
|
/// A named per-entity Properties colour field.
|
||||||
|
PropertiesField(String),
|
||||||
/// Current creation colour (ribbon).
|
/// Current creation colour (ribbon).
|
||||||
Ribbon,
|
Ribbon,
|
||||||
/// A layer's colour, by panel row index.
|
/// A layer's colour, by panel row index.
|
||||||
|
|
@ -1553,6 +1599,7 @@ pub enum ModalKind {
|
||||||
LayerStateManager,
|
LayerStateManager,
|
||||||
LayerTranslator,
|
LayerTranslator,
|
||||||
DrawingUnits,
|
DrawingUnits,
|
||||||
|
GeometricTolerance,
|
||||||
DraftingSettings,
|
DraftingSettings,
|
||||||
LayerStateEditor,
|
LayerStateEditor,
|
||||||
Plot,
|
Plot,
|
||||||
|
|
@ -1937,9 +1984,23 @@ pub enum Message {
|
||||||
AecDropBack,
|
AecDropBack,
|
||||||
/// Periodic autosave tick — write `.sv$` recovery files for dirty tabs.
|
/// Periodic autosave tick — write `.sv$` recovery files for dirty tabs.
|
||||||
AutoSave,
|
AutoSave,
|
||||||
|
/// A clean viewport frame is ready for thumbnail capture.
|
||||||
|
ThumbnailCaptureFrame,
|
||||||
|
/// Restore drawing UI after the compositor screenshot is captured.
|
||||||
|
ThumbnailCaptureFinished,
|
||||||
/// Native background save/autosave completed.
|
/// Native background save/autosave completed.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
SaveFinished(SaveOutcome),
|
SaveFinished(SaveOutcome),
|
||||||
|
/// Web viewport capture completed; serialize and download the drawing.
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
WebSaveScreenshot {
|
||||||
|
tab_id: u64,
|
||||||
|
filename: String,
|
||||||
|
ext: String,
|
||||||
|
version: acadrust::DxfVersion,
|
||||||
|
bounds: Option<iced::Rectangle>,
|
||||||
|
screenshot: Option<iced::window::Screenshot>,
|
||||||
|
},
|
||||||
/// Retry the failed save after the other application releases the file.
|
/// Retry the failed save after the other application releases the file.
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
SaveFileInUseRetry,
|
SaveFileInUseRetry,
|
||||||
|
|
@ -2287,6 +2348,14 @@ pub enum Message {
|
||||||
DrawingUnitsField(crate::ui::window::drawing_units::Field),
|
DrawingUnitsField(crate::ui::window::drawing_units::Field),
|
||||||
/// Drawing Units OK — write the working copy into the drawing.
|
/// Drawing Units OK — write the working copy into the drawing.
|
||||||
DrawingUnitsApply,
|
DrawingUnitsApply,
|
||||||
|
/// One structured feature-control-frame field changed.
|
||||||
|
ToleranceDialogField(crate::ui::window::geometric_tolerance::Field),
|
||||||
|
/// One structured feature-control-frame option changed.
|
||||||
|
ToleranceDialogToggle(crate::ui::window::geometric_tolerance::Toggle),
|
||||||
|
/// Apply edits without closing the structured editor.
|
||||||
|
ToleranceDialogApply,
|
||||||
|
/// Commit edits or continue to insertion-point placement.
|
||||||
|
ToleranceDialogOk,
|
||||||
/// Toggle the Isolate pill's action menu open/closed.
|
/// Toggle the Isolate pill's action menu open/closed.
|
||||||
ToggleIsolatePopup,
|
ToggleIsolatePopup,
|
||||||
/// Close the Isolate action menu.
|
/// Close the Isolate action menu.
|
||||||
|
|
@ -2624,6 +2693,31 @@ pub enum Message {
|
||||||
MTextWidth(String),
|
MTextWidth(String),
|
||||||
/// Toolbar character-spacing field changed.
|
/// Toolbar character-spacing field changed.
|
||||||
MTextCharSpace(String),
|
MTextCharSpace(String),
|
||||||
|
/// Undo / redo editor text and inline-format operations.
|
||||||
|
MTextUndo,
|
||||||
|
MTextRedo,
|
||||||
|
/// Convert the selected numerator/separator/denominator to a stacked run.
|
||||||
|
MTextStack,
|
||||||
|
/// Remove inline character formatting from the selection (or all text).
|
||||||
|
MTextClearFormatting,
|
||||||
|
/// Insert a predefined symbol or field token at the caret.
|
||||||
|
MTextInsert(String),
|
||||||
|
/// Per-object annotation flag edited from the text toolbar.
|
||||||
|
MTextAnnotative(bool),
|
||||||
|
/// Column layout controls.
|
||||||
|
MTextColumnMode(String),
|
||||||
|
MTextColumnCount(String),
|
||||||
|
MTextColumnWidth(String),
|
||||||
|
MTextColumnGutter(String),
|
||||||
|
MTextColumnHeight(String),
|
||||||
|
MTextColumnFlowReversed(bool),
|
||||||
|
/// Paragraph indent/spacing controls.
|
||||||
|
MTextParagraphNumber(mtext_editor::ParaNumber, String),
|
||||||
|
MTextFindText(String),
|
||||||
|
MTextReplaceText(String),
|
||||||
|
MTextFindNext,
|
||||||
|
MTextReplaceNext,
|
||||||
|
MTextReplaceAll,
|
||||||
/// Toolbar colour picker (same widget as Properties) — applies to the
|
/// Toolbar colour picker (same widget as Properties) — applies to the
|
||||||
/// selection, or the whole text when nothing is selected.
|
/// selection, or the whole text when nothing is selected.
|
||||||
MTextColorChanged(AcadColor),
|
MTextColorChanged(AcadColor),
|
||||||
|
|
@ -3114,6 +3208,7 @@ impl OpenCADStudio {
|
||||||
last_layer_translation: None,
|
last_layer_translation: None,
|
||||||
layer_translator: None,
|
layer_translator: None,
|
||||||
drawing_units: None,
|
drawing_units: None,
|
||||||
|
geometric_tolerance: None,
|
||||||
pick_drag_rect: false,
|
pick_drag_rect: false,
|
||||||
perf_hud: false,
|
perf_hud: false,
|
||||||
cycle_candidates: None,
|
cycle_candidates: None,
|
||||||
|
|
@ -3142,6 +3237,10 @@ impl OpenCADStudio {
|
||||||
savetime_min: 10,
|
savetime_min: 10,
|
||||||
default_bg_color: None,
|
default_bg_color: None,
|
||||||
default_paper_bg_color: None,
|
default_paper_bg_color: None,
|
||||||
|
cliprompt_lines: 3,
|
||||||
|
block_mru: Vec::new(),
|
||||||
|
block_freq: std::collections::HashMap::new(),
|
||||||
|
block_usage_last_persist: None,
|
||||||
awaiting_vports: false,
|
awaiting_vports: false,
|
||||||
pending_setvar: None,
|
pending_setvar: None,
|
||||||
ucs_icon_hover: false,
|
ucs_icon_hover: false,
|
||||||
|
|
@ -3183,6 +3282,11 @@ impl OpenCADStudio {
|
||||||
show_layout_tabs: true,
|
show_layout_tabs: true,
|
||||||
last_point: None,
|
last_point: None,
|
||||||
main_window: None,
|
main_window: None,
|
||||||
|
thumbnail_capture_clean: false,
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pending_native_thumbnail_save: None,
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
pending_web_thumbnail_save: None,
|
||||||
color_pick_target: None,
|
color_pick_target: None,
|
||||||
color_picker_tab: ColorPickerTab::Index,
|
color_picker_tab: ColorPickerTab::Index,
|
||||||
recent_colors: Vec::new(),
|
recent_colors: Vec::new(),
|
||||||
|
|
@ -3206,6 +3310,8 @@ impl OpenCADStudio {
|
||||||
attr_editor_tab: crate::ui::window::attribute_editor::AttrTab::Attribute,
|
attr_editor_tab: crate::ui::window::attribute_editor::AttrTab::Attribute,
|
||||||
attr_editor_selected: 0,
|
attr_editor_selected: 0,
|
||||||
disabled_plugins: rustc_hash::FxHashSet::default(),
|
disabled_plugins: rustc_hash::FxHashSet::default(),
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
last_plugin_selection: None,
|
||||||
external_plugins: Vec::new(),
|
external_plugins: Vec::new(),
|
||||||
loaded_plugin_ids: rustc_hash::FxHashSet::default(),
|
loaded_plugin_ids: rustc_hash::FxHashSet::default(),
|
||||||
plugin_load_errors: rustc_hash::FxHashMap::default(),
|
plugin_load_errors: rustc_hash::FxHashMap::default(),
|
||||||
|
|
@ -3236,6 +3342,8 @@ impl OpenCADStudio {
|
||||||
ctrl_down: false,
|
ctrl_down: false,
|
||||||
cont_anchor: None,
|
cont_anchor: None,
|
||||||
mtext_editor: None,
|
mtext_editor: None,
|
||||||
|
command_mtext_input: false,
|
||||||
|
pending_command_editor_text: None,
|
||||||
text_inline: None,
|
text_inline: None,
|
||||||
snap_override_popup: None,
|
snap_override_popup: None,
|
||||||
axis_lock_dir: None,
|
axis_lock_dir: None,
|
||||||
|
|
@ -3250,11 +3358,14 @@ impl OpenCADStudio {
|
||||||
plot_orientation: crate::io::paper_sizes::Orientation::Landscape,
|
plot_orientation: crate::io::paper_sizes::Orientation::Landscape,
|
||||||
plot_dialog: crate::ui::window::plot::PlotDialogState::default(),
|
plot_dialog: crate::ui::window::plot::PlotDialogState::default(),
|
||||||
plot_prev: None,
|
plot_prev: None,
|
||||||
|
plot_setup_template: None,
|
||||||
print_all_layouts: Vec::new(),
|
print_all_layouts: Vec::new(),
|
||||||
print_all_options: false,
|
print_all_options: false,
|
||||||
|
print_all_settings_override: false,
|
||||||
print_all_options_prev: None,
|
print_all_options_prev: None,
|
||||||
print_all_plot_style_prev: None,
|
print_all_plot_style_prev: None,
|
||||||
print_all_plot_window_prev: None,
|
print_all_plot_window_prev: None,
|
||||||
|
print_all_plot_setup_prev: None,
|
||||||
opening: None,
|
opening: None,
|
||||||
open_job_serial: 0,
|
open_job_serial: 0,
|
||||||
recovery_report: None,
|
recovery_report: None,
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
// 3D solid modelling support on the App: committing Model-tab primitives,
|
// Kernel B-rep solid modelling and exact ACIS persistence.
|
||||||
// and the Design-group boolean operations over the scene's session-cached
|
|
||||||
// B-reps.
|
|
||||||
//
|
|
||||||
// The bodies come from the geometry kernel, so every operation here is on
|
|
||||||
// analytic surfaces rather than on facets — a turned cylinder is still a
|
|
||||||
// cylinder afterwards, and saves as one.
|
|
||||||
|
|
||||||
use acadrust::entities::Solid3D;
|
use acadrust::{
|
||||||
use acadrust::objects::SolidHistoryOperation;
|
entities::Solid3D, objects::SolidHistoryOperation, EntityType, Handle,
|
||||||
|
};
|
||||||
use cadkernel::brep::Body;
|
use cadkernel::brep::Body;
|
||||||
use acadrust::{EntityType, Handle};
|
|
||||||
use iced::Task;
|
use iced::Task;
|
||||||
|
|
||||||
use super::Message;
|
use super::Message;
|
||||||
|
|
@ -18,16 +12,25 @@ use crate::scene::model::solid_history;
|
||||||
use crate::scene::model::solid_model::{self, Bool};
|
use crate::scene::model::solid_model::{self, Bool};
|
||||||
|
|
||||||
impl super::OpenCADStudio {
|
impl super::OpenCADStudio {
|
||||||
/// Commit a Model-tab solid: add its acadrust entity to the document, then
|
/// Add a solid and register its persistent B-rep.
|
||||||
/// register the B-rep (caches it for booleans + tessellates it into the
|
|
||||||
/// shaded mesh pipeline). Returns the new entity handle.
|
|
||||||
pub(super) fn add_solid_model(
|
pub(super) fn add_solid_model(
|
||||||
&mut self,
|
&mut self,
|
||||||
entity: EntityType,
|
mut entity: EntityType,
|
||||||
solid: Body,
|
solid: Body,
|
||||||
history: SolidHistoryOperation,
|
history: SolidHistoryOperation,
|
||||||
) -> Handle {
|
) -> Handle {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
|
let EntityType::Solid3D(inner) = &mut entity else {
|
||||||
|
return Handle::NULL;
|
||||||
|
};
|
||||||
|
inner.wires = solid_model::edge_wires(&solid);
|
||||||
|
let Some(document) = crate::scene::convert::acis_export::solid_to_sat(&solid)
|
||||||
|
else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("The solid could not be encoded as ACIS.").as_ref());
|
||||||
|
return Handle::NULL;
|
||||||
|
};
|
||||||
|
inner.set_sat_document(&document);
|
||||||
let Some(handle) = self.commit_entity_handle(entity) else {
|
let Some(handle) = self.commit_entity_handle(entity) else {
|
||||||
return Handle::NULL;
|
return Handle::NULL;
|
||||||
};
|
};
|
||||||
|
|
@ -36,42 +39,202 @@ impl super::OpenCADStudio {
|
||||||
handle
|
handle
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a boolean (`union` / `subtract` / `intersect`) on exactly two
|
fn selected_solid_handles(&mut self) -> Vec<Handle> {
|
||||||
/// selected solids whose B-reps are in the session cache.
|
|
||||||
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
// Selected entities that have a cached B-rep.
|
let mut handles: Vec<Handle> = self.tabs[i]
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
|
||||||
.scene
|
.scene
|
||||||
.selected_handles_in_order()
|
.selected_handles_in_order()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
.filter(|handle| !self.tabs[i].scene.is_layer_locked(*handle))
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
.filter(|handle| {
|
||||||
|
matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(*handle),
|
||||||
|
Some(EntityType::Solid3D(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
if handles.len() != 2 {
|
self.tabs[i].scene.restore_solid_models(&handles);
|
||||||
|
handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
|
||||||
|
handles
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_solid_body(&mut self, handle: Handle, result: Body, label: &str) -> bool {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let Some(document) =
|
||||||
|
crate::scene::convert::acis_export::solid_to_sat(&result)
|
||||||
|
else {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("Boolean: select exactly two solids created this session.").as_ref());
|
.push_error(crate::t!("The result could not be encoded as ACIS.").as_ref());
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(EntityType::Solid3D(mut entity)) =
|
||||||
|
self.tabs[i].scene.document.get_entity(handle).cloned()
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
entity.wires = solid_model::edge_wires(&result);
|
||||||
|
entity.silhouettes.clear();
|
||||||
|
entity.history_handle = None;
|
||||||
|
entity.set_sat_document(&document);
|
||||||
|
|
||||||
|
self.push_undo_snapshot(i, label);
|
||||||
|
self.tabs[i].scene.delete_solid_history(handle);
|
||||||
|
if !self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.update_entity(EntityType::Solid3D(entity))
|
||||||
|
{
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("The solid could not be updated.").as_ref());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let history = solid_history::brep_op(&result);
|
||||||
|
self.tabs[i].scene.create_solid_history(handle, history);
|
||||||
|
self.tabs[i].scene.register_solid_model(handle, result);
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
|
self.tabs[i].scene.select_entity(handle, false);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn solid_edge_blend(
|
||||||
|
&mut self,
|
||||||
|
handle: Handle,
|
||||||
|
pick: glam::DVec3,
|
||||||
|
value: f64,
|
||||||
|
fillet: bool,
|
||||||
|
) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if self.reject_locked_edit(i, handle) {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
if !matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
Some(EntityType::Solid3D(_))
|
||||||
|
) {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("Select a 3D solid edge.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
self.tabs[i].scene.restore_solid_models(&[handle]);
|
||||||
|
let Some(body) = self.tabs[i].scene.solid_models.get(&handle).cloned() else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("The solid geometry could not be restored.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let Some(edge) = solid_model::nearest_edge(&body, pick.to_array()) else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("Select a solid edge.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let result = if fillet {
|
||||||
|
cadkernel::brep::fillet(&body, edge, value)
|
||||||
|
} else {
|
||||||
|
cadkernel::brep::chamfer(&body, edge, value)
|
||||||
|
};
|
||||||
|
let Some(result) = result else {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("Edge operation failed. Use a convex planar solid and a smaller value.")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let label = if fillet { "SOLIDFILLET" } else { "SOLIDCHAMFER" };
|
||||||
|
if self.replace_solid_body(handle, result, label) {
|
||||||
|
self.command_line
|
||||||
|
.push_output(crate::tf!("{label}: solid updated.").as_ref());
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn solid_face_presspull(
|
||||||
|
&mut self,
|
||||||
|
handle: Handle,
|
||||||
|
pick: glam::DVec3,
|
||||||
|
distance: f64,
|
||||||
|
drag: Option<glam::DVec3>,
|
||||||
|
) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if self.reject_locked_edit(i, handle) {
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
self.tabs[i].scene.restore_solid_models(&[handle]);
|
||||||
|
let Some(body) = self.tabs[i].scene.solid_models.get(&handle).cloned() else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("The solid geometry could not be restored.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let Some(face) = solid_model::nearest_planar_face(&body, pick.to_array()) else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("Select a planar solid face.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
let distance = match drag {
|
||||||
|
Some(point) => {
|
||||||
|
let Some(normal) = solid_model::planar_face_normal(&body, face) else {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("Select a planar solid face.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
(point - pick).dot(glam::DVec3::from_array(normal))
|
||||||
|
}
|
||||||
|
None => distance,
|
||||||
|
};
|
||||||
|
if !distance.is_finite() || distance.abs() <= 1e-6 {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("PRESSPULL: drag along the selected face normal.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
let Some(result) = cadkernel::brep::presspull(&body, face, distance) else {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("PRESSPULL: the face move would collapse or invalidate the solid.")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
if self.replace_solid_body(handle, result, "PRESSPULL") {
|
||||||
|
self.command_line
|
||||||
|
.push_output(crate::t!("PRESSPULL: solid updated.").as_ref());
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run a boolean over the selected solids in selection order.
|
||||||
|
pub(super) fn solid_boolean(&mut self, op: BoolOp) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let handles = self.selected_solid_handles();
|
||||||
|
if handles.len() < 2 {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("Boolean: select at least two solids.").as_ref());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
let a = self.tabs[i].scene.solid_models[&handles[0]].clone();
|
|
||||||
let b = self.tabs[i].scene.solid_models[&handles[1]].clone();
|
|
||||||
let kind = match op {
|
let kind = match op {
|
||||||
BoolOp::Union => Bool::Union,
|
BoolOp::Union => Bool::Union,
|
||||||
BoolOp::Subtract => Bool::Subtract,
|
BoolOp::Subtract => Bool::Subtract,
|
||||||
BoolOp::Intersect => Bool::Intersect,
|
BoolOp::Intersect => Bool::Intersect,
|
||||||
};
|
};
|
||||||
let Some(result) = solid_model::boolean(kind, &a, &b) else {
|
let mut operands = handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| self.tabs[i].scene.solid_models.get(handle).cloned());
|
||||||
|
let mut result = operands.next().expect("at least two restored solids");
|
||||||
|
for operand in operands {
|
||||||
|
let Some(combined) = solid_model::boolean(kind, &result, &operand) else {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("Boolean failed while combining the selected solids.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
result = combined;
|
||||||
|
}
|
||||||
|
if crate::scene::convert::acis_export::solid_to_sat(&result).is_none() {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("Boolean failed — the solids may not overlap.").as_ref());
|
.push_error(crate::t!("The boolean result could not be encoded as ACIS.").as_ref());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
}
|
||||||
|
|
||||||
self.push_undo_snapshot(i, "BOOLEAN");
|
self.push_undo_snapshot(i, "BOOLEAN");
|
||||||
// Remove the two operands (entity + mesh + cached B-rep).
|
|
||||||
self.tabs[i].scene.erase_entities(&handles);
|
self.tabs[i].scene.erase_entities(&handles);
|
||||||
// The result is freshly combined geometry with no ACIS parametrisation,
|
|
||||||
// so it lives as a Solid3D whose render/boolean data is the injected
|
|
||||||
// mesh + cached B-rep; its edge wires make it pickable.
|
|
||||||
let mut s3d = Solid3D::new();
|
let mut s3d = Solid3D::new();
|
||||||
s3d.wires = solid_model::edge_wires(&result);
|
s3d.wires = solid_model::edge_wires(&result);
|
||||||
let history = solid_history::brep_op(&result);
|
let history = solid_history::brep_op(&result);
|
||||||
|
|
@ -85,20 +248,87 @@ impl super::OpenCADStudio {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn solid_subtract(
|
||||||
|
&mut self,
|
||||||
|
bases: &[Handle],
|
||||||
|
cutters: &[Handle],
|
||||||
|
) -> Task<Message> {
|
||||||
|
let i = self.active_tab;
|
||||||
|
let valid_solid = |handle: &Handle| {
|
||||||
|
!self.tabs[i].scene.is_layer_locked(*handle)
|
||||||
|
&& matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(*handle),
|
||||||
|
Some(EntityType::Solid3D(_))
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let mut base_handles: Vec<_> = bases.iter().copied().filter(valid_solid).collect();
|
||||||
|
let mut cutter_handles: Vec<_> = cutters.iter().copied().filter(valid_solid).collect();
|
||||||
|
cutter_handles.retain(|handle| !base_handles.contains(handle));
|
||||||
|
|
||||||
|
let mut operands = base_handles.clone();
|
||||||
|
operands.extend(cutter_handles.iter().copied());
|
||||||
|
self.tabs[i].scene.restore_solid_models(&operands);
|
||||||
|
base_handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
|
||||||
|
cutter_handles.retain(|handle| self.tabs[i].scene.solid_models.contains_key(handle));
|
||||||
|
if base_handles.is_empty() || cutter_handles.is_empty() {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("SUBTRACT: select at least one base solid and one cutter solid.")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut result = self.tabs[i].scene.solid_models[&base_handles[0]].clone();
|
||||||
|
for handle in &base_handles[1..] {
|
||||||
|
let operand = &self.tabs[i].scene.solid_models[handle];
|
||||||
|
let Some(combined) = solid_model::boolean(Bool::Union, &result, operand) else {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("SUBTRACT failed while combining the base solids.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
result = combined;
|
||||||
|
}
|
||||||
|
for handle in &cutter_handles {
|
||||||
|
let operand = &self.tabs[i].scene.solid_models[handle];
|
||||||
|
let Some(difference) = solid_model::boolean(Bool::Subtract, &result, operand) else {
|
||||||
|
self.command_line.push_error(
|
||||||
|
crate::t!("SUBTRACT failed while removing the selected solids.").as_ref(),
|
||||||
|
);
|
||||||
|
return Task::none();
|
||||||
|
};
|
||||||
|
result = difference;
|
||||||
|
}
|
||||||
|
if crate::scene::convert::acis_export::solid_to_sat(&result).is_none() {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("The boolean result could not be encoded as ACIS.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
|
||||||
|
operands = base_handles;
|
||||||
|
operands.extend(cutter_handles);
|
||||||
|
self.push_undo_snapshot(i, "SUBTRACT");
|
||||||
|
self.tabs[i].scene.erase_entities(&operands);
|
||||||
|
let mut entity = Solid3D::new();
|
||||||
|
entity.wires = solid_model::edge_wires(&result);
|
||||||
|
let history = solid_history::brep_op(&result);
|
||||||
|
let handle = self.add_solid_model(EntityType::Solid3D(entity), result, history);
|
||||||
|
self.tabs[i].scene.deselect_all();
|
||||||
|
if !handle.is_null() {
|
||||||
|
self.tabs[i].scene.select_entity(handle, false);
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
/// Slice the one selected solid with an axis-aligned plane (axis 0/1/2 =
|
/// Slice the one selected solid with an axis-aligned plane (axis 0/1/2 =
|
||||||
/// X/Y/Z at `value`), keeping the lower side when `keep_low` is true. The
|
/// X/Y/Z at `value`), keeping the lower side when `keep_low` is true. The
|
||||||
/// kept half is the intersection of the solid with a half-space box, reusing
|
/// kept half is the intersection of the solid with a half-space box, reusing
|
||||||
/// the same boolean path the Design-group tools use.
|
/// the same boolean path as the modelling tools.
|
||||||
pub(super) fn solid_slice(&mut self, axis: usize, value: f64, keep_low: bool) -> Task<Message> {
|
pub(super) fn solid_slice(&mut self, axis: usize, value: f64, keep_low: bool) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("SLICE: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("SLICE: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -167,14 +397,7 @@ impl super::OpenCADStudio {
|
||||||
/// leaving the originals in place (a non-destructive boolean intersect).
|
/// leaving the originals in place (a non-destructive boolean intersect).
|
||||||
pub(super) fn solid_interfere(&mut self) -> Task<Message> {
|
pub(super) fn solid_interfere(&mut self) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 2 {
|
if handles.len() != 2 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("INTERFERE: select exactly two solids created this session.").as_ref());
|
.push_error(crate::t!("INTERFERE: select exactly two solids created this session.").as_ref());
|
||||||
|
|
@ -207,14 +430,7 @@ impl super::OpenCADStudio {
|
||||||
/// orientation, so it reuses the cached B-rep directly.
|
/// orientation, so it reuses the cached B-rep directly.
|
||||||
pub(super) fn solid_rotate3d(&mut self, axis: usize, angle_deg: f64) -> Task<Message> {
|
pub(super) fn solid_rotate3d(&mut self, axis: usize, angle_deg: f64) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("3DROTATE: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("3DROTATE: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -252,63 +468,34 @@ impl super::OpenCADStudio {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// POLYSOLID — build a wall-like solid from a selected polyline: one box per
|
/// POLYSOLID — build a wall solid around an exact polyline offset.
|
||||||
/// segment (oriented along it, `width` wide, `height` tall) unioned together,
|
|
||||||
/// reusing the box primitive + the boolean union path.
|
|
||||||
pub(super) fn solid_polysolid(&mut self, width: f64, height: f64) -> Task<Message> {
|
pub(super) fn solid_polysolid(&mut self, width: f64, height: f64) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let found: Option<(Handle, Vec<[f64; 3]>)> = self.tabs[i]
|
let found: Option<(Handle, EntityType)> = self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.selected_entities()
|
.selected_entities()
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(h, _)| !self.tabs[i].scene.is_layer_locked(*h))
|
.filter(|(h, _)| !self.tabs[i].scene.is_layer_locked(*h))
|
||||||
.find_map(|(h, e)| match e {
|
.find_map(|(h, e)| match e {
|
||||||
EntityType::LwPolyline(_) => crate::entities::curve::entity_curve(e)
|
EntityType::LwPolyline(_) => Some((*h, (*e).clone())),
|
||||||
.map(|curve| (*h, crate::entities::curve::curve_points(&curve))),
|
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
let Some((handle, pts)) = found else {
|
let Some((handle, entity)) = found else {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("POLYSOLID: select a polyline first.").as_ref());
|
.push_error(crate::t!("POLYSOLID: select a polyline first.").as_ref());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
let segs: Vec<([f64; 3], [f64; 3])> = pts.windows(2).map(|w| (w[0], w[1])).collect();
|
let Some(result) = crate::scene::model::sweep_model::polysolid(&entity, width, height)
|
||||||
let mut acc: Option<Body> = None;
|
else {
|
||||||
let mut used = 0usize;
|
|
||||||
for (a, b) in &segs {
|
|
||||||
let (dx, dy) = (b[0] - a[0], b[1] - a[1]);
|
|
||||||
let len = (dx * dx + dy * dy).sqrt();
|
|
||||||
if len < 1e-9 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let angle = dy.atan2(dx);
|
|
||||||
// Local box: X 0..len, Y -w/2..w/2, Z 0..h, then turned to lie
|
|
||||||
// along the segment and moved onto its start.
|
|
||||||
let (sin, cos) = angle.sin_cos();
|
|
||||||
let Some(bx) = solid_model::box_solid([len / 2.0, 0.0, height / 2.0], len, width, height)
|
|
||||||
.and_then(|bx| {
|
|
||||||
solid_model::placed(
|
|
||||||
&bx,
|
|
||||||
[cos, sin, 0.0],
|
|
||||||
[-sin, cos, 0.0],
|
|
||||||
[0.0, 0.0, 1.0],
|
|
||||||
[a[0], a[1], a[2]],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
acc = Some(match acc {
|
|
||||||
None => bx,
|
|
||||||
Some(prev) => solid_model::boolean(Bool::Union, &prev, &bx).unwrap_or(prev),
|
|
||||||
});
|
|
||||||
used += 1;
|
|
||||||
}
|
|
||||||
let Some(result) = acc else {
|
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("POLYSOLID: the polyline has no usable segments.").as_ref());
|
.push_error(crate::t!("POLYSOLID: the kernel could not offset the polyline.").as_ref());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
};
|
};
|
||||||
|
if crate::scene::convert::acis_export::solid_to_sat(&result).is_none() {
|
||||||
|
self.command_line
|
||||||
|
.push_error(crate::t!("POLYSOLID: the result could not be encoded as ACIS.").as_ref());
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
self.push_undo_snapshot(i, "POLYSOLID");
|
self.push_undo_snapshot(i, "POLYSOLID");
|
||||||
self.tabs[i].scene.erase_entities(&[handle]);
|
self.tabs[i].scene.erase_entities(&[handle]);
|
||||||
let mut s3d = Solid3D::new();
|
let mut s3d = Solid3D::new();
|
||||||
|
|
@ -319,11 +506,14 @@ impl super::OpenCADStudio {
|
||||||
if !h.is_null() {
|
if !h.is_null() {
|
||||||
self.tabs[i].scene.select_entity(h, false);
|
self.tabs[i].scene.select_entity(h, false);
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
if !h.is_null() {
|
||||||
self.refresh_properties();
|
self.tabs[i].dirty = true;
|
||||||
self.command_line.push_output(crate::tf!(
|
self.refresh_properties();
|
||||||
"POLYSOLID: built a wall solid from {used} segment(s) (width {width}, height {height})."
|
self.command_line.push_output(
|
||||||
).as_ref());
|
crate::tf!("POLYSOLID: created a wall solid (width {width}, height {height}).")
|
||||||
|
.as_ref(),
|
||||||
|
);
|
||||||
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -333,14 +523,7 @@ impl super::OpenCADStudio {
|
||||||
/// loop on the way through; without that the copy lights black.
|
/// loop on the way through; without that the copy lights black.
|
||||||
pub(super) fn solid_mirror3d(&mut self, axis: usize) -> Task<Message> {
|
pub(super) fn solid_mirror3d(&mut self, axis: usize) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("3DMIRROR: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("3DMIRROR: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -385,14 +568,7 @@ impl super::OpenCADStudio {
|
||||||
dst: [[f64; 3]; 3],
|
dst: [[f64; 3]; 3],
|
||||||
) -> Task<Message> {
|
) -> Task<Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("3DALIGN: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("3DALIGN: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -453,14 +629,7 @@ impl super::OpenCADStudio {
|
||||||
use acadrust::Line;
|
use acadrust::Line;
|
||||||
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.len() != 1 {
|
if handles.len() != 1 {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("SECTION: select exactly one solid created this session.").as_ref());
|
.push_error(crate::t!("SECTION: select exactly one solid created this session.").as_ref());
|
||||||
|
|
@ -537,12 +706,12 @@ impl super::OpenCADStudio {
|
||||||
self.tabs[i].scene.deselect_all();
|
self.tabs[i].scene.deselect_all();
|
||||||
if !handle.is_null() {
|
if !handle.is_null() {
|
||||||
self.tabs[i].scene.select_entity(handle, false);
|
self.tabs[i].scene.select_entity(handle, false);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
self.command_line.push_output(crate::tf!(
|
||||||
|
"PYRAMID: created a {n}-sided pyramid (radius {radius}, height {height})."
|
||||||
|
).as_ref());
|
||||||
}
|
}
|
||||||
self.tabs[i].dirty = true;
|
|
||||||
self.refresh_properties();
|
|
||||||
self.command_line.push_output(crate::tf!(
|
|
||||||
"PYRAMID: created a {n}-sided pyramid (radius {radius}, height {height})."
|
|
||||||
).as_ref());
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -636,14 +805,7 @@ impl super::OpenCADStudio {
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
use acadrust::Line;
|
use acadrust::Line;
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.is_empty() {
|
if handles.is_empty() {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("FLATSHOT: select a solid created this session.").as_ref());
|
.push_error(crate::t!("FLATSHOT: select a solid created this session.").as_ref());
|
||||||
|
|
@ -677,14 +839,7 @@ impl super::OpenCADStudio {
|
||||||
use acadrust::entities::{Surface, SurfaceKind, Wire as AWire};
|
use acadrust::entities::{Surface, SurfaceKind, Wire as AWire};
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let handles: Vec<Handle> = self.tabs[i]
|
let handles = self.selected_solid_handles();
|
||||||
.scene
|
|
||||||
.selected
|
|
||||||
.iter()
|
|
||||||
.copied()
|
|
||||||
.filter(|h| !self.tabs[i].scene.is_layer_locked(*h))
|
|
||||||
.filter(|h| self.tabs[i].scene.solid_models.contains_key(h))
|
|
||||||
.collect();
|
|
||||||
if handles.is_empty() {
|
if handles.is_empty() {
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_error(crate::t!("CONVTOSURFACE: select a solid created this session.").as_ref());
|
.push_error(crate::t!("CONVTOSURFACE: select a solid created this session.").as_ref());
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
use acadrust::entities::mtext::AttachmentPoint;
|
use acadrust::entities::mtext::AttachmentPoint;
|
||||||
use acadrust::entities::mtext_format::{
|
use acadrust::entities::mtext_format::{
|
||||||
parse_mtext, MTextColor, MTextDocument, MTextFont, MTextParagraph, MTextParagraphAlignment,
|
parse_mtext, MTextColor, MTextDocument, MTextFont, MTextParagraph, MTextParagraphAlignment,
|
||||||
MTextSpan, ParagraphProperties, SpanProperties, StackingData, StackingType,
|
MTextScalar, MTextSpan, ParagraphProperties, SpanProperties, StackingData, StackingType,
|
||||||
};
|
};
|
||||||
use acadrust::types::Vector3;
|
use acadrust::types::Vector3;
|
||||||
use acadrust::{EntityType, Handle, MText};
|
use acadrust::{EntityType, Handle, MText};
|
||||||
|
|
@ -38,6 +38,15 @@ pub enum ParaAlign {
|
||||||
Justify,
|
Justify,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum ParaNumber {
|
||||||
|
FirstIndent,
|
||||||
|
LeftIndent,
|
||||||
|
RightIndent,
|
||||||
|
SpaceBefore,
|
||||||
|
SpaceAfter,
|
||||||
|
}
|
||||||
|
|
||||||
/// `pick_list`-friendly wrapper for the 9 attachment points.
|
/// `pick_list`-friendly wrapper for the 9 attachment points.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub struct JustifyChoice(pub AttachmentPoint);
|
pub struct JustifyChoice(pub AttachmentPoint);
|
||||||
|
|
@ -107,6 +116,23 @@ pub struct MTextEditorState {
|
||||||
/// it is NOT derived from the typed content, so adding characters wraps
|
/// it is NOT derived from the typed content, so adding characters wraps
|
||||||
/// to the next line instead of stretching the box into one long line.
|
/// to the next line instead of stretching the box into one long line.
|
||||||
pub rect_width: f64,
|
pub rect_width: f64,
|
||||||
|
pub rect_height: String,
|
||||||
|
pub annotative: bool,
|
||||||
|
pub column_type: i16,
|
||||||
|
pub column_count: String,
|
||||||
|
pub column_auto_height: bool,
|
||||||
|
pub column_flow_reversed: bool,
|
||||||
|
pub column_width: String,
|
||||||
|
pub column_gutter: String,
|
||||||
|
pub paragraph_first_indent: String,
|
||||||
|
pub paragraph_left_indent: String,
|
||||||
|
pub paragraph_right_indent: String,
|
||||||
|
pub paragraph_space_before: String,
|
||||||
|
pub paragraph_space_after: String,
|
||||||
|
pub find_text: String,
|
||||||
|
pub replace_text: String,
|
||||||
|
undo: Vec<String>,
|
||||||
|
redo: Vec<String>,
|
||||||
/// `Some` when editing an existing entity; `None` for a fresh MText.
|
/// `Some` when editing an existing entity; `None` for a fresh MText.
|
||||||
pub editing: Option<Handle>,
|
pub editing: Option<Handle>,
|
||||||
/// When true the panel shows the rendered preview; when false the raw
|
/// When true the panel shows the rendered preview; when false the raw
|
||||||
|
|
@ -167,6 +193,23 @@ impl MTextEditorState {
|
||||||
// Default box ~20 characters wide; overwritten with the entity's
|
// Default box ~20 characters wide; overwritten with the entity's
|
||||||
// own width when editing an existing MText.
|
// own width when editing an existing MText.
|
||||||
rect_width: (height * 20.0).max(1.0),
|
rect_width: (height * 20.0).max(1.0),
|
||||||
|
rect_height: "0".to_string(),
|
||||||
|
annotative: false,
|
||||||
|
column_type: 0,
|
||||||
|
column_count: "2".to_string(),
|
||||||
|
column_auto_height: false,
|
||||||
|
column_flow_reversed: false,
|
||||||
|
column_width: (height * 10.0).to_string(),
|
||||||
|
column_gutter: (height * 5.0).to_string(),
|
||||||
|
paragraph_first_indent: "0".to_string(),
|
||||||
|
paragraph_left_indent: "0".to_string(),
|
||||||
|
paragraph_right_indent: "0".to_string(),
|
||||||
|
paragraph_space_before: "0".to_string(),
|
||||||
|
paragraph_space_after: "0".to_string(),
|
||||||
|
find_text: String::new(),
|
||||||
|
replace_text: String::new(),
|
||||||
|
undo: Vec::new(),
|
||||||
|
redo: Vec::new(),
|
||||||
editing,
|
editing,
|
||||||
screen_anchor: iced::Point::new(60.0, 90.0),
|
screen_anchor: iced::Point::new(60.0, 90.0),
|
||||||
original: None,
|
original: None,
|
||||||
|
|
@ -274,8 +317,54 @@ impl MTextEditorState {
|
||||||
mt.attachment_point = self.attachment;
|
mt.attachment_point = self.attachment;
|
||||||
mt.line_spacing_factor = self.line_spacing as f64;
|
mt.line_spacing_factor = self.line_spacing as f64;
|
||||||
mt.style = self.style.clone();
|
mt.style = self.style.clone();
|
||||||
|
mt.is_annotative = self.annotative;
|
||||||
|
let rectangle_height = self.rect_height.trim().parse::<f64>().unwrap_or(0.0);
|
||||||
|
mt.rectangle_height = (rectangle_height > 0.0).then_some(rectangle_height);
|
||||||
|
mt.column_data.column_type = self.column_type;
|
||||||
|
if self.column_type == 0 {
|
||||||
|
mt.column_data.column_count = 0;
|
||||||
|
mt.column_data.heights.clear();
|
||||||
|
} else {
|
||||||
|
mt.column_data.column_count = crate::entities::text_support::clamp_mtext_column_count(
|
||||||
|
self.column_count
|
||||||
|
.trim()
|
||||||
|
.parse::<i32>()
|
||||||
|
.ok()
|
||||||
|
.filter(|count| *count > 0)
|
||||||
|
.unwrap_or(1),
|
||||||
|
);
|
||||||
|
mt.column_data.auto_height = self.column_auto_height;
|
||||||
|
mt.column_data.flow_reversed = self.column_flow_reversed;
|
||||||
|
if let Ok(width) = self.column_width.trim().parse::<f64>() {
|
||||||
|
if width > 0.0 {
|
||||||
|
mt.column_data.width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Ok(gutter) = self.column_gutter.trim().parse::<f64>() {
|
||||||
|
if gutter >= 0.0 {
|
||||||
|
mt.column_data.gutter = gutter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !self.column_auto_height && rectangle_height > 0.0 {
|
||||||
|
mt.column_data.heights = vec![
|
||||||
|
rectangle_height;
|
||||||
|
mt.column_data.column_count as usize
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
mt
|
mt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn record_undo(&mut self) {
|
||||||
|
let value = self.content.text();
|
||||||
|
if self.undo.last().is_none_or(|last| last != &value) {
|
||||||
|
self.undo.push(value);
|
||||||
|
if self.undo.len() > 100 {
|
||||||
|
self.undo.remove(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.redo.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse a numeric field, returning `Some(v)` only when it differs from the
|
/// Parse a numeric field, returning `Some(v)` only when it differs from the
|
||||||
|
|
@ -595,6 +684,27 @@ fn vertical_caret_target(
|
||||||
best.1
|
best.1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn seed_mtext_state(state: &mut MTextEditorState, m: &MText) {
|
||||||
|
state.attachment = m.attachment_point;
|
||||||
|
state.line_spacing = m.line_spacing_factor as f32;
|
||||||
|
state.rect_width = m.rectangle_width.max(0.0);
|
||||||
|
state.rect_height = m.rectangle_height.unwrap_or(0.0).max(0.0).to_string();
|
||||||
|
state.annotative = m.is_annotative;
|
||||||
|
state.column_type = m.column_data.column_type;
|
||||||
|
state.column_count = crate::entities::text_support::clamp_mtext_column_count(
|
||||||
|
m.column_data.column_count,
|
||||||
|
)
|
||||||
|
.to_string();
|
||||||
|
state.column_auto_height = m.column_data.auto_height;
|
||||||
|
state.column_flow_reversed = m.column_data.flow_reversed;
|
||||||
|
state.column_width = m.column_data.width.max(0.0).to_string();
|
||||||
|
state.column_gutter = m.column_data.gutter.max(0.0).to_string();
|
||||||
|
if !m.style.trim().is_empty() {
|
||||||
|
state.style = m.style.clone();
|
||||||
|
}
|
||||||
|
state.original = Some(m.clone());
|
||||||
|
}
|
||||||
|
|
||||||
impl super::OpenCADStudio {
|
impl super::OpenCADStudio {
|
||||||
/// Open the in-place editor for a new (`handle = None`) or existing MText.
|
/// Open the in-place editor for a new (`handle = None`) or existing MText.
|
||||||
/// Open the rich MText editor for a new or existing MText / MultiLeader.
|
/// Open the rich MText editor for a new or existing MText / MultiLeader.
|
||||||
|
|
@ -605,6 +715,7 @@ impl super::OpenCADStudio {
|
||||||
handle: Option<Handle>,
|
handle: Option<Handle>,
|
||||||
initial: &str,
|
initial: &str,
|
||||||
height: f64,
|
height: f64,
|
||||||
|
template: Option<MText>,
|
||||||
) {
|
) {
|
||||||
if handle.is_some_and(|h| self.tabs[self.active_tab].scene.is_layer_locked(h)) {
|
if handle.is_some_and(|h| self.tabs[self.active_tab].scene.is_layer_locked(h)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -614,20 +725,11 @@ impl super::OpenCADStudio {
|
||||||
state.screen_anchor = p;
|
state.screen_anchor = p;
|
||||||
}
|
}
|
||||||
// Seed attachment / line-spacing / box width from the entity being edited.
|
// Seed attachment / line-spacing / box width from the entity being edited.
|
||||||
|
let has_template = template.is_some();
|
||||||
if let Some(h) = handle {
|
if let Some(h) = handle {
|
||||||
match self.tabs[self.active_tab].scene.document.get_entity(h) {
|
match self.tabs[self.active_tab].scene.document.get_entity(h) {
|
||||||
Some(EntityType::MText(m)) => {
|
Some(EntityType::MText(m)) => {
|
||||||
state.attachment = m.attachment_point;
|
seed_mtext_state(&mut state, m);
|
||||||
state.line_spacing = m.line_spacing_factor as f32;
|
|
||||||
if !m.style.trim().is_empty() {
|
|
||||||
state.style = m.style.clone();
|
|
||||||
}
|
|
||||||
if m.rectangle_width > 0.0 {
|
|
||||||
state.rect_width = m.rectangle_width;
|
|
||||||
}
|
|
||||||
// Keep the whole entity so `build_mtext` preserves the fields
|
|
||||||
// the toolbar can't edit (columns, rotation, background…).
|
|
||||||
state.original = Some(m.clone());
|
|
||||||
}
|
}
|
||||||
Some(EntityType::MultiLeader(ml)) => {
|
Some(EntityType::MultiLeader(ml)) => {
|
||||||
state.line_spacing = ml.context.line_spacing_factor as f32;
|
state.line_spacing = ml.context.line_spacing_factor as f32;
|
||||||
|
|
@ -637,6 +739,8 @@ impl super::OpenCADStudio {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
} else if let Some(m) = template {
|
||||||
|
seed_mtext_state(&mut state, &m);
|
||||||
} else {
|
} else {
|
||||||
// New MText inherits the document's current text style (STYLE),
|
// New MText inherits the document's current text style (STYLE),
|
||||||
// not the "Standard" default. See #92.
|
// not the "Standard" default. See #92.
|
||||||
|
|
@ -659,7 +763,7 @@ impl super::OpenCADStudio {
|
||||||
// The preview uses a fixed on-screen text size. Therefore the initial
|
// The preview uses a fixed on-screen text size. Therefore the initial
|
||||||
// wrap width is the drawing-unit span that exactly reaches the right
|
// wrap width is the drawing-unit span that exactly reaches the right
|
||||||
// edge of the editor, placing both ruler and slider at their maximum.
|
// edge of the editor, placing both ruler and slider at their maximum.
|
||||||
if handle.is_none() {
|
if handle.is_none() && !has_template {
|
||||||
let initial_width = self.mtext_editor.as_ref().map(|ed| {
|
let initial_width = self.mtext_editor.as_ref().map(|ed| {
|
||||||
super::view::overlay::MTEXT_EDITOR_WRITING_WIDTH / ed.preview_scale()
|
super::view::overlay::MTEXT_EDITOR_WRITING_WIDTH / ed.preview_scale()
|
||||||
});
|
});
|
||||||
|
|
@ -746,6 +850,322 @@ impl super::OpenCADStudio {
|
||||||
/// Splice text around the preview selection (visible-char range) in the
|
/// Splice text around the preview selection (visible-char range) in the
|
||||||
/// raw value. `case` optionally transforms the selected slice. Returns
|
/// raw value. `case` optionally transforms the selected slice. Returns
|
||||||
/// true when a preview selection was present and applied.
|
/// true when a preview selection was present and applied.
|
||||||
|
pub(super) fn mtext_undo(&mut self) {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
if let Some(previous) = ed.undo.pop() {
|
||||||
|
ed.redo.push(ed.content.text());
|
||||||
|
ed.content = text_editor::Content::with_text(&previous);
|
||||||
|
ed.caret = previous.chars().count();
|
||||||
|
ed.sel = Some((ed.caret, ed.caret));
|
||||||
|
ed.sel_anchor = ed.caret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_redo(&mut self) {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
if let Some(next) = ed.redo.pop() {
|
||||||
|
ed.undo.push(ed.content.text());
|
||||||
|
ed.content = text_editor::Content::with_text(&next);
|
||||||
|
ed.caret = next.chars().count();
|
||||||
|
ed.sel = Some((ed.caret, ed.caret));
|
||||||
|
ed.sel_anchor = ed.caret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_apply_span_number(&mut self, field: &'static str, value: String) {
|
||||||
|
let Some(number) = value.trim().replace(',', ".").parse::<f64>().ok() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let valid = match field {
|
||||||
|
"height" => number > 0.0,
|
||||||
|
"oblique" => (-85.0..=85.0).contains(&number),
|
||||||
|
"width" => number > 0.0,
|
||||||
|
"tracking" => number.is_finite(),
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
if !valid {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let has_selection = self
|
||||||
|
.mtext_editor
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|editor| editor.sel)
|
||||||
|
.is_some_and(|(start, end)| start < end);
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
if has_selection {
|
||||||
|
let (start, end) = ed.sel.unwrap();
|
||||||
|
ed.record_undo();
|
||||||
|
let para0 = doc_para0(&ed.doc);
|
||||||
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
|
let end = end.min(cells.len());
|
||||||
|
for cell in &mut cells[start.min(end)..end] {
|
||||||
|
if let Cell::Char(_, props) | Cell::Stack { props, .. } = cell {
|
||||||
|
match field {
|
||||||
|
"height" => props.height = Some(MTextScalar::Absolute(number)),
|
||||||
|
"oblique" => props.oblique_angle = (number != 0.0).then_some(number),
|
||||||
|
"width" => props.width_factor = (number != 1.0).then_some(number),
|
||||||
|
"tracking" => props.tracking = (number != 0.0).then_some(number),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ed.content = text_editor::Content::with_text(
|
||||||
|
&cells_to_doc(¶0, &cells).to_mtext_string(),
|
||||||
|
);
|
||||||
|
ed.sel = Some((start, end));
|
||||||
|
ed.caret = end;
|
||||||
|
} else {
|
||||||
|
match field {
|
||||||
|
"height" => ed.height = value,
|
||||||
|
"oblique" => ed.oblique = value,
|
||||||
|
"width" => ed.width = value,
|
||||||
|
"tracking" => ed.char_space = value,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_apply_paragraph_number(&mut self, field: ParaNumber, value: String) {
|
||||||
|
let Some(number) = value.trim().replace(',', ".").parse::<f64>().ok() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if !number.is_finite() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.record_undo();
|
||||||
|
match field {
|
||||||
|
ParaNumber::FirstIndent => ed.paragraph_first_indent = value,
|
||||||
|
ParaNumber::LeftIndent => ed.paragraph_left_indent = value,
|
||||||
|
ParaNumber::RightIndent => ed.paragraph_right_indent = value,
|
||||||
|
ParaNumber::SpaceBefore => ed.paragraph_space_before = value,
|
||||||
|
ParaNumber::SpaceAfter => ed.paragraph_space_after = value,
|
||||||
|
}
|
||||||
|
let para0 = doc_para0(&ed.doc);
|
||||||
|
let cells = doc_to_cells(&ed.doc);
|
||||||
|
let (start, end) = ed
|
||||||
|
.sel
|
||||||
|
.filter(|(start, end)| start < end)
|
||||||
|
.unwrap_or((ed.caret, ed.caret));
|
||||||
|
let paragraph_of = |index: usize| {
|
||||||
|
cells
|
||||||
|
.iter()
|
||||||
|
.take(index.min(cells.len()))
|
||||||
|
.filter(|cell| matches!(cell, Cell::Break(..)))
|
||||||
|
.count()
|
||||||
|
};
|
||||||
|
let first = paragraph_of(start);
|
||||||
|
let last = paragraph_of(end.saturating_sub((end > start) as usize));
|
||||||
|
let mut doc = cells_to_doc(¶0, &cells);
|
||||||
|
for paragraph in doc
|
||||||
|
.paragraphs
|
||||||
|
.iter_mut()
|
||||||
|
.skip(first)
|
||||||
|
.take(last.saturating_sub(first) + 1)
|
||||||
|
{
|
||||||
|
let optional = (number != 0.0).then_some(number);
|
||||||
|
match field {
|
||||||
|
ParaNumber::FirstIndent => paragraph.properties.first_line_indent = optional,
|
||||||
|
ParaNumber::LeftIndent => paragraph.properties.left_margin = optional,
|
||||||
|
ParaNumber::RightIndent => paragraph.properties.right_margin = optional,
|
||||||
|
ParaNumber::SpaceBefore => paragraph.properties.spacing_before = optional,
|
||||||
|
ParaNumber::SpaceAfter => paragraph.properties.spacing_after = optional,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ed.content = text_editor::Content::with_text(&doc.to_mtext_string());
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_stack_selection(&mut self) {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
let Some((start, end)) = ed.sel.filter(|(start, end)| start < end) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let para0 = doc_para0(&ed.doc);
|
||||||
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
|
let end = end.min(cells.len());
|
||||||
|
let plain = cells_to_doc(&ParagraphProperties::default(), &cells[start..end])
|
||||||
|
.to_plain_text();
|
||||||
|
let Some((split, separator)) = plain
|
||||||
|
.char_indices()
|
||||||
|
.find(|(_, ch)| matches!(ch, '/' | '#' | '^'))
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let separator_len = separator.len_utf8();
|
||||||
|
let data = StackingData {
|
||||||
|
numerator: plain[..split].to_string(),
|
||||||
|
denominator: plain[split + separator_len..].to_string(),
|
||||||
|
stacking_type: StackingType::from_char(separator),
|
||||||
|
};
|
||||||
|
if data.numerator.is_empty() || data.denominator.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let props = cells[start..end]
|
||||||
|
.iter()
|
||||||
|
.find_map(|cell| match cell {
|
||||||
|
Cell::Char(_, props) | Cell::Stack { props, .. } => Some(props.clone()),
|
||||||
|
Cell::Break(..) => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
ed.record_undo();
|
||||||
|
let replacement = flatten_stack(&data)
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(index, _)| Cell::Stack {
|
||||||
|
data: data.clone(),
|
||||||
|
props: props.clone(),
|
||||||
|
head: index == 0,
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let replacement_len = replacement.len();
|
||||||
|
cells.splice(start..end, replacement);
|
||||||
|
let new_end = start + replacement_len;
|
||||||
|
ed.content = text_editor::Content::with_text(
|
||||||
|
&cells_to_doc(¶0, &cells).to_mtext_string(),
|
||||||
|
);
|
||||||
|
ed.sel = Some((start, new_end));
|
||||||
|
ed.caret = new_end;
|
||||||
|
ed.sel_anchor = start;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_clear_formatting(&mut self) {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
let para0 = doc_para0(&ed.doc);
|
||||||
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
|
let (start, end) = ed
|
||||||
|
.sel
|
||||||
|
.filter(|(start, end)| start < end)
|
||||||
|
.unwrap_or((0, cells.len()));
|
||||||
|
let end = end.min(cells.len());
|
||||||
|
ed.record_undo();
|
||||||
|
for cell in &mut cells[start.min(end)..end] {
|
||||||
|
if let Cell::Char(_, props) | Cell::Stack { props, .. } = cell {
|
||||||
|
*props = SpanProperties::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ed.content = text_editor::Content::with_text(
|
||||||
|
&cells_to_doc(¶0, &cells).to_mtext_string(),
|
||||||
|
);
|
||||||
|
ed.sel = Some((start, end));
|
||||||
|
ed.caret = end;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_find_next(&mut self) {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
let needle: Vec<char> = ed.find_text.chars().collect();
|
||||||
|
if needle.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let cells = doc_to_cells(&ed.doc);
|
||||||
|
let chars: Vec<Option<char>> = cells
|
||||||
|
.iter()
|
||||||
|
.map(|cell| match cell {
|
||||||
|
Cell::Char(ch, _) => Some(*ch),
|
||||||
|
Cell::Break(..) => Some('\n'),
|
||||||
|
Cell::Stack { .. } => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if needle.len() > chars.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let start = ed.caret.min(chars.len());
|
||||||
|
let last_start = chars.len() - needle.len();
|
||||||
|
let found = (start..=last_start)
|
||||||
|
.chain(0..start.min(last_start + 1))
|
||||||
|
.find(|index| {
|
||||||
|
chars[*index..*index + needle.len()]
|
||||||
|
.iter()
|
||||||
|
.zip(&needle)
|
||||||
|
.all(|(actual, expected)| actual == &Some(*expected))
|
||||||
|
});
|
||||||
|
if let Some(found) = found {
|
||||||
|
ed.sel_anchor = found;
|
||||||
|
ed.sel = Some((found, found + needle.len()));
|
||||||
|
ed.caret = found + needle.len();
|
||||||
|
ed.caret_blink_on = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_replace_next(&mut self) {
|
||||||
|
let replacement = self
|
||||||
|
.mtext_editor
|
||||||
|
.as_ref()
|
||||||
|
.map(|editor| editor.replace_text.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
let matches = self.mtext_editor.as_ref().is_some_and(|editor| {
|
||||||
|
let Some((start, end)) = editor.sel.filter(|(start, end)| start < end) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let cells = doc_to_cells(&editor.doc);
|
||||||
|
cells_to_doc(
|
||||||
|
&ParagraphProperties::default(),
|
||||||
|
&cells[start.min(cells.len())..end.min(cells.len())],
|
||||||
|
)
|
||||||
|
.to_plain_text()
|
||||||
|
== editor.find_text
|
||||||
|
});
|
||||||
|
if matches {
|
||||||
|
self.mtext_type(&replacement);
|
||||||
|
}
|
||||||
|
self.mtext_find_next();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn mtext_replace_all(&mut self) {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
let needle: Vec<char> = ed.find_text.chars().collect();
|
||||||
|
if needle.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let para0 = doc_para0(&ed.doc);
|
||||||
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
|
let replacement = ed.replace_text.clone();
|
||||||
|
let mut matches = Vec::new();
|
||||||
|
let mut index = 0usize;
|
||||||
|
while index + needle.len() <= cells.len() {
|
||||||
|
let found = cells[index..index + needle.len()]
|
||||||
|
.iter()
|
||||||
|
.zip(&needle)
|
||||||
|
.all(|(cell, expected)| matches!(cell, Cell::Char(actual, _) if actual == expected));
|
||||||
|
if found {
|
||||||
|
matches.push(index);
|
||||||
|
index += needle.len();
|
||||||
|
} else {
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ed.record_undo();
|
||||||
|
for start in matches.into_iter().rev() {
|
||||||
|
let props = insert_props(&cells, start);
|
||||||
|
let paragraph = para_props_at(¶0, &cells, start);
|
||||||
|
let replacement_cells = str_to_cells(&replacement, &props, ¶graph);
|
||||||
|
cells.splice(start..start + needle.len(), replacement_cells);
|
||||||
|
}
|
||||||
|
ed.content = text_editor::Content::with_text(
|
||||||
|
&cells_to_doc(¶0, &cells).to_mtext_string(),
|
||||||
|
);
|
||||||
|
ed.caret = cells.len();
|
||||||
|
ed.sel = Some((ed.caret, ed.caret));
|
||||||
|
ed.sel_anchor = ed.caret;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
}
|
||||||
|
|
||||||
/// Toggle a character format over the preview selection, on the structured
|
/// Toggle a character format over the preview selection, on the structured
|
||||||
/// span properties. The stroke-font renderer has no true bold, so Bold
|
/// span properties. The stroke-font renderer has no true bold, so Bold
|
||||||
/// switches the run to the heavier "Gothic" face; Italic applies a 15°
|
/// switches the run to the heavier "Gothic" face; Italic applies a 15°
|
||||||
|
|
@ -798,6 +1218,7 @@ impl super::OpenCADStudio {
|
||||||
if a >= b {
|
if a >= b {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
ed.record_undo();
|
||||||
match kind {
|
match kind {
|
||||||
MTextFmt::Underline => {
|
MTextFmt::Underline => {
|
||||||
let on = !all_have(&cells[a..b], |p| p.underline());
|
let on = !all_have(&cells[a..b], |p| p.underline());
|
||||||
|
|
@ -882,6 +1303,7 @@ impl super::OpenCADStudio {
|
||||||
/// Set the alignment of every paragraph the selection (or caret) touches.
|
/// Set the alignment of every paragraph the selection (or caret) touches.
|
||||||
pub(super) fn mtext_apply_align(&mut self, align: ParaAlign) {
|
pub(super) fn mtext_apply_align(&mut self, align: ParaAlign) {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.record_undo();
|
||||||
let para0 = doc_para0(&ed.doc);
|
let para0 = doc_para0(&ed.doc);
|
||||||
let cells = doc_to_cells(&ed.doc);
|
let cells = doc_to_cells(&ed.doc);
|
||||||
let (a, b) = ed
|
let (a, b) = ed
|
||||||
|
|
@ -933,6 +1355,7 @@ impl super::OpenCADStudio {
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.record_undo();
|
||||||
// Edit the structured document as a flat cell list, then serialize
|
// Edit the structured document as a flat cell list, then serialize
|
||||||
// it back into the raw content the preview/commit still read.
|
// it back into the raw content the preview/commit still read.
|
||||||
let para0 = doc_para0(&ed.doc);
|
let para0 = doc_para0(&ed.doc);
|
||||||
|
|
@ -962,6 +1385,7 @@ impl super::OpenCADStudio {
|
||||||
/// Delete the selection, or the visible character before the caret.
|
/// Delete the selection, or the visible character before the caret.
|
||||||
pub(super) fn mtext_backspace(&mut self) {
|
pub(super) fn mtext_backspace(&mut self) {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.record_undo();
|
||||||
let para0 = doc_para0(&ed.doc);
|
let para0 = doc_para0(&ed.doc);
|
||||||
let mut cells = doc_to_cells(&ed.doc);
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
let count = cells.len();
|
let count = cells.len();
|
||||||
|
|
@ -985,6 +1409,7 @@ impl super::OpenCADStudio {
|
||||||
/// Delete the selection, or the visible character at the caret.
|
/// Delete the selection, or the visible character at the caret.
|
||||||
pub(super) fn mtext_delete(&mut self) {
|
pub(super) fn mtext_delete(&mut self) {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.record_undo();
|
||||||
let para0 = doc_para0(&ed.doc);
|
let para0 = doc_para0(&ed.doc);
|
||||||
let mut cells = doc_to_cells(&ed.doc);
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
let count = cells.len();
|
let count = cells.len();
|
||||||
|
|
@ -1104,6 +1529,7 @@ impl super::OpenCADStudio {
|
||||||
let mut cells = doc_to_cells(&ed.doc);
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
let b = b.min(cells.len());
|
let b = b.min(cells.len());
|
||||||
if a < b {
|
if a < b {
|
||||||
|
ed.record_undo();
|
||||||
for c in &mut cells[a..b] {
|
for c in &mut cells[a..b] {
|
||||||
if let Cell::Char(_, p) | Cell::Stack { props: p, .. } = c {
|
if let Cell::Char(_, p) | Cell::Stack { props: p, .. } = c {
|
||||||
let (bold, italic) = p
|
let (bold, italic) = p
|
||||||
|
|
@ -1155,6 +1581,7 @@ impl super::OpenCADStudio {
|
||||||
let mut cells = doc_to_cells(&ed.doc);
|
let mut cells = doc_to_cells(&ed.doc);
|
||||||
let b = b.min(cells.len());
|
let b = b.min(cells.len());
|
||||||
if a < b {
|
if a < b {
|
||||||
|
ed.record_undo();
|
||||||
for c in &mut cells[a..b] {
|
for c in &mut cells[a..b] {
|
||||||
if let Cell::Char(_, p) | Cell::Stack { props: p, .. } = c {
|
if let Cell::Char(_, p) | Cell::Stack { props: p, .. } = c {
|
||||||
p.color = mcolor.clone();
|
p.color = mcolor.clone();
|
||||||
|
|
@ -1209,9 +1636,16 @@ impl super::OpenCADStudio {
|
||||||
pub(super) fn mtext_commit(&mut self) -> bool {
|
pub(super) fn mtext_commit(&mut self) -> bool {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let Some(ed) = self.mtext_editor.take() else { return false };
|
let Some(ed) = self.mtext_editor.take() else { return false };
|
||||||
|
if self.command_mtext_input {
|
||||||
|
self.pending_command_editor_text = Some(ed.build_mtext().value);
|
||||||
|
self.command_mtext_input = false;
|
||||||
|
self.refresh_properties();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
let body_empty = ed.content.text().trim().is_empty();
|
let body_empty = ed.content.text().trim().is_empty();
|
||||||
let mut mt = ed.build_mtext();
|
let mut mt = ed.build_mtext();
|
||||||
let annotative = ed.editing.is_none()
|
let annotative = mt.is_annotative
|
||||||
|
|| ed.editing.is_none()
|
||||||
&& crate::scene::annotative::text_style_is_annotative(
|
&& crate::scene::annotative::text_style_is_annotative(
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
&mt.style,
|
&mt.style,
|
||||||
|
|
@ -1234,9 +1668,12 @@ impl super::OpenCADStudio {
|
||||||
Some(EntityType::MText(t)) => {
|
Some(EntityType::MText(t)) => {
|
||||||
t.value = mt.value;
|
t.value = mt.value;
|
||||||
t.height = mt.height;
|
t.height = mt.height;
|
||||||
|
t.style = mt.style;
|
||||||
t.attachment_point = mt.attachment_point;
|
t.attachment_point = mt.attachment_point;
|
||||||
t.line_spacing_factor = mt.line_spacing_factor;
|
t.line_spacing_factor = mt.line_spacing_factor;
|
||||||
t.rectangle_width = mt.rectangle_width;
|
t.rectangle_width = mt.rectangle_width;
|
||||||
|
t.rectangle_height = mt.rectangle_height;
|
||||||
|
t.column_data = mt.column_data;
|
||||||
}
|
}
|
||||||
Some(EntityType::MultiLeader(ml)) => {
|
Some(EntityType::MultiLeader(ml)) => {
|
||||||
ml.context.text_string = mt.value;
|
ml.context.text_string = mt.value;
|
||||||
|
|
@ -1248,6 +1685,31 @@ impl super::OpenCADStudio {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
// Keep the displayed annotation context in sync.
|
||||||
|
if matches!(self.tabs[i].scene.document.get_entity(h), Some(EntityType::MText(_))) {
|
||||||
|
crate::scene::annotative::set_entity_annotative(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
h,
|
||||||
|
annotative,
|
||||||
|
);
|
||||||
|
if annotative {
|
||||||
|
if let Some(scale) = self.tabs[i].scene.current_annotation_scale_handle() {
|
||||||
|
crate::scene::annotative::create_annotation_context(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(h),
|
||||||
|
Some(EntityType::MText(_) | EntityType::MultiLeader(_))
|
||||||
|
) {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.sync_displayed_annotation_context(h);
|
||||||
|
}
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||||
|
|
@ -1268,7 +1730,6 @@ impl super::OpenCADStudio {
|
||||||
position.y,
|
position.y,
|
||||||
position.z,
|
position.z,
|
||||||
);
|
);
|
||||||
mt.rotation = 0.0;
|
|
||||||
self.push_undo_snapshot(i, "MTEXT");
|
self.push_undo_snapshot(i, "MTEXT");
|
||||||
let handle = self.commit_entity_handle(plane.place_entity(EntityType::MText(mt)));
|
let handle = self.commit_entity_handle(plane.place_entity(EntityType::MText(mt)));
|
||||||
if annotative {
|
if annotative {
|
||||||
|
|
@ -1293,6 +1754,10 @@ impl super::OpenCADStudio {
|
||||||
/// then updated in place on later applies, so repeated Apply never leaves
|
/// then updated in place on later applies, so repeated Apply never leaves
|
||||||
/// duplicate entities behind.
|
/// duplicate entities behind.
|
||||||
pub(super) fn mtext_apply(&mut self) {
|
pub(super) fn mtext_apply(&mut self) {
|
||||||
|
if self.command_mtext_input {
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let (mut mt, editing, body_empty) = match self.mtext_editor.as_ref() {
|
let (mut mt, editing, body_empty) = match self.mtext_editor.as_ref() {
|
||||||
Some(ed) => (
|
Some(ed) => (
|
||||||
|
|
@ -1302,7 +1767,8 @@ impl super::OpenCADStudio {
|
||||||
),
|
),
|
||||||
None => return,
|
None => return,
|
||||||
};
|
};
|
||||||
let annotative = editing.is_none()
|
let annotative = mt.is_annotative
|
||||||
|
|| editing.is_none()
|
||||||
&& crate::scene::annotative::text_style_is_annotative(
|
&& crate::scene::annotative::text_style_is_annotative(
|
||||||
&self.tabs[i].scene.document,
|
&self.tabs[i].scene.document,
|
||||||
&mt.style,
|
&mt.style,
|
||||||
|
|
@ -1322,9 +1788,12 @@ impl super::OpenCADStudio {
|
||||||
Some(EntityType::MText(t)) => {
|
Some(EntityType::MText(t)) => {
|
||||||
t.value = mt.value;
|
t.value = mt.value;
|
||||||
t.height = mt.height;
|
t.height = mt.height;
|
||||||
|
t.style = mt.style;
|
||||||
t.attachment_point = mt.attachment_point;
|
t.attachment_point = mt.attachment_point;
|
||||||
t.line_spacing_factor = mt.line_spacing_factor;
|
t.line_spacing_factor = mt.line_spacing_factor;
|
||||||
t.rectangle_width = mt.rectangle_width;
|
t.rectangle_width = mt.rectangle_width;
|
||||||
|
t.rectangle_height = mt.rectangle_height;
|
||||||
|
t.column_data = mt.column_data;
|
||||||
}
|
}
|
||||||
Some(EntityType::MultiLeader(ml)) => {
|
Some(EntityType::MultiLeader(ml)) => {
|
||||||
ml.context.text_string = mt.value;
|
ml.context.text_string = mt.value;
|
||||||
|
|
@ -1336,6 +1805,30 @@ impl super::OpenCADStudio {
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
if matches!(self.tabs[i].scene.document.get_entity(h), Some(EntityType::MText(_))) {
|
||||||
|
crate::scene::annotative::set_entity_annotative(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
h,
|
||||||
|
annotative,
|
||||||
|
);
|
||||||
|
if annotative {
|
||||||
|
if let Some(scale) = self.tabs[i].scene.current_annotation_scale_handle() {
|
||||||
|
crate::scene::annotative::create_annotation_context(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
h,
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(h),
|
||||||
|
Some(EntityType::MText(_) | EntityType::MultiLeader(_))
|
||||||
|
) {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.sync_displayed_annotation_context(h);
|
||||||
|
}
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
.bump_entities(&[(h, crate::scene::ChangeKind::Modified)]);
|
||||||
|
|
@ -1356,7 +1849,6 @@ impl super::OpenCADStudio {
|
||||||
position.y,
|
position.y,
|
||||||
position.z,
|
position.z,
|
||||||
);
|
);
|
||||||
mt.rotation = 0.0;
|
|
||||||
self.push_undo_snapshot(i, "MTEXT");
|
self.push_undo_snapshot(i, "MTEXT");
|
||||||
let handle = self.commit_entity_handle(plane.place_entity(EntityType::MText(mt)));
|
let handle = self.commit_entity_handle(plane.place_entity(EntityType::MText(mt)));
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
|
|
@ -1373,6 +1865,11 @@ impl super::OpenCADStudio {
|
||||||
// Bind the editor to the fresh entity so the next Apply updates it.
|
// Bind the editor to the fresh entity so the next Apply updates it.
|
||||||
if let (Some(h), Some(ed)) = (handle, self.mtext_editor.as_mut()) {
|
if let (Some(h), Some(ed)) = (handle, self.mtext_editor.as_mut()) {
|
||||||
ed.editing = Some(h);
|
ed.editing = Some(h);
|
||||||
|
if let Some(EntityType::MText(mtext)) =
|
||||||
|
self.tabs[i].scene.document.get_entity(h)
|
||||||
|
{
|
||||||
|
ed.original = Some(mtext.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
|
|
@ -1381,6 +1878,8 @@ impl super::OpenCADStudio {
|
||||||
/// Discard the editor without changing the drawing.
|
/// Discard the editor without changing the drawing.
|
||||||
pub(super) fn mtext_cancel(&mut self) {
|
pub(super) fn mtext_cancel(&mut self) {
|
||||||
self.mtext_editor = None;
|
self.mtext_editor = None;
|
||||||
|
self.command_mtext_input = false;
|
||||||
|
self.pending_command_editor_text = None;
|
||||||
self.reset_modal_geometry();
|
self.reset_modal_geometry();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,33 @@ pub struct UserSettings {
|
||||||
/// Interface language preference. `System` negotiates against the
|
/// Interface language preference. `System` negotiates against the
|
||||||
/// platform locale on every launch.
|
/// platform locale on every launch.
|
||||||
pub language: crate::i18n::Language,
|
pub language: crate::i18n::Language,
|
||||||
|
/// CLIPROMPTLINES: how many temporary prompt lines for a single command
|
||||||
|
/// are displayed above the command window (0–50, Registry, default 3).
|
||||||
|
#[serde(default = "default_clipromptlines", deserialize_with = "deserialize_clipromptlines")]
|
||||||
|
pub cliprompt_lines: i32,
|
||||||
|
/// Most-recently-inserted block names, most recent first, capped to 20.
|
||||||
|
/// Used to rank INSERT suggestions without touching the drawing file.
|
||||||
|
#[serde(default)]
|
||||||
|
pub block_mru: Vec<String>,
|
||||||
|
/// Insertion frequency per block name (uppercase key → count).
|
||||||
|
#[serde(default)]
|
||||||
|
pub block_freq: std::collections::HashMap<String, u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_clipromptlines() -> i32 {
|
||||||
|
3
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_clipromptlines<'de, D>(de: D) -> Result<i32, D::Error>
|
||||||
|
where
|
||||||
|
D: serde::Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let v = i32::deserialize(de).unwrap_or(3);
|
||||||
|
Ok(v.clamp(0, 50))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clamp_clipromptlines(v: i32) -> i32 {
|
||||||
|
v.clamp(0, 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for UserSettings {
|
impl Default for UserSettings {
|
||||||
|
|
@ -268,6 +295,9 @@ impl Default for UserSettings {
|
||||||
bg_color: None,
|
bg_color: None,
|
||||||
paper_bg_color: None,
|
paper_bg_color: None,
|
||||||
language: crate::i18n::Language::default(),
|
language: crate::i18n::Language::default(),
|
||||||
|
cliprompt_lines: 3,
|
||||||
|
block_mru: Vec::new(),
|
||||||
|
block_freq: std::collections::HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,16 +18,134 @@
|
||||||
//! style added without a handle (dropped on DWG save, issue #67).
|
//! style added without a handle (dropped on DWG save, issue #67).
|
||||||
|
|
||||||
use super::OpenCADStudio;
|
use super::OpenCADStudio;
|
||||||
use acadrust::objects::{MLineStyle, MultiLeaderStyle, ObjectType, TableStyle};
|
use acadrust::objects::{
|
||||||
|
Dictionary, MLineStyle, MultiLeaderStyle, ObjectType, TableStyle,
|
||||||
|
};
|
||||||
use acadrust::tables::{DimStyle, TextStyle};
|
use acadrust::tables::{DimStyle, TextStyle};
|
||||||
use acadrust::types::Handle;
|
use acadrust::types::Handle;
|
||||||
|
|
||||||
|
const MLEADERSTYLE_DICT_NAME: &str = "ACAD_MLEADERSTYLE";
|
||||||
|
|
||||||
|
fn mleaderstyle_dict_handle(doc: &acadrust::CadDocument) -> Option<Handle> {
|
||||||
|
let root_h = doc.header.named_objects_dict_handle;
|
||||||
|
|
||||||
|
let root = match doc.objects.get(&root_h) {
|
||||||
|
Some(ObjectType::Dictionary(root)) => root,
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
root.entries
|
||||||
|
.iter()
|
||||||
|
.find(|(name, _)| name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME))
|
||||||
|
.map(|(_, handle)| *handle)
|
||||||
|
.filter(|handle| {
|
||||||
|
matches!(
|
||||||
|
doc.objects.get(handle),
|
||||||
|
Some(ObjectType::Dictionary(_))
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn import_mleaderstyle_names_from_dictionary(doc: &mut acadrust::CadDocument) {
|
||||||
|
let Some(dict_h) = mleaderstyle_dict_handle(doc) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let entries = match doc.objects.get(&dict_h) {
|
||||||
|
Some(ObjectType::Dictionary(dict)) => dict.entries.clone(),
|
||||||
|
_ => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (name, handle) in entries {
|
||||||
|
if let Some(ObjectType::MultiLeaderStyle(style)) =
|
||||||
|
doc.objects.get_mut(&handle)
|
||||||
|
{
|
||||||
|
style.name = name;
|
||||||
|
style.owner_handle = dict_h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sync_mleaderstyle_dictionary(doc: &mut acadrust::CadDocument) {
|
||||||
|
let root_h = crate::scene::annotative::root_named_dict_handle(doc);
|
||||||
|
|
||||||
|
let existing = match doc.objects.get(&root_h) {
|
||||||
|
Some(ObjectType::Dictionary(root)) => root
|
||||||
|
.entries
|
||||||
|
.iter()
|
||||||
|
.find(|(name, _)| name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME))
|
||||||
|
.map(|(_, handle)| *handle),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let dict_h = match existing.filter(|handle| {
|
||||||
|
matches!(
|
||||||
|
doc.objects.get(handle),
|
||||||
|
Some(ObjectType::Dictionary(_))
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
Some(handle) => handle,
|
||||||
|
None => {
|
||||||
|
let handle = doc.allocate_handle();
|
||||||
|
|
||||||
|
let mut dict = Dictionary::new();
|
||||||
|
dict.handle = handle;
|
||||||
|
dict.owner = root_h;
|
||||||
|
|
||||||
|
doc.objects
|
||||||
|
.insert(handle, ObjectType::Dictionary(dict));
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ObjectType::Dictionary(root)) = doc.objects.get_mut(&root_h) {
|
||||||
|
root.entries
|
||||||
|
.retain(|(name, _)| !name.eq_ignore_ascii_case(MLEADERSTYLE_DICT_NAME));
|
||||||
|
|
||||||
|
root.add_entry(MLEADERSTYLE_DICT_NAME, dict_h);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut entries: Vec<(String, Handle)> = doc
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|(&handle, object)| match object {
|
||||||
|
ObjectType::MultiLeaderStyle(style) => {
|
||||||
|
Some((style.name.clone(), handle))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
entries.sort_by(|a, b| {
|
||||||
|
a.0.to_lowercase().cmp(&b.0.to_lowercase())
|
||||||
|
});
|
||||||
|
|
||||||
|
for (_, handle) in &entries {
|
||||||
|
if let Some(ObjectType::MultiLeaderStyle(style)) =
|
||||||
|
doc.objects.get_mut(handle)
|
||||||
|
{
|
||||||
|
style.owner_handle = dict_h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ObjectType::Dictionary(dict)) = doc.objects.get_mut(&dict_h) {
|
||||||
|
dict.owner = root_h;
|
||||||
|
dict.entries = entries;
|
||||||
|
|
||||||
|
// MLEADERSTYLE uses soft-owner entries (group 350).
|
||||||
|
dict.hard_owner = false;
|
||||||
|
dict.hard_owner_entries.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Guarantee the built-in "Standard" style of every kind exists in `doc` —
|
/// Guarantee the built-in "Standard" style of every kind exists in `doc` —
|
||||||
/// a foreign or damaged file saved without them leaves the style dropdowns
|
/// a foreign or damaged file saved without them leaves the style dropdowns
|
||||||
/// broken with no way to recover, and new text/dimensions have nothing to
|
/// broken with no way to recover, and new text/dimensions have nothing to
|
||||||
/// reference (#366). Missing entries are re-seeded with the app defaults.
|
/// reference (#366). Missing entries are re-seeded with the app defaults.
|
||||||
/// Called on every file open; a no-op for healthy documents.
|
/// Called on every file open; a no-op for healthy documents.
|
||||||
pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) {
|
pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) {
|
||||||
|
import_mleaderstyle_names_from_dictionary(doc);
|
||||||
if !doc
|
if !doc
|
||||||
.text_styles
|
.text_styles
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -68,6 +186,8 @@ pub(crate) fn ensure_standard_styles(doc: &mut acadrust::CadDocument) {
|
||||||
s.handle = doc.allocate_handle();
|
s.handle = doc.allocate_handle();
|
||||||
doc.objects.insert(s.handle, ObjectType::MultiLeaderStyle(s));
|
doc.objects.insert(s.handle, ObjectType::MultiLeaderStyle(s));
|
||||||
}
|
}
|
||||||
|
sync_mleaderstyle_dictionary(doc);
|
||||||
|
|
||||||
if !has(doc, |o| match o {
|
if !has(doc, |o| match o {
|
||||||
ObjectType::MLineStyle(s) => Some(&s.name),
|
ObjectType::MLineStyle(s) => Some(&s.name),
|
||||||
_ => None,
|
_ => None,
|
||||||
|
|
@ -741,6 +861,7 @@ impl OpenCADStudio {
|
||||||
for (h, o) in &snap.style_objects {
|
for (h, o) in &snap.style_objects {
|
||||||
doc.objects.insert(*h, o.clone());
|
doc.objects.insert(*h, o.clone());
|
||||||
}
|
}
|
||||||
|
sync_mleaderstyle_dictionary(doc);
|
||||||
doc.header.current_text_style_name = snap.current_text.clone();
|
doc.header.current_text_style_name = snap.current_text.clone();
|
||||||
doc.header.current_dimstyle_name = snap.current_dim.clone();
|
doc.header.current_dimstyle_name = snap.current_dim.clone();
|
||||||
doc.header.multiline_style = snap.multiline_style.clone();
|
doc.header.multiline_style = snap.multiline_style.clone();
|
||||||
|
|
@ -772,11 +893,77 @@ impl OpenCADStudio {
|
||||||
self.sync_ribbon_styles();
|
self.sync_ribbon_styles();
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
sync_mleaderstyle_dictionary(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
);
|
||||||
|
|
||||||
let edited = self.capture_style_state();
|
let edited = self.capture_style_state();
|
||||||
let changed = edited != stage.baseline;
|
let changed = edited != stage.baseline;
|
||||||
if changed {
|
if changed {
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
let (text_names, dim_names, object_handles) = edited.changed_keys(&stage.baseline);
|
let (text_names, dim_names, object_handles) = edited.changed_keys(&stage.baseline);
|
||||||
|
let changed_mleader_styles:
|
||||||
|
Vec<acadrust::objects::MultiLeaderStyle> =
|
||||||
|
object_handles
|
||||||
|
.iter()
|
||||||
|
.filter_map(|handle| {
|
||||||
|
match self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.get(handle)
|
||||||
|
{
|
||||||
|
Some(
|
||||||
|
acadrust::objects::ObjectType::
|
||||||
|
MultiLeaderStyle(style),
|
||||||
|
) => Some(style.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut changed_mleaders = Vec::new();
|
||||||
|
|
||||||
|
for style in changed_mleader_styles {
|
||||||
|
let entity_handles: Vec<acadrust::Handle> = {
|
||||||
|
let doc = &self.tabs[i].scene.document;
|
||||||
|
|
||||||
|
doc.entities()
|
||||||
|
.filter_map(|entity| {
|
||||||
|
match entity {
|
||||||
|
acadrust::EntityType::MultiLeader(ml)
|
||||||
|
if ml.style_handle
|
||||||
|
== Some(style.handle) =>
|
||||||
|
{
|
||||||
|
Some(ml.common.handle)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
|
for handle in entity_handles {
|
||||||
|
if crate::scene::annotative::
|
||||||
|
apply_mleader_style_to_object(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
&style,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
changed_mleaders.push((
|
||||||
|
handle,
|
||||||
|
crate::scene::ChangeKind::Modified,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !changed_mleaders.is_empty() {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.bump_entities(&changed_mleaders);
|
||||||
|
}
|
||||||
self.tabs[i].scene.invalidate_text_style_dependencies_many(&text_names);
|
self.tabs[i].scene.invalidate_text_style_dependencies_many(&text_names);
|
||||||
self.tabs[i]
|
self.tabs[i]
|
||||||
.scene
|
.scene
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,9 @@ pub struct TextInlineState {
|
||||||
pub editing: Option<Handle>,
|
pub editing: Option<Handle>,
|
||||||
/// Which entity slot this session writes to on commit.
|
/// Which entity slot this session writes to on commit.
|
||||||
pub field: TextEntityField,
|
pub field: TextEntityField,
|
||||||
|
/// Fully prepared entity supplied by the interactive TEXT command. Editing
|
||||||
|
/// existing text and legacy direct-open paths leave this empty.
|
||||||
|
pub creation: Option<Text>,
|
||||||
/// Canvas-space anchor where the field is drawn (the insertion-point click).
|
/// Canvas-space anchor where the field is drawn (the insertion-point click).
|
||||||
pub screen_anchor: iced::Point,
|
pub screen_anchor: iced::Point,
|
||||||
}
|
}
|
||||||
|
|
@ -145,6 +148,13 @@ impl super::OpenCADStudio {
|
||||||
if self.tabs[i].scene.is_layer_locked(target) {
|
if self.tabs[i].scene.is_layer_locked(target) {
|
||||||
return iced::Task::none();
|
return iced::Task::none();
|
||||||
}
|
}
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(target),
|
||||||
|
Some(EntityType::Tolerance(_))
|
||||||
|
) {
|
||||||
|
self.open_tolerance_dialog(Some(target));
|
||||||
|
return iced::Task::none();
|
||||||
|
}
|
||||||
// Snapshot what we need before borrowing `self` mutably to open.
|
// Snapshot what we need before borrowing `self` mutably to open.
|
||||||
let Some(entity) = self.tabs[i].scene.document.get_entity(target) else {
|
let Some(entity) = self.tabs[i].scene.document.get_entity(target) else {
|
||||||
return iced::Task::none();
|
return iced::Task::none();
|
||||||
|
|
@ -166,10 +176,10 @@ impl super::OpenCADStudio {
|
||||||
};
|
};
|
||||||
|
|
||||||
if field.is_rich() {
|
if field.is_rich() {
|
||||||
self.open_mtext_editor(pos, Some(target), &value, height);
|
self.open_mtext_editor(pos, Some(target), &value, height, None);
|
||||||
self.unfocus_widgets()
|
self.unfocus_widgets()
|
||||||
} else {
|
} else {
|
||||||
self.open_text_inline(pos, Some(target), &value, height, field);
|
self.open_text_inline(pos, Some(target), &value, height, field, None);
|
||||||
iced::widget::operation::focus(iced::widget::Id::new(super::view::TEXT_INLINE_ID))
|
iced::widget::operation::focus(iced::widget::Id::new(super::view::TEXT_INLINE_ID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -183,6 +193,7 @@ impl super::OpenCADStudio {
|
||||||
initial: &str,
|
initial: &str,
|
||||||
height: f64,
|
height: f64,
|
||||||
field: TextEntityField,
|
field: TextEntityField,
|
||||||
|
creation: Option<Text>,
|
||||||
) {
|
) {
|
||||||
if handle.is_some_and(|h| self.tabs[self.active_tab].scene.is_layer_locked(h)) {
|
if handle.is_some_and(|h| self.tabs[self.active_tab].scene.is_layer_locked(h)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -193,6 +204,7 @@ impl super::OpenCADStudio {
|
||||||
height: if height > 0.0 { height } else { 0.25 },
|
height: if height > 0.0 { height } else { 0.25 },
|
||||||
editing: handle,
|
editing: handle,
|
||||||
field,
|
field,
|
||||||
|
creation,
|
||||||
screen_anchor: iced::Point::new(60.0, 90.0),
|
screen_anchor: iced::Point::new(60.0, 90.0),
|
||||||
};
|
};
|
||||||
if let Some(p) = self.tabs[self.active_tab].scene.selection.borrow().last_move_pos {
|
if let Some(p) = self.tabs[self.active_tab].scene.selection.borrow().last_move_pos {
|
||||||
|
|
@ -229,12 +241,18 @@ impl super::OpenCADStudio {
|
||||||
} else {
|
} else {
|
||||||
crate::command::WorkingPlane::default()
|
crate::command::WorkingPlane::default()
|
||||||
};
|
};
|
||||||
let position = plane.to_local(ed.pos);
|
let command_creation = ed.creation.is_some();
|
||||||
let mut t = Text::with_value(
|
let mut t = if let Some(mut prepared) = ed.creation {
|
||||||
&ed.value,
|
prepared.value = ed.value.clone();
|
||||||
Vector3::new(position.x, position.y, position.z),
|
prepared
|
||||||
)
|
} else {
|
||||||
.with_height(ed.height);
|
let position = plane.to_local(ed.pos);
|
||||||
|
Text::with_value(
|
||||||
|
&ed.value,
|
||||||
|
Vector3::new(position.x, position.y, position.z),
|
||||||
|
)
|
||||||
|
.with_height(ed.height)
|
||||||
|
};
|
||||||
// New text inherits the document's current text style (STYLE), not
|
// New text inherits the document's current text style (STYLE), not
|
||||||
// the entity default. See #92.
|
// the entity default. See #92.
|
||||||
let cur_style = self.tabs[i]
|
let cur_style = self.tabs[i]
|
||||||
|
|
@ -244,24 +262,48 @@ impl super::OpenCADStudio {
|
||||||
.current_text_style_name
|
.current_text_style_name
|
||||||
.clone();
|
.clone();
|
||||||
if !cur_style.is_empty() {
|
if !cur_style.is_empty() {
|
||||||
t.style = cur_style;
|
if t.style.trim().is_empty() {
|
||||||
}
|
t.style = cur_style;
|
||||||
let annotative = crate::scene::annotative::text_style_is_annotative(
|
|
||||||
&self.tabs[i].scene.document,
|
|
||||||
&t.style,
|
|
||||||
);
|
|
||||||
self.push_undo_snapshot(i, "TEXT");
|
|
||||||
let handle = self.commit_entity_handle(plane.place_entity(EntityType::Text(t)));
|
|
||||||
if annotative {
|
|
||||||
let scale = self.tabs[i].scene.current_annotation_scale_handle();
|
|
||||||
if let (Some(handle), Some(scale)) = (handle, scale) {
|
|
||||||
crate::scene::annotative::create_annotation_context(
|
|
||||||
&mut self.tabs[i].scene.document,
|
|
||||||
handle,
|
|
||||||
scale,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if command_creation {
|
||||||
|
let annotation_multiplier = if crate::scene::annotative::text_style_is_annotative(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
&t.style,
|
||||||
|
) {
|
||||||
|
self.tabs[i].scene.creation_annotation_multiplier()
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
let display_height = crate::entities::text::text_run_placement_at_scale(
|
||||||
|
&t,
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
annotation_multiplier as f32,
|
||||||
|
)
|
||||||
|
.height as f64
|
||||||
|
* annotation_multiplier;
|
||||||
|
if let Some(command) = self.tabs[i].suspended_cmd.as_mut() {
|
||||||
|
command.on_editor_display_height(display_height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "TEXT");
|
||||||
|
self.tabs[i].scene.document.header.current_text_style_name = t.style.clone();
|
||||||
|
let variable_height = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.text_styles
|
||||||
|
.iter()
|
||||||
|
.find(|style| style.name.eq_ignore_ascii_case(&t.style))
|
||||||
|
.is_none_or(|style| style.height <= 1.0e-9);
|
||||||
|
if variable_height
|
||||||
|
&& !matches!(
|
||||||
|
t.horizontal_alignment,
|
||||||
|
acadrust::entities::TextHorizontalAlignment::Aligned
|
||||||
|
)
|
||||||
|
{
|
||||||
|
self.tabs[i].scene.document.header.text_height = t.height;
|
||||||
|
}
|
||||||
|
let _ = self.commit_entity_handle(plane.place_entity(EntityType::Text(t)));
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
|
|
|
||||||
108
src/app/tolerance_dialog.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
use acadrust::{EntityType, Handle};
|
||||||
|
|
||||||
|
impl super::OpenCADStudio {
|
||||||
|
pub(super) fn open_tolerance_dialog(&mut self, editing: Option<Handle>) {
|
||||||
|
let i = self.active_tab;
|
||||||
|
if editing.is_some_and(|handle| self.tabs[i].scene.is_layer_locked(handle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let text = editing
|
||||||
|
.and_then(|handle| self.tabs[i].scene.document.get_entity(handle))
|
||||||
|
.and_then(|entity| match entity {
|
||||||
|
EntityType::Tolerance(tolerance) => Some(tolerance.text.as_str()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
self.geometric_tolerance = Some(
|
||||||
|
crate::ui::window::geometric_tolerance::State::from_text(editing, text),
|
||||||
|
);
|
||||||
|
self.active_modal = Some(super::ModalKind::GeometricTolerance);
|
||||||
|
self.modal_offset = iced::Vector::ZERO;
|
||||||
|
self.modal_resize = iced::Vector::ZERO;
|
||||||
|
self.modal_content_size = None;
|
||||||
|
self.modal_drag_last = None;
|
||||||
|
self.modal_dragging = false;
|
||||||
|
self.modal_resizing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn apply_tolerance_dialog_edit(&mut self) -> bool {
|
||||||
|
let Some(state) = self.geometric_tolerance.as_ref() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let Some(handle) = state.editing else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !state.is_valid() || self.tabs[self.active_tab].scene.is_layer_locked(handle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let text = state.to_text();
|
||||||
|
let i = self.active_tab;
|
||||||
|
let unchanged = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.is_some_and(|entity| {
|
||||||
|
matches!(entity, EntityType::Tolerance(tolerance) if tolerance.text == text)
|
||||||
|
});
|
||||||
|
if unchanged {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
self.push_undo_snapshot(i, "TOLERANCE");
|
||||||
|
let Some(EntityType::Tolerance(tolerance)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
tolerance.text = text;
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.bump_entities(&[(handle, crate::scene::ChangeKind::Modified)]);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn begin_tolerance_placement(&mut self) -> bool {
|
||||||
|
let Some(state) = self.geometric_tolerance.take() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !state.is_valid() || state.editing.is_some() {
|
||||||
|
self.geometric_tolerance = Some(state);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let i = self.active_tab;
|
||||||
|
let text = state.to_text();
|
||||||
|
let mut preview_entity = EntityType::Tolerance(
|
||||||
|
acadrust::entities::Tolerance::with_text(
|
||||||
|
acadrust::types::Vector3::ZERO,
|
||||||
|
text.clone(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
crate::scene::creation_style::apply_current_creation_styles(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
&mut preview_entity,
|
||||||
|
);
|
||||||
|
let preview_strokes = match &preview_entity {
|
||||||
|
EntityType::Tolerance(tolerance) => crate::entities::tolerance::preview_strokes(
|
||||||
|
tolerance,
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
),
|
||||||
|
_ => Vec::new(),
|
||||||
|
};
|
||||||
|
let mut command = crate::modules::annotate::tolerance_cmd::ToleranceCommand::with_text(
|
||||||
|
text,
|
||||||
|
preview_strokes,
|
||||||
|
);
|
||||||
|
use crate::command::CadCommand;
|
||||||
|
let plane = if self.tabs[i].editing_model_space() {
|
||||||
|
self.tabs[i].ucs_xform().working_plane()
|
||||||
|
} else {
|
||||||
|
crate::command::WorkingPlane::default()
|
||||||
|
};
|
||||||
|
command.set_working_plane(plane);
|
||||||
|
self.command_line.push_info(&command.prompt());
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
self.active_modal = None;
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -202,6 +202,16 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
// Command-line entry is shown uppercase.
|
// Command-line entry is shown uppercase.
|
||||||
self.command_line.input.push_str(&s.to_uppercase());
|
self.command_line.input.push_str(&s.to_uppercase());
|
||||||
self.command_line.cancel_history_navigation();
|
self.command_line.cancel_history_navigation();
|
||||||
|
// Live incremental search for INSERT/MINSERT (see CommandInput)
|
||||||
|
let live = self.command_line.input.clone();
|
||||||
|
let i = self.active_tab;
|
||||||
|
let (should_update, opts, prompt) = if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
|
||||||
|
if cmd.on_live_input(&live) { (true, cmd.options(), cmd.prompt()) } else { (false, Vec::new(), String::new()) }
|
||||||
|
} else { (false, Vec::new(), String::new()) };
|
||||||
|
if should_update {
|
||||||
|
self.command_line.set_step_options(opts);
|
||||||
|
if let Some(last) = self.command_line.history.last_mut() { if last.pinned { last.text = prompt; } }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.command_line.autocomplete_cursor = None;
|
self.command_line.autocomplete_cursor = None;
|
||||||
|
|
@ -231,6 +241,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
self.command_line.input.pop();
|
self.command_line.input.pop();
|
||||||
self.command_line.autocomplete_cursor = None;
|
self.command_line.autocomplete_cursor = None;
|
||||||
self.command_line.cancel_history_navigation();
|
self.command_line.cancel_history_navigation();
|
||||||
|
// Live incremental search for INSERT/MINSERT
|
||||||
|
{
|
||||||
|
let live = self.command_line.input.clone();
|
||||||
|
let i = self.active_tab;
|
||||||
|
let (should_update, opts, prompt) = if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
|
||||||
|
if cmd.on_live_input(&live) { (true, cmd.options(), cmd.prompt()) } else { (false, Vec::new(), String::new()) }
|
||||||
|
} else { (false, Vec::new(), String::new()) };
|
||||||
|
if should_update {
|
||||||
|
self.command_line.set_step_options(opts);
|
||||||
|
if let Some(last) = self.command_line.history.last_mut() { if last.pinned { last.text = prompt; } }
|
||||||
|
}
|
||||||
|
}
|
||||||
self.focus_cmd_input()
|
self.focus_cmd_input()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1140,9 +1162,36 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
if matches!(
|
if matches!(
|
||||||
item.action,
|
item.action,
|
||||||
GripMenuAction::Stretch
|
GripMenuAction::Stretch
|
||||||
|
| GripMenuAction::MoveWithText
|
||||||
|
| GripMenuAction::MoveWithDimLine
|
||||||
| GripMenuAction::MoveWithLeader
|
| GripMenuAction::MoveWithLeader
|
||||||
| GripMenuAction::MoveIndependent
|
| GripMenuAction::MoveIndependent
|
||||||
) {
|
) {
|
||||||
|
let is_dimension = matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(popup.handle),
|
||||||
|
Some(acadrust::EntityType::Dimension(_))
|
||||||
|
);
|
||||||
|
if is_dimension {
|
||||||
|
let movement = match item.action {
|
||||||
|
GripMenuAction::MoveWithDimLine => Some("Keep dim line with text"),
|
||||||
|
GripMenuAction::MoveWithLeader => Some("Move text, add leader"),
|
||||||
|
GripMenuAction::MoveIndependent => Some("Move text, no leader"),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(movement) = movement {
|
||||||
|
crate::entities::dim_override::set_property(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
popup.handle,
|
||||||
|
"dim_text_movement",
|
||||||
|
movement,
|
||||||
|
);
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.tabs[i].scene.bump_entities(&[(
|
||||||
|
popup.handle,
|
||||||
|
crate::scene::ChangeKind::Modified,
|
||||||
|
)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
// Stretch / Move = grab this grip. Engage it so the next
|
// Stretch / Move = grab this grip. Engage it so the next
|
||||||
// click places it (click-move-click) — same as picking the
|
// click places it (click-move-click) — same as picking the
|
||||||
// grip directly in the viewport. Without this the menu just
|
// grip directly in the viewport. Without this the menu just
|
||||||
|
|
@ -1153,13 +1202,22 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
.zip(self.tabs[i].selected_grips.iter())
|
.zip(self.tabs[i].selected_grips.iter())
|
||||||
.find(|(owner, g)| **owner == popup.handle && g.id == popup.grip_id)
|
.find(|(owner, g)| **owner == popup.handle && g.id == popup.grip_id)
|
||||||
{
|
{
|
||||||
// "Move with Leader" drags the whole multileader; the
|
// Only multileaders use the whole-object grip.
|
||||||
// others move just the picked grip.
|
let is_multileader = matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(popup.handle),
|
||||||
|
Some(acadrust::EntityType::MultiLeader(_))
|
||||||
|
);
|
||||||
let (grip_id, is_translate) =
|
let (grip_id, is_translate) =
|
||||||
if matches!(item.action, GripMenuAction::MoveWithLeader) {
|
if matches!(item.action, GripMenuAction::MoveWithLeader)
|
||||||
|
&& is_multileader
|
||||||
|
{
|
||||||
(crate::entities::multileader::MOVE_ALL_GRIP, true)
|
(crate::entities::multileader::MOVE_ALL_GRIP, true)
|
||||||
} else {
|
} else {
|
||||||
(popup.grip_id, g.is_midpoint)
|
(
|
||||||
|
popup.grip_id,
|
||||||
|
matches!(item.action, GripMenuAction::MoveWithText)
|
||||||
|
|| (!is_dimension && g.is_midpoint),
|
||||||
|
)
|
||||||
};
|
};
|
||||||
self.tabs[i].active_grip = Some(GripEdit::single(
|
self.tabs[i].active_grip = Some(GripEdit::single(
|
||||||
popup.handle,
|
popup.handle,
|
||||||
|
|
@ -1672,9 +1730,18 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StyleKey::MLeaderStyle => {
|
StyleKey::MLeaderStyle => {
|
||||||
self.ribbon.active_mleader_style = name.clone();
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
self.tabs[i].active_mleader_style = name;
|
|
||||||
|
self.ribbon.active_mleader_style = name.clone();
|
||||||
|
self.tabs[i].active_mleader_style = name.clone();
|
||||||
|
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.current_mleader_style_name = name;
|
||||||
|
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
}
|
}
|
||||||
StyleKey::TableStyle => {
|
StyleKey::TableStyle => {
|
||||||
self.ribbon.active_table_style = name;
|
self.ribbon.active_table_style = name;
|
||||||
|
|
@ -1839,7 +1906,90 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
self.push_undo_snapshot(i, "CHPROP");
|
self.push_undo_snapshot(i, "CHPROP");
|
||||||
|
|
||||||
if field == "vscale_std" {
|
if field == "tol_text_style" {
|
||||||
|
use crate::entities::dim_override as dov;
|
||||||
|
use acadrust::xdata::XDataValue;
|
||||||
|
let style_handle = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.text_styles
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.name.eq_ignore_ascii_case(&value))
|
||||||
|
.map(|entry| entry.handle);
|
||||||
|
if let Some(style_handle) = style_handle {
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle)
|
||||||
|
|| !matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
Some(acadrust::EntityType::Tolerance(_))
|
||||||
|
)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
dov::set(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
dov::DIMTXSTY,
|
||||||
|
Some(XDataValue::Handle(style_handle)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field == "tol_dim_style" {
|
||||||
|
let style = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.dim_styles
|
||||||
|
.iter()
|
||||||
|
.find(|entry| entry.name.eq_ignore_ascii_case(&value))
|
||||||
|
.map(|entry| (entry.handle, entry.name.clone(), entry.annotative));
|
||||||
|
if let Some((style_handle, style_name, annotative)) = style {
|
||||||
|
let scale = self.tabs[i].scene.creation_annotation_scale_handle();
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(acadrust::EntityType::Tolerance(tolerance)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
tolerance.dimension_style_handle = Some(style_handle);
|
||||||
|
tolerance.dimension_style_name = style_name.clone();
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
crate::scene::annotative::set_entity_annotative(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
annotative,
|
||||||
|
);
|
||||||
|
if annotative {
|
||||||
|
if let Some(scale) = scale {
|
||||||
|
crate::scene::annotative::create_annotation_context(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
scale,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field.starts_with("dim_") {
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
Some(acadrust::EntityType::Dimension(_))
|
||||||
|
) {
|
||||||
|
crate::entities::dim_override::set_property(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
field,
|
||||||
|
&value,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field == "vscale_std" {
|
||||||
for &handle in &handles {
|
for &handle in &handles {
|
||||||
if matches!(
|
if matches!(
|
||||||
self.tabs[i].scene.document.get_entity(handle),
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
|
@ -2109,7 +2259,43 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
self.tabs[i].scene.bump_entities(&changes);
|
self.tabs[i].scene.bump_entities(&changes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if field == "tbl_style_handle" {
|
||||||
|
let style_handle = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.find_map(|(handle, object)| match object {
|
||||||
|
acadrust::objects::ObjectType::TableStyle(style)
|
||||||
|
if style.name.eq_ignore_ascii_case(value.trim()) =>
|
||||||
|
{
|
||||||
|
Some(*handle)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let Some(style_handle) = style_handle {
|
||||||
|
for &handle in &handles {
|
||||||
|
if let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
table.table_style_handle = Some(style_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field == "transparency" {
|
||||||
|
for &handle in &handles {
|
||||||
|
if self.tabs[i].scene.is_layer_locked(handle) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(entity) = self.tabs[i].scene.document.get_entity_mut(handle) {
|
||||||
|
crate::scene::view::dispatch::apply_common_prop(entity, "transparency", &value);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if field == "plot_style" {
|
} else if field == "plot_style" {
|
||||||
|
if self.tabs[i].scene.document.header.plotstyle_mode {
|
||||||
|
self.refresh_properties();
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
// Named plot-style pick: ByLayer / ByBlock clear the
|
// Named plot-style pick: ByLayer / ByBlock clear the
|
||||||
// handle; a named style resolves through the drawing's
|
// handle; a named style resolves through the drawing's
|
||||||
// ACAD_PLOTSTYLENAME dictionary to its placeholder handle.
|
// ACAD_PLOTSTYLENAME dictionary to its placeholder handle.
|
||||||
|
|
@ -2139,6 +2325,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
common.plotstyle_flags = 1;
|
common.plotstyle_flags = 1;
|
||||||
common.plotstyle_handle = None;
|
common.plotstyle_handle = None;
|
||||||
}
|
}
|
||||||
|
"Normal" => {
|
||||||
|
common.plotstyle_flags = 2;
|
||||||
|
common.plotstyle_handle = None;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(h) = ph {
|
if let Some(h) = ph {
|
||||||
common.plotstyle_flags = 3;
|
common.plotstyle_flags = 3;
|
||||||
|
|
@ -2179,6 +2369,10 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
common.material_flags = 1;
|
common.material_flags = 1;
|
||||||
common.material_handle = None;
|
common.material_handle = None;
|
||||||
}
|
}
|
||||||
|
"Global" => {
|
||||||
|
common.material_flags = 2;
|
||||||
|
common.material_handle = None;
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(h) = mat_handle {
|
if let Some(h) = mat_handle {
|
||||||
common.material_flags = 3;
|
common.material_flags = 3;
|
||||||
|
|
@ -2231,13 +2425,75 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if field.starts_with("tbl_") {
|
||||||
|
for &handle in &handles {
|
||||||
|
if let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
table.block_record_handle = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.invalidate_property_targets(i, &handles);
|
self.invalidate_property_targets(i, &handles);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
|
self.tabs[i].properties.edit_choice_open = false;
|
||||||
if field == "spline_method" {
|
if field == "spline_method" {
|
||||||
self.tabs[i].properties.prop_vertex = 0;
|
self.tabs[i].properties.prop_vertex = 0;
|
||||||
self.tabs[i].properties.prop_vertex_indicator_active = false;
|
self.tabs[i].properties.prop_vertex_indicator_active = false;
|
||||||
}
|
}
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
|
} else {
|
||||||
|
match field {
|
||||||
|
"transparency" => {
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
"material" => {
|
||||||
|
let mat_handle: Option<acadrust::Handle> = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.find_map(|(h, o)| match o {
|
||||||
|
acadrust::objects::ObjectType::Material(m) if m.name == value => {
|
||||||
|
Some(*h)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
match value.as_str() {
|
||||||
|
"ByLayer" | "ByBlock" | "Global" => {
|
||||||
|
self.tabs[i].scene.document.header.current_material_handle =
|
||||||
|
acadrust::Handle::NULL;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if let Some(h) = mat_handle {
|
||||||
|
self.tabs[i].scene.document.header.current_material_handle =
|
||||||
|
h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
"plot_style" => {
|
||||||
|
if self.tabs[i].scene.document.header.plotstyle_mode {
|
||||||
|
self.refresh_properties();
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
|
match value.as_str() {
|
||||||
|
"ByBlock" => {
|
||||||
|
self.tabs[i].scene.document.header.current_plotstyle_type = 1
|
||||||
|
}
|
||||||
|
"Normal" => {
|
||||||
|
self.tabs[i].scene.document.header.current_plotstyle_type = 2
|
||||||
|
}
|
||||||
|
_ => self.tabs[i].scene.document.header.current_plotstyle_type = 0,
|
||||||
|
}
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -2298,7 +2554,30 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
self.push_undo_snapshot(i, "CHPROP");
|
self.push_undo_snapshot(i, "CHPROP");
|
||||||
if field == "block" {
|
if field == "tbl_style_handle" {
|
||||||
|
let style_handle = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.find_map(|(handle, object)| match object {
|
||||||
|
acadrust::objects::ObjectType::TableStyle(style)
|
||||||
|
if style.name.eq_ignore_ascii_case(val.trim()) =>
|
||||||
|
{
|
||||||
|
Some(*handle)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let Some(style_handle) = style_handle {
|
||||||
|
for &handle in &handles {
|
||||||
|
if let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
table.table_style_handle = Some(style_handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if field == "block" {
|
||||||
// Name row on a block reference: an existing name
|
// Name row on a block reference: an existing name
|
||||||
// re-points the selected inserts; a new one renames
|
// re-points the selected inserts; a new one renames
|
||||||
// the definition they share. Commit closes the list.
|
// the definition they share. Commit closes the list.
|
||||||
|
|
@ -2344,6 +2623,40 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
match field {
|
match field {
|
||||||
|
"tol_text_height" => {
|
||||||
|
use crate::entities::dim_override as dov;
|
||||||
|
let trimmed = val.trim();
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
dov::set(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
dov::DIMTXT,
|
||||||
|
None,
|
||||||
|
);
|
||||||
|
} else if let Ok(height) = trimmed.parse::<f64>() {
|
||||||
|
if height > 0.0 {
|
||||||
|
dov::set(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
dov::DIMTXT,
|
||||||
|
Some(acadrust::xdata::XDataValue::Real(height)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ if field.starts_with("dim_") => {
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
Some(acadrust::EntityType::Dimension(_))
|
||||||
|
) {
|
||||||
|
crate::entities::dim_override::set_property(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
field,
|
||||||
|
&val,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
"hyperlink" => {
|
"hyperlink" => {
|
||||||
// Stored in the standard PE_URL XDATA
|
// Stored in the standard PE_URL XDATA
|
||||||
// record; an empty value clears it.
|
// record; an empty value clears it.
|
||||||
|
|
@ -2466,10 +2779,25 @@ pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if field.starts_with("tbl_") {
|
||||||
|
for &handle in &handles {
|
||||||
|
if let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
table.block_record_handle = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.invalidate_property_targets(i, &handles);
|
self.invalidate_property_targets(i, &handles);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
let _ = self.tabs[i]
|
||||||
|
.properties
|
||||||
|
.edit_buf
|
||||||
|
.remove(&crate::ui::properties::FieldKey::Geom(field));
|
||||||
|
self.refresh_properties();
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,9 @@ impl OpenCADStudio {
|
||||||
if let Some(previous) = self.print_all_plot_window_prev.take() {
|
if let Some(previous) = self.print_all_plot_window_prev.take() {
|
||||||
self.plot_window = previous;
|
self.plot_window = previous;
|
||||||
}
|
}
|
||||||
|
if let Some(previous) = self.print_all_plot_setup_prev.take() {
|
||||||
|
self.plot_setup_template = previous;
|
||||||
|
}
|
||||||
self.print_all_options = false;
|
self.print_all_options = false;
|
||||||
self.active_modal = Some(PrintAll);
|
self.active_modal = Some(PrintAll);
|
||||||
self.reset_modal_geometry();
|
self.reset_modal_geometry();
|
||||||
|
|
@ -192,6 +195,7 @@ impl OpenCADStudio {
|
||||||
self.attr_editor_selected = 0;
|
self.attr_editor_selected = 0;
|
||||||
self.attr_editor_tab = crate::ui::window::attribute_editor::AttrTab::Attribute;
|
self.attr_editor_tab = crate::ui::window::attribute_editor::AttrTab::Attribute;
|
||||||
}
|
}
|
||||||
|
Some(GeometricTolerance) => self.geometric_tolerance = None,
|
||||||
// Closing (✕) discards edits made since the last Apply — matching the
|
// Closing (✕) discards edits made since the last Apply — matching the
|
||||||
// style editors. Committing happens only through the Apply button.
|
// style editors. Committing happens only through the Apply button.
|
||||||
Some(Aliases) => self.alias_editor_rows.clear(),
|
Some(Aliases) => self.alias_editor_rows.clear(),
|
||||||
|
|
@ -231,6 +235,25 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Emit `SelectionChangedV4` to V4 plugins when the active tab's selection
|
||||||
|
/// set actually changed since the last broadcast.
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub(super) fn notify_plugins_selection_changed(&mut self) {
|
||||||
|
if self.active_tab >= self.tabs.len() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let i = self.active_tab;
|
||||||
|
let tab_id = self.tabs[i].id;
|
||||||
|
let fingerprint = self.tabs[i].scene.selection_fingerprint();
|
||||||
|
let key = (tab_id, fingerprint);
|
||||||
|
if self.last_plugin_selection == Some(key) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.last_plugin_selection = Some(key);
|
||||||
|
let handles = self.tabs[i].scene.selected_handles_in_order();
|
||||||
|
crate::plugin::v4_support::publish_selection_changed_v4(tab_id, handles);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn update(&mut self, msg: Message) -> Task<Message> {
|
pub fn update(&mut self, msg: Message) -> Task<Message> {
|
||||||
let perf_started = crate::perf::enabled().then(Instant::now);
|
let perf_started = crate::perf::enabled().then(Instant::now);
|
||||||
let perf_label = perf_message_label(&msg);
|
let perf_label = perf_message_label(&msg);
|
||||||
|
|
@ -275,6 +298,11 @@ impl OpenCADStudio {
|
||||||
// The block panel watches the drawing's block list and rebuilds its
|
// The block panel watches the drawing's block list and rebuilds its
|
||||||
// thumbnails whenever the names change (BLOCK define, file open, …).
|
// thumbnails whenever the names change (BLOCK define, file open, …).
|
||||||
self.refresh_block_palette_if_stale();
|
self.refresh_block_palette_if_stale();
|
||||||
|
// Let V4 plugins observe selection changes that happened while handling
|
||||||
|
// this message (picking, window select, QSELECT, SELECTALL, grip edits,
|
||||||
|
// and plugin request draining).
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
self.notify_plugins_selection_changed();
|
||||||
// OTRACK acquires tracking points only while a command or grip drag is
|
// OTRACK acquires tracking points only while a command or grip drag is
|
||||||
// running; drop them once neither is active so the temporary tracking
|
// running; drop them once neither is active so the temporary tracking
|
||||||
// points / vectors disappear when the command ends (issue #64).
|
// points / vectors disappear when the command ends (issue #64).
|
||||||
|
|
@ -1573,9 +1601,38 @@ impl OpenCADStudio {
|
||||||
self.command_line.input = s;
|
self.command_line.input = s;
|
||||||
return Task::batch(vec![sweep, self.update(Message::CommandSubmit)]);
|
return Task::batch(vec![sweep, self.update(Message::CommandSubmit)]);
|
||||||
}
|
}
|
||||||
self.command_line.input = s;
|
let live_input = s.clone();
|
||||||
|
self.command_line.input = live_input.clone();
|
||||||
self.command_line.autocomplete_cursor = None;
|
self.command_line.autocomplete_cursor = None;
|
||||||
self.command_line.cancel_history_navigation();
|
self.command_line.cancel_history_navigation();
|
||||||
|
// Live incremental search for INSERT/MINSERT: update picker on each keystroke
|
||||||
|
// without requiring Enter. Performance-first: uses upper/lower caches
|
||||||
|
// and partial sort (O(k)) so per-keystroke is <0.2ms even for 10k blocks.
|
||||||
|
// Suggestion count is in direct relation to CLIPROMPTLINES (picker limit).
|
||||||
|
{
|
||||||
|
let i = self.active_tab;
|
||||||
|
// Capture prompt/options while holding cmd borrow, then release before
|
||||||
|
// borrowing command_line to satisfy borrow checker.
|
||||||
|
let (should_update, opts, prompt) = if let Some(cmd) = self.tabs[i].active_cmd.as_mut() {
|
||||||
|
if cmd.on_live_input(&live_input) {
|
||||||
|
(true, cmd.options(), cmd.prompt())
|
||||||
|
} else {
|
||||||
|
(false, Vec::new(), String::new())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(false, Vec::new(), String::new())
|
||||||
|
};
|
||||||
|
if should_update {
|
||||||
|
self.command_line.set_step_options(opts);
|
||||||
|
// Update pinned prompt text live without pushing new history entry
|
||||||
|
// (avoids flooding history with one entry per keystroke).
|
||||||
|
if let Some(last) = self.command_line.history.last_mut() {
|
||||||
|
if last.pinned {
|
||||||
|
last.text = prompt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Task::batch(vec![sweep, Task::none()])
|
Task::batch(vec![sweep, Task::none()])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3792,6 +3849,40 @@ impl OpenCADStudio {
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
Message::ToleranceDialogField(field) => {
|
||||||
|
if let Some(state) = self.geometric_tolerance.as_mut() {
|
||||||
|
state.apply_field(field);
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ToleranceDialogToggle(toggle) => {
|
||||||
|
if let Some(state) = self.geometric_tolerance.as_mut() {
|
||||||
|
state.apply_toggle(toggle);
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ToleranceDialogApply => {
|
||||||
|
self.apply_tolerance_dialog_edit();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::ToleranceDialogOk => {
|
||||||
|
let editing = self
|
||||||
|
.geometric_tolerance
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|state| state.editing)
|
||||||
|
.is_some();
|
||||||
|
if editing {
|
||||||
|
if self.apply_tolerance_dialog_edit() {
|
||||||
|
self.geometric_tolerance = None;
|
||||||
|
self.active_modal = None;
|
||||||
|
self.reset_modal_geometry();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.begin_tolerance_placement();
|
||||||
|
self.reset_modal_geometry();
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::SetLinearFormat(code) => {
|
Message::SetLinearFormat(code) => {
|
||||||
self.units_popup_open = false;
|
self.units_popup_open = false;
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
|
|
@ -3966,10 +4057,7 @@ impl OpenCADStudio {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::MTextHeight(s) => {
|
Message::MTextHeight(s) => {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
self.mtext_apply_span_number("height", s);
|
||||||
ed.height = s;
|
|
||||||
}
|
|
||||||
self.rebuild_mtext_preview();
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::MTextRectWidth(width) => {
|
Message::MTextRectWidth(width) => {
|
||||||
|
|
@ -4001,26 +4089,128 @@ impl OpenCADStudio {
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::MTextOblique(s) => {
|
Message::MTextOblique(s) => {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
self.mtext_apply_span_number("oblique", s);
|
||||||
ed.oblique = s;
|
|
||||||
}
|
|
||||||
self.rebuild_mtext_preview();
|
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::MTextWidth(s) => {
|
Message::MTextWidth(s) => {
|
||||||
|
self.mtext_apply_span_number("width", s);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextCharSpace(s) => {
|
||||||
|
self.mtext_apply_span_number("tracking", s);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextUndo => {
|
||||||
|
self.mtext_undo();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextRedo => {
|
||||||
|
self.mtext_redo();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextStack => {
|
||||||
|
self.mtext_stack_selection();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextClearFormatting => {
|
||||||
|
self.mtext_clear_formatting();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextInsert(value) => {
|
||||||
|
self.mtext_type(&value);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextAnnotative(value) => {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
ed.width = s;
|
ed.annotative = value;
|
||||||
}
|
}
|
||||||
self.rebuild_mtext_preview();
|
self.rebuild_mtext_preview();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
Message::MTextCharSpace(s) => {
|
Message::MTextColumnMode(mode) => {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
ed.char_space = s;
|
match mode.as_str() {
|
||||||
|
"Static" => {
|
||||||
|
ed.column_type = 1;
|
||||||
|
ed.column_auto_height = false;
|
||||||
|
}
|
||||||
|
"Dynamic auto" => {
|
||||||
|
ed.column_type = 2;
|
||||||
|
ed.column_auto_height = true;
|
||||||
|
}
|
||||||
|
"Dynamic manual" => {
|
||||||
|
ed.column_type = 2;
|
||||||
|
ed.column_auto_height = false;
|
||||||
|
}
|
||||||
|
_ => ed.column_type = 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.rebuild_mtext_preview();
|
self.rebuild_mtext_preview();
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
Message::MTextColumnCount(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.column_count = value;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextColumnWidth(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.column_width = value;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextColumnGutter(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.column_gutter = value;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextColumnHeight(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.rect_height = value;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextColumnFlowReversed(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.column_flow_reversed = value;
|
||||||
|
}
|
||||||
|
self.rebuild_mtext_preview();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextParagraphNumber(field, value) => {
|
||||||
|
self.mtext_apply_paragraph_number(field, value);
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextFindText(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.find_text = value;
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextReplaceText(value) => {
|
||||||
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
|
ed.replace_text = value;
|
||||||
|
}
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextFindNext => {
|
||||||
|
self.mtext_find_next();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextReplaceNext => {
|
||||||
|
self.mtext_replace_next();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
Message::MTextReplaceAll => {
|
||||||
|
self.mtext_replace_all();
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
Message::MTextJustify(ap) => {
|
Message::MTextJustify(ap) => {
|
||||||
if let Some(ed) = self.mtext_editor.as_mut() {
|
if let Some(ed) = self.mtext_editor.as_mut() {
|
||||||
ed.attachment = ap;
|
ed.attachment = ap;
|
||||||
|
|
@ -4247,7 +4437,7 @@ impl OpenCADStudio {
|
||||||
.push_error(crate::t!("DRAWORDER: select entities first.").as_ref());
|
.push_error(crate::t!("DRAWORDER: select entities first.").as_ref());
|
||||||
} else {
|
} else {
|
||||||
use crate::command::CadCommand;
|
use crate::command::CadCommand;
|
||||||
let cmd = super::commands::DrawOrderRefCommand::new(to_move, above);
|
let cmd = super::commands::DrawOrderCommand::for_reference_pick(to_move, above);
|
||||||
self.command_line.push_info(&cmd.prompt());
|
self.command_line.push_info(&cmd.prompt());
|
||||||
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
self.tabs[i].active_cmd = Some(Box::new(cmd));
|
||||||
}
|
}
|
||||||
|
|
@ -4769,6 +4959,47 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"tbl_title_suppressed" | "tbl_header_suppressed" => {
|
||||||
|
let next = {
|
||||||
|
let document = &self.tabs[i].scene.document;
|
||||||
|
let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
document.get_entity(handle)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let table_style = table.table_style_handle.and_then(|style_handle| {
|
||||||
|
document.objects.get(&style_handle).and_then(|object| {
|
||||||
|
match object {
|
||||||
|
acadrust::objects::ObjectType::TableStyle(style) => {
|
||||||
|
Some(style)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let current = if field == "tbl_title_suppressed" {
|
||||||
|
crate::entities::table::resolved_title_suppressed(
|
||||||
|
table,
|
||||||
|
table_style,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
crate::entities::table::resolved_header_suppressed(
|
||||||
|
table,
|
||||||
|
table_style,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
!current
|
||||||
|
};
|
||||||
|
if let Some(entity) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
crate::scene::view::dispatch::apply_geom_prop(
|
||||||
|
entity,
|
||||||
|
field,
|
||||||
|
if next { "true" } else { "false" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if let Some(entity) =
|
if let Some(entity) =
|
||||||
self.tabs[i].scene.document.get_entity_mut(handle)
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
|
@ -4780,6 +5011,15 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if field.starts_with("tbl_") {
|
||||||
|
for &handle in &handles {
|
||||||
|
if let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
{
|
||||||
|
table.block_record_handle = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
self.invalidate_property_targets(i, &handles);
|
self.invalidate_property_targets(i, &handles);
|
||||||
self.tabs[i].dirty = true;
|
self.tabs[i].dirty = true;
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
|
|
@ -4795,6 +5035,9 @@ impl OpenCADStudio {
|
||||||
match self.tabs[i].scene.document.get_entity(handles[0]) {
|
match self.tabs[i].scene.document.get_entity(handles[0]) {
|
||||||
Some(acadrust::EntityType::LwPolyline(p)) => p.vertices.len(),
|
Some(acadrust::EntityType::LwPolyline(p)) => p.vertices.len(),
|
||||||
Some(acadrust::EntityType::Polyline2D(p)) => p.vertices.len(),
|
Some(acadrust::EntityType::Polyline2D(p)) => p.vertices.len(),
|
||||||
|
Some(acadrust::EntityType::Table(table)) => {
|
||||||
|
table.row_count().saturating_mul(table.column_count())
|
||||||
|
}
|
||||||
_ => 0,
|
_ => 0,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -4806,6 +5049,7 @@ impl OpenCADStudio {
|
||||||
let next = (cur + delta as i64).rem_euclid(n as i64) as usize;
|
let next = (cur + delta as i64).rem_euclid(n as i64) as usize;
|
||||||
self.tabs[i].properties.prop_vertex = next;
|
self.tabs[i].properties.prop_vertex = next;
|
||||||
self.tabs[i].properties.prop_vertex_indicator_active = next != cur as usize;
|
self.tabs[i].properties.prop_vertex_indicator_active = next != cur as usize;
|
||||||
|
crate::entities::table::set_prop_current_cell(next);
|
||||||
self.refresh_properties();
|
self.refresh_properties();
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
@ -4952,8 +5196,25 @@ impl OpenCADStudio {
|
||||||
// line with the rest of the dim-colour stack (index-only through
|
// line with the rest of the dim-colour stack (index-only through
|
||||||
// the file layer). Guarded to leaders / dimensions so a mixed
|
// the file layer). Guarded to leaders / dimensions so a mixed
|
||||||
// selection can't stamp the override onto other entities.
|
// selection can't stamp the override onto other entities.
|
||||||
if field == "dim_line_color" {
|
if matches!(
|
||||||
|
field.as_str(),
|
||||||
|
"dim_line_color"
|
||||||
|
| "dim_ext_line_color"
|
||||||
|
| "dim_text_color"
|
||||||
|
| "dim_text_fill_color"
|
||||||
|
) {
|
||||||
|
let fill_mode = (field == "dim_text_fill_color").then(|| match color {
|
||||||
|
acadrust::types::Color::None => 0,
|
||||||
|
acadrust::types::Color::ByBlock => 1,
|
||||||
|
_ => 2,
|
||||||
|
});
|
||||||
let aci = color.approximate_index();
|
let aci = color.approximate_index();
|
||||||
|
let code = match field.as_str() {
|
||||||
|
"dim_ext_line_color" => crate::entities::dim_override::DIMCLRE,
|
||||||
|
"dim_text_color" => crate::entities::dim_override::DIMCLRT,
|
||||||
|
"dim_text_fill_color" => crate::entities::dim_override::DIMTFILLCLR,
|
||||||
|
_ => crate::entities::dim_override::DIMCLRD,
|
||||||
|
};
|
||||||
let targets: Vec<acadrust::Handle> = handles
|
let targets: Vec<acadrust::Handle> = handles
|
||||||
.iter()
|
.iter()
|
||||||
.copied()
|
.copied()
|
||||||
|
|
@ -4968,10 +5229,23 @@ impl OpenCADStudio {
|
||||||
if !targets.is_empty() {
|
if !targets.is_empty() {
|
||||||
self.push_undo_snapshot(i, "CHPROP");
|
self.push_undo_snapshot(i, "CHPROP");
|
||||||
for &handle in &targets {
|
for &handle in &targets {
|
||||||
|
if field == "dim_text_fill_color" {
|
||||||
|
crate::entities::dim_override::set(
|
||||||
|
&mut self.tabs[i].scene.document,
|
||||||
|
handle,
|
||||||
|
crate::entities::dim_override::DIMTFILL,
|
||||||
|
Some(acadrust::xdata::XDataValue::Integer16(
|
||||||
|
fill_mode.unwrap_or(2),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
if fill_mode != Some(2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
crate::entities::dim_override::set(
|
crate::entities::dim_override::set(
|
||||||
&mut self.tabs[i].scene.document,
|
&mut self.tabs[i].scene.document,
|
||||||
handle,
|
handle,
|
||||||
crate::entities::dim_override::DIMCLRD,
|
code,
|
||||||
Some(acadrust::xdata::XDataValue::Integer16(aci)),
|
Some(acadrust::xdata::XDataValue::Integer16(aci)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -4982,6 +5256,57 @@ impl OpenCADStudio {
|
||||||
}
|
}
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
if matches!(
|
||||||
|
field.as_str(),
|
||||||
|
"tbl_cell_content_color" | "tbl_cell_background_color"
|
||||||
|
) {
|
||||||
|
let cell_index = self.tabs[i].properties.prop_vertex;
|
||||||
|
if !handles.is_empty() {
|
||||||
|
self.push_undo_snapshot(i, "TABLE CELL COLOR");
|
||||||
|
for &handle in &handles {
|
||||||
|
let Some(acadrust::EntityType::Table(table)) =
|
||||||
|
self.tabs[i].scene.document.get_entity_mut(handle)
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
let columns = table.column_count();
|
||||||
|
if columns == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Some(cell) = table.cell_mut(
|
||||||
|
cell_index / columns,
|
||||||
|
cell_index % columns,
|
||||||
|
) {
|
||||||
|
use acadrust::entities::table::CellStateFlags;
|
||||||
|
if cell.state.intersects(
|
||||||
|
CellStateFlags::FORMAT_LOCKED
|
||||||
|
| CellStateFlags::FORMAT_READ_ONLY,
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let style = cell.style.get_or_insert_with(Default::default);
|
||||||
|
if field == "tbl_cell_content_color" {
|
||||||
|
style.content_color = color;
|
||||||
|
style.property_flags.insert(
|
||||||
|
acadrust::entities::table::CellStylePropertyFlags::CONTENT_COLOR,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
style.background_color = color;
|
||||||
|
style.fill_enabled = true;
|
||||||
|
style.property_flags.insert(
|
||||||
|
acadrust::entities::table::CellStylePropertyFlags::BACKGROUND_COLOR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.block_record_handle = None;
|
||||||
|
}
|
||||||
|
self.invalidate_property_targets(i, &handles);
|
||||||
|
self.tabs[i].properties.open_color_field = None;
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
|
self.refresh_properties();
|
||||||
|
}
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
if !handles.is_empty() {
|
if !handles.is_empty() {
|
||||||
self.push_undo_snapshot(i, "CHPROP");
|
self.push_undo_snapshot(i, "CHPROP");
|
||||||
let idx = if field == "gradient_color_2" { 1 } else { 0 };
|
let idx = if field == "gradient_color_2" { 1 } else { 0 };
|
||||||
|
|
@ -6438,9 +6763,33 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
Message::AutoSave => self.on_autosave(),
|
Message::AutoSave => self.on_autosave(),
|
||||||
|
|
||||||
|
Message::ThumbnailCaptureFrame => self.on_thumbnail_capture_frame(),
|
||||||
|
|
||||||
|
Message::ThumbnailCaptureFinished => {
|
||||||
|
self.thumbnail_capture_clean = false;
|
||||||
|
Task::none()
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
Message::SaveFinished(outcome) => self.on_save_finished(outcome),
|
Message::SaveFinished(outcome) => self.on_save_finished(outcome),
|
||||||
|
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
Message::WebSaveScreenshot {
|
||||||
|
tab_id,
|
||||||
|
filename,
|
||||||
|
ext,
|
||||||
|
version,
|
||||||
|
bounds,
|
||||||
|
screenshot,
|
||||||
|
} => self.on_web_save_screenshot(
|
||||||
|
tab_id,
|
||||||
|
filename,
|
||||||
|
ext,
|
||||||
|
version,
|
||||||
|
bounds,
|
||||||
|
screenshot,
|
||||||
|
),
|
||||||
|
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
Message::SaveFileInUseRetry => self.on_save_file_in_use_retry(),
|
Message::SaveFileInUseRetry => self.on_save_file_in_use_retry(),
|
||||||
|
|
||||||
|
|
@ -6847,6 +7196,9 @@ impl OpenCADStudio {
|
||||||
self.plot_dialog.style_missing = false;
|
self.plot_dialog.style_missing = false;
|
||||||
self.plot_dialog.plot_styles =
|
self.plot_dialog.plot_styles =
|
||||||
crate::io::plot_style::available_ctb_names();
|
crate::io::plot_style::available_ctb_names();
|
||||||
|
self.tabs[self.active_tab]
|
||||||
|
.scene
|
||||||
|
.invalidate_display_plot_style();
|
||||||
|
|
||||||
self.command_line.push_output(
|
self.command_line.push_output(
|
||||||
crate::tf!(
|
crate::tf!(
|
||||||
|
|
@ -6898,6 +7250,9 @@ impl OpenCADStudio {
|
||||||
self.plot_dialog.style_missing = false;
|
self.plot_dialog.style_missing = false;
|
||||||
self.plot_dialog.plot_styles =
|
self.plot_dialog.plot_styles =
|
||||||
crate::io::plot_style::available_ctb_names();
|
crate::io::plot_style::available_ctb_names();
|
||||||
|
self.tabs[self.active_tab]
|
||||||
|
.scene
|
||||||
|
.invalidate_display_plot_style();
|
||||||
self.command_line.push_output(crate::tf!(
|
self.command_line.push_output(crate::tf!(
|
||||||
"Plot style table saved to \"{}\".",
|
"Plot style table saved to \"{}\".",
|
||||||
path.display()
|
path.display()
|
||||||
|
|
@ -7621,6 +7976,7 @@ impl OpenCADStudio {
|
||||||
|
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
self.tabs[i].properties.color_picker_open = false;
|
self.tabs[i].properties.color_picker_open = false;
|
||||||
|
self.tabs[i].properties.open_color_field = None;
|
||||||
self.tabs[i].layers.color_picker_row = None;
|
self.tabs[i].layers.color_picker_row = None;
|
||||||
|
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
|
|
@ -1146,6 +1146,9 @@ pub(super) fn on_text_style_dialog_open(&mut self) -> Task<Message> {
|
||||||
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
Some(crate::app::ColorPickTarget::PropertiesBg) => {
|
||||||
Some(Message::PropBgColorChanged(color))
|
Some(Message::PropBgColorChanged(color))
|
||||||
}
|
}
|
||||||
|
Some(crate::app::ColorPickTarget::PropertiesField(field)) => {
|
||||||
|
Some(Message::PropColorFieldChanged { field, color })
|
||||||
|
}
|
||||||
Some(crate::app::ColorPickTarget::MText) => {
|
Some(crate::app::ColorPickTarget::MText) => {
|
||||||
Some(Message::MTextColorChanged(color))
|
Some(Message::MTextColorChanged(color))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,7 @@ pub(super) fn layout_entry_name(s: &str) -> &str {
|
||||||
s.trim_start_matches('*').trim_end_matches('*')
|
s.trim_start_matches('*').trim_end_matches('*')
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Infer the closest A-series paper label and orientation from sheet
|
/// Infer an A-series label when dimensions match; otherwise retain the size.
|
||||||
/// dimensions (mm). Falls back to A4 when nothing is close.
|
|
||||||
pub(super) fn paper_label_from_dims(w: f64, h: f64) -> (String, String) {
|
pub(super) fn paper_label_from_dims(w: f64, h: f64) -> (String, String) {
|
||||||
use crate::io::paper_sizes::PaperSize;
|
use crate::io::paper_sizes::PaperSize;
|
||||||
let orient = if w >= h { "Landscape" } else { "Portrait" };
|
let orient = if w >= h { "Landscape" } else { "Portrait" };
|
||||||
|
|
@ -76,7 +75,12 @@ pub(super) fn paper_label_from_dims(w: f64, h: f64) -> (String, String) {
|
||||||
best = (p.label().to_string(), err);
|
best = (p.label().to_string(), err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(best.0, orient.to_string())
|
let label = if best.1 <= 2.0 {
|
||||||
|
best.0
|
||||||
|
} else {
|
||||||
|
format!("{w:.2} × {h:.2} mm")
|
||||||
|
};
|
||||||
|
(label, orient.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn parse_plot_scale(s: &str) -> (f64, f64) {
|
pub(super) fn parse_plot_scale(s: &str) -> (f64, f64) {
|
||||||
|
|
|
||||||
|
|
@ -809,6 +809,7 @@ impl OpenCADStudio {
|
||||||
// render mode; the model-layout tab style is untouched.
|
// render mode; the model-layout tab style is untouched.
|
||||||
if self.tabs[i].scene.set_active_viewport_render_mode(mode) {
|
if self.tabs[i].scene.set_active_viewport_render_mode(mode) {
|
||||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(crate::tf!("Viewport visual style: {label}").as_ref());
|
.push_output(crate::tf!("Viewport visual style: {label}").as_ref());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
|
|
@ -827,6 +828,7 @@ impl OpenCADStudio {
|
||||||
// Re-upload face3d fills on the next frame — the render
|
// Re-upload face3d fills on the next frame — the render
|
||||||
// pipeline keys its upload cache off `geometry_epoch`.
|
// pipeline keys its upload cache off `geometry_epoch`.
|
||||||
self.tabs[i].scene.bump_geometry_no_blocks();
|
self.tabs[i].scene.bump_geometry_no_blocks();
|
||||||
|
self.tabs[i].dirty = true;
|
||||||
self.command_line
|
self.command_line
|
||||||
.push_output(crate::tf!("Visual style: {label}").as_ref());
|
.push_output(crate::tf!("Visual style: {label}").as_ref());
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
@ -2240,6 +2242,7 @@ impl OpenCADStudio {
|
||||||
let guide = crate::scene::WireModel {
|
let guide = crate::scene::WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
@ -3452,6 +3455,38 @@ impl OpenCADStudio {
|
||||||
})?
|
})?
|
||||||
});
|
});
|
||||||
if let Some(handle) = hit {
|
if let Some(handle) = hit {
|
||||||
|
let uses_surface_point = self.tabs[i]
|
||||||
|
.active_cmd
|
||||||
|
.as_ref()
|
||||||
|
.is_some_and(|command| command.entity_pick_uses_surface_point());
|
||||||
|
let entity_pick_point = if uses_surface_point {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.solid_click_point_for(p, view_rot2, eye2, bounds, handle)
|
||||||
|
.unwrap_or(pick_wcs)
|
||||||
|
} else {
|
||||||
|
pick_wcs
|
||||||
|
};
|
||||||
|
let entity_pick_direction = if uses_surface_point {
|
||||||
|
if matches!(
|
||||||
|
self.tabs[i].scene.document.get_entity(handle),
|
||||||
|
Some(acadrust::EntityType::Solid3D(_))
|
||||||
|
) {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.solid_planar_face_normal_at(handle, entity_pick_point)
|
||||||
|
} else {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.and_then(crate::entities::curve::entity_curve)
|
||||||
|
.and_then(|curve| curve.plane.normal())
|
||||||
|
.map(glam::DVec3::from_array)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
// Some commands (e.g. SS_CATCHMENT) need the entity
|
// Some commands (e.g. SS_CATCHMENT) need the entity
|
||||||
// body before `on_entity_pick` can advance.
|
// body before `on_entity_pick` can advance.
|
||||||
let inject_first = self.tabs[i]
|
let inject_first = self.tabs[i]
|
||||||
|
|
@ -3482,7 +3517,8 @@ impl OpenCADStudio {
|
||||||
let result = self.tabs[i].active_cmd.as_mut().map(|c| {
|
let result = self.tabs[i].active_cmd.as_mut().map(|c| {
|
||||||
// Shift-swap state for TRIM/EXTEND (#336).
|
// Shift-swap state for TRIM/EXTEND (#336).
|
||||||
c.set_shift(shift);
|
c.set_shift(shift);
|
||||||
c.on_entity_pick(handle, pick_wcs)
|
c.set_entity_pick_direction(entity_pick_direction);
|
||||||
|
c.on_entity_pick(handle, entity_pick_point)
|
||||||
});
|
});
|
||||||
// HATCHEDIT: after pick, inject hatch model data into the command.
|
// HATCHEDIT: after pick, inject hatch model data into the command.
|
||||||
if self.tabs[i]
|
if self.tabs[i]
|
||||||
|
|
@ -4009,8 +4045,7 @@ impl OpenCADStudio {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
// Block-internal hatch: resolve to the
|
// Block-internal hatch: resolve to the parent Insert.
|
||||||
// parent Insert (AutoCAD behaviour).
|
|
||||||
scene::pick::hit_test::click_hit_insert_hatch(
|
scene::pick::hit_test::click_hit_insert_hatch(
|
||||||
p,
|
p,
|
||||||
self.tabs[i].scene.insert_hatches_for_click().as_ref(),
|
self.tabs[i].scene.insert_hatches_for_click().as_ref(),
|
||||||
|
|
@ -4357,6 +4392,104 @@ impl OpenCADStudio {
|
||||||
).as_ref());
|
).as_ref());
|
||||||
return Task::none();
|
return Task::none();
|
||||||
}
|
}
|
||||||
|
let table_editor = self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.get_entity(handle)
|
||||||
|
.and_then(|entity| match entity {
|
||||||
|
AcadEntityType::Table(table) => {
|
||||||
|
let horizontal = glam::DVec3::new(
|
||||||
|
table.horizontal_direction.x,
|
||||||
|
table.horizontal_direction.y,
|
||||||
|
table.horizontal_direction.z,
|
||||||
|
)
|
||||||
|
.normalize_or(glam::DVec3::X);
|
||||||
|
let normal = glam::DVec3::new(
|
||||||
|
table.normal.x,
|
||||||
|
table.normal.y,
|
||||||
|
table.normal.z,
|
||||||
|
)
|
||||||
|
.normalize_or(glam::DVec3::Z);
|
||||||
|
let mut down = horizontal
|
||||||
|
.cross(normal)
|
||||||
|
.normalize_or(glam::DVec3::NEG_Y);
|
||||||
|
let table_style = table.table_style_handle.and_then(|style_handle| {
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.objects
|
||||||
|
.get(&style_handle)
|
||||||
|
.and_then(|object| match object {
|
||||||
|
acadrust::objects::ObjectType::TableStyle(style) => {
|
||||||
|
Some(style)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
let flows_up = crate::entities::table::resolved_flow_up(
|
||||||
|
table,
|
||||||
|
table_style,
|
||||||
|
);
|
||||||
|
if flows_up {
|
||||||
|
down = -down;
|
||||||
|
}
|
||||||
|
let origin = glam::DVec3::new(
|
||||||
|
table.insertion_point.x,
|
||||||
|
table.insertion_point.y,
|
||||||
|
table.insertion_point.z,
|
||||||
|
);
|
||||||
|
let relative = click_world - origin;
|
||||||
|
let x = relative.dot(horizontal);
|
||||||
|
let y = relative.dot(down);
|
||||||
|
if x < 0.0 || y < 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let column = table
|
||||||
|
.columns
|
||||||
|
.iter()
|
||||||
|
.scan(0.0, |offset, column| {
|
||||||
|
*offset += column.width;
|
||||||
|
Some(*offset)
|
||||||
|
})
|
||||||
|
.position(|end| x <= end)?;
|
||||||
|
let row = table
|
||||||
|
.rows
|
||||||
|
.iter()
|
||||||
|
.scan(0.0, |offset, row| {
|
||||||
|
*offset += row.height;
|
||||||
|
Some(*offset)
|
||||||
|
})
|
||||||
|
.position(|end| y <= end)?;
|
||||||
|
let locked = table.cell(row, column).is_some_and(|cell| {
|
||||||
|
use acadrust::entities::table::CellStateFlags;
|
||||||
|
cell.state.intersects(
|
||||||
|
CellStateFlags::CONTENT_LOCKED
|
||||||
|
| CellStateFlags::CONTENT_READ_ONLY,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
Some((
|
||||||
|
(!locked).then(|| {
|
||||||
|
crate::modules::annotate::table_cmd::TableCellEditCommand::new(
|
||||||
|
handle, table, row, column,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
row * table.column_count() + column,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let Some((command, cell_index)) = table_editor {
|
||||||
|
self.tabs[i].properties.prop_vertex = cell_index;
|
||||||
|
self.tabs[i].properties.prop_vertex_indicator_active = true;
|
||||||
|
crate::entities::table::set_prop_current_cell(cell_index);
|
||||||
|
self.refresh_properties();
|
||||||
|
if let Some(command) = command {
|
||||||
|
self.command_line
|
||||||
|
.push_info(&crate::command::CadCommand::prompt(&command));
|
||||||
|
self.tabs[i].active_cmd = Some(Box::new(command));
|
||||||
|
}
|
||||||
|
return Task::none();
|
||||||
|
}
|
||||||
// Any text-bearing entity opens its in-place editor
|
// Any text-bearing entity opens its in-place editor
|
||||||
// (plain box or rich MText editor, per type). A
|
// (plain box or rich MText editor, per type). A
|
||||||
// Leader resolves to the entity it annotates.
|
// Leader resolves to the entity it annotates.
|
||||||
|
|
@ -4487,7 +4620,10 @@ impl OpenCADStudio {
|
||||||
if let Some(AcadEntityType::Viewport(vp)) =
|
if let Some(AcadEntityType::Viewport(vp)) =
|
||||||
self.tabs[i].scene.document.get_entity(h)
|
self.tabs[i].scene.document.get_entity(h)
|
||||||
{
|
{
|
||||||
if Scene::is_content_viewport(vp) {
|
if !Scene::is_sheet_viewport(
|
||||||
|
&self.tabs[i].scene.document,
|
||||||
|
vp,
|
||||||
|
) {
|
||||||
Some(h)
|
Some(h)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|
@ -5160,8 +5296,7 @@ impl OpenCADStudio {
|
||||||
self.tabs[i].scene.set_current_layout(name.clone());
|
self.tabs[i].scene.set_current_layout(name.clone());
|
||||||
self.tabs[i].scene.deselect_all();
|
self.tabs[i].scene.deselect_all();
|
||||||
let switch_ms = perf_phase.elapsed().as_secs_f64() * 1000.0;
|
let switch_ms = perf_phase.elapsed().as_secs_f64() * 1000.0;
|
||||||
// UCS follows the pane: model header UCS in the Model tab, none
|
// UCS follows the active model, layout, or floating viewport pane.
|
||||||
// in plain paper space (a viewport's UCS is adopted on entry).
|
|
||||||
let perf_phase = Instant::now();
|
let perf_phase = Instant::now();
|
||||||
self.tabs[i].refresh_active_ucs();
|
self.tabs[i].refresh_active_ucs();
|
||||||
self.tabs[i].scene.restore_saved_camera();
|
self.tabs[i].scene.restore_saved_camera();
|
||||||
|
|
@ -5225,10 +5360,28 @@ impl OpenCADStudio {
|
||||||
self.push_undo_snapshot(i, "LAYOUT");
|
self.push_undo_snapshot(i, "LAYOUT");
|
||||||
match self.tabs[i].scene.document.add_layout(&new_name) {
|
match self.tabs[i].scene.document.add_layout(&new_name) {
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
// Override the acadrust default limits (12×9 imperial) with A4 landscape.
|
let layout_flags = i16::from(
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.paper_space_linetype_scaling,
|
||||||
|
) | (i16::from(
|
||||||
|
self.tabs[i]
|
||||||
|
.scene
|
||||||
|
.document
|
||||||
|
.header
|
||||||
|
.paper_space_limit_check,
|
||||||
|
) << 1);
|
||||||
|
let plot_style = self
|
||||||
|
.active_plot_style
|
||||||
|
.as_ref()
|
||||||
|
.map(|style| style.name.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
for obj in self.tabs[i].scene.document.objects.values_mut() {
|
for obj in self.tabs[i].scene.document.objects.values_mut() {
|
||||||
if let acadrust::objects::ObjectType::Layout(l) = obj {
|
if let acadrust::objects::ObjectType::Layout(l) = obj {
|
||||||
if l.name == new_name {
|
if l.name == new_name {
|
||||||
|
l.flags = layout_flags;
|
||||||
l.min_limits = (0.0, 0.0);
|
l.min_limits = (0.0, 0.0);
|
||||||
l.max_limits = (297.0, 210.0);
|
l.max_limits = (297.0, 210.0);
|
||||||
l.min_extents = (0.0, 0.0, 0.0);
|
l.min_extents = (0.0, 0.0, 0.0);
|
||||||
|
|
@ -5238,6 +5391,14 @@ impl OpenCADStudio {
|
||||||
l.plot_paper_units = 1;
|
l.plot_paper_units = 1;
|
||||||
l.plot_scale_numerator = 1.0;
|
l.plot_scale_numerator = 1.0;
|
||||||
l.plot_scale_denominator = 1.0;
|
l.plot_scale_denominator = 1.0;
|
||||||
|
l.plot_scale_type = 16;
|
||||||
|
l.plot_scale_factor = 1.0;
|
||||||
|
l.plot_type = 5;
|
||||||
|
l.plot_flags.use_standard_scale = true;
|
||||||
|
l.plot_flags.print_lineweights = true;
|
||||||
|
l.plot_flags.plot_plot_styles = !plot_style.is_empty();
|
||||||
|
l.plot_flags.show_plot_styles = !plot_style.is_empty();
|
||||||
|
l.plot_style_sheet = plot_style;
|
||||||
l.paper_size = "ISO_A4_(297.00_x_210.00_MM)".into();
|
l.paper_size = "ISO_A4_(297.00_x_210.00_MM)".into();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ use viewcube::{viewcube_nav_controls, viewcube_ucs_picker, UCS_PICKER_W};
|
||||||
// the `view::` path as before the split.
|
// the `view::` path as before the split.
|
||||||
pub(in crate::app) use overlay::{MTEXT_TEXT_ID, TEXT_INLINE_ID};
|
pub(in crate::app) use overlay::{MTEXT_TEXT_ID, TEXT_INLINE_ID};
|
||||||
|
|
||||||
|
pub(in crate::app) const VIEWPORT_CAPTURE_BOUNDS_ID: &str = "viewport-capture-bounds";
|
||||||
|
|
||||||
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_REGION_PX;
|
const VIEWCUBE_HIT_SIZE: f32 = VIEWCUBE_REGION_PX;
|
||||||
/// The desk shown around the sheet, as the widget wants it. One definition,
|
/// The desk shown around the sheet, as the widget wants it. One definition,
|
||||||
/// shared with the renderer that clears the sheet viewport to the same thing.
|
/// shared with the renderer that clears the sheet viewport to the same thing.
|
||||||
|
|
@ -184,6 +186,7 @@ impl OpenCADStudio {
|
||||||
pub fn view_main(&self) -> Element<'_, Message> {
|
pub fn view_main(&self) -> Element<'_, Message> {
|
||||||
let i = self.active_tab;
|
let i = self.active_tab;
|
||||||
let tab = &self.tabs[i];
|
let tab = &self.tabs[i];
|
||||||
|
let thumbnail_capture_clean = self.thumbnail_capture_clean;
|
||||||
let theme_text = self.active_theme.palette().background.base.text;
|
let theme_text = self.active_theme.palette().background.base.text;
|
||||||
let viewcube_text_color = [
|
let viewcube_text_color = [
|
||||||
theme_text.r,
|
theme_text.r,
|
||||||
|
|
@ -229,8 +232,10 @@ impl OpenCADStudio {
|
||||||
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
let (vw, vh) = tab.scene.selection.borrow().vp_size;
|
||||||
tab.scene.active_model_tile_bounds(vw, vh).width
|
tab.scene.active_model_tile_bounds(vw, vh).width
|
||||||
};
|
};
|
||||||
let viewcube_visible =
|
let viewcube_visible = self.show_viewcube
|
||||||
self.show_viewcube && !tab.is_start && viewcube_has_room(render_bar_w, active_vp_w);
|
&& !thumbnail_capture_clean
|
||||||
|
&& !tab.is_start
|
||||||
|
&& viewcube_has_room(render_bar_w, active_vp_w);
|
||||||
// Start tab: render welcome page in place of the viewport.
|
// Start tab: render welcome page in place of the viewport.
|
||||||
// Surrounding chrome (tab bar, status bar) stays; the welcome widget
|
// Surrounding chrome (tab bar, status bar) stays; the welcome widget
|
||||||
// returned here also flags the rest of `view` to skip drawing-only
|
// returned here also flags the rest of `view` to skip drawing-only
|
||||||
|
|
@ -258,6 +263,7 @@ impl OpenCADStudio {
|
||||||
shader(ViewportPane::model(
|
shader(ViewportPane::model(
|
||||||
&tab.scene,
|
&tab.scene,
|
||||||
viewcube_visible,
|
viewcube_visible,
|
||||||
|
!thumbnail_capture_clean,
|
||||||
viewport_render_mode,
|
viewport_render_mode,
|
||||||
viewcube_text_color,
|
viewcube_text_color,
|
||||||
))
|
))
|
||||||
|
|
@ -276,6 +282,7 @@ impl OpenCADStudio {
|
||||||
// mouse_areas' hover state and drops their move events).
|
// mouse_areas' hover state and drops their move events).
|
||||||
let scene = &tab.scene;
|
let scene = &tab.scene;
|
||||||
let show_viewcube = viewcube_visible;
|
let show_viewcube = viewcube_visible;
|
||||||
|
let show_interaction = !thumbnail_capture_clean;
|
||||||
let render_mode = viewport_render_mode;
|
let render_mode = viewport_render_mode;
|
||||||
let size_probe: Element<'_, Message> = responsive(move |size| {
|
let size_probe: Element<'_, Message> = responsive(move |size| {
|
||||||
{
|
{
|
||||||
|
|
@ -293,6 +300,7 @@ impl OpenCADStudio {
|
||||||
shader(ViewportPane::for_pane(
|
shader(ViewportPane::for_pane(
|
||||||
scene,
|
scene,
|
||||||
show_viewcube,
|
show_viewcube,
|
||||||
|
show_interaction,
|
||||||
render_mode,
|
render_mode,
|
||||||
idx,
|
idx,
|
||||||
viewcube_text_color,
|
viewcube_text_color,
|
||||||
|
|
@ -868,6 +876,20 @@ impl OpenCADStudio {
|
||||||
.height(Fill)]
|
.height(Fill)]
|
||||||
.width(Fill)
|
.width(Fill)
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
|
} else if thumbnail_capture_clean {
|
||||||
|
let capture_bg = if is_paper { PAPER_SPACE_BACKGROUND } else { bg_color };
|
||||||
|
stack![
|
||||||
|
container(Space::new())
|
||||||
|
.style(move |_: &Theme| container::Style {
|
||||||
|
background: Some(Background::Color(capture_bg)),
|
||||||
|
..Default::default()
|
||||||
|
})
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill),
|
||||||
|
viewport_3d,
|
||||||
|
]
|
||||||
|
.width(Fill)
|
||||||
|
.height(Fill)
|
||||||
} else if is_paper {
|
} else if is_paper {
|
||||||
// Keep the grid above the opaque GPU sheet and below interaction UI.
|
// Keep the grid above the opaque GPU sheet and below interaction UI.
|
||||||
stack![
|
stack![
|
||||||
|
|
@ -902,6 +924,7 @@ impl OpenCADStudio {
|
||||||
.height(Fill)
|
.height(Fill)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !thumbnail_capture_clean {
|
||||||
// Per-pane input pane_grid goes ABOVE the crosshair overlay so it
|
// Per-pane input pane_grid goes ABOVE the crosshair overlay so it
|
||||||
// receives mouse events (the overlay's `Hidden` cursor would otherwise
|
// receives mouse events (the overlay's `Hidden` cursor would otherwise
|
||||||
// starve any layer beneath it). The controls bar is pushed on top of it.
|
// starve any layer beneath it). The controls bar is pushed on top of it.
|
||||||
|
|
@ -1481,6 +1504,7 @@ impl OpenCADStudio {
|
||||||
viewport_stack = viewport_stack.push(text_inline_overlay(ed, canvas));
|
viewport_stack = viewport_stack.push(text_inline_overlay(ed, canvas));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Docked side panels (Properties, block palette, future palettes) live
|
// Docked side panels (Properties, block palette, future palettes) live
|
||||||
// in an ordered vertical stack on the left/right edge of the drawing
|
// in an ordered vertical stack on the left/right edge of the drawing
|
||||||
|
|
@ -1535,7 +1559,13 @@ impl OpenCADStudio {
|
||||||
if let Some(e) = left_edge {
|
if let Some(e) = left_edge {
|
||||||
parts.push(e);
|
parts.push(e);
|
||||||
}
|
}
|
||||||
parts.push(viewport_stack.into());
|
parts.push(
|
||||||
|
crate::ui::wrap_bar::PosReport::new(
|
||||||
|
VIEWPORT_CAPTURE_BOUNDS_ID,
|
||||||
|
viewport_stack,
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
);
|
||||||
if let Some(e) = right_edge {
|
if let Some(e) = right_edge {
|
||||||
parts.push(e);
|
parts.push(e);
|
||||||
}
|
}
|
||||||
|
|
@ -1712,7 +1742,9 @@ impl OpenCADStudio {
|
||||||
&self.history_content,
|
&self.history_content,
|
||||||
self.win_size.1,
|
self.win_size.1,
|
||||||
);
|
);
|
||||||
let center_stack: Element<'_, Message> = if tab.is_start {
|
let center_stack: Element<'_, Message> = if thumbnail_capture_clean {
|
||||||
|
workspace
|
||||||
|
} else if tab.is_start {
|
||||||
column![
|
column![
|
||||||
workspace,
|
workspace,
|
||||||
iced::widget::container(command_line)
|
iced::widget::container(command_line)
|
||||||
|
|
@ -2096,6 +2128,11 @@ impl OpenCADStudio {
|
||||||
} else {
|
} else {
|
||||||
Subscription::none()
|
Subscription::none()
|
||||||
};
|
};
|
||||||
|
let thumbnail_capture = if self.thumbnail_capture_clean {
|
||||||
|
window::frames().map(|_| Message::ThumbnailCaptureFrame)
|
||||||
|
} else {
|
||||||
|
Subscription::none()
|
||||||
|
};
|
||||||
// Blink the MText preview caret while the editor is open.
|
// Blink the MText preview caret while the editor is open.
|
||||||
let caret_blink = if self.mtext_editor.is_some() {
|
let caret_blink = if self.mtext_editor.is_some() {
|
||||||
iced::time::every(std::time::Duration::from_millis(530))
|
iced::time::every(std::time::Duration::from_millis(530))
|
||||||
|
|
@ -2154,6 +2191,7 @@ impl OpenCADStudio {
|
||||||
grip_dwell,
|
grip_dwell,
|
||||||
hover_dwell,
|
hover_dwell,
|
||||||
nav_settle,
|
nav_settle,
|
||||||
|
thumbnail_capture,
|
||||||
caret_blink,
|
caret_blink,
|
||||||
web_fonts,
|
web_fonts,
|
||||||
autosave,
|
autosave,
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ impl OpenCADStudio {
|
||||||
Some(K::LayerStateManager) => crate::tr!("modal", "layer-state-manager"),
|
Some(K::LayerStateManager) => crate::tr!("modal", "layer-state-manager"),
|
||||||
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
|
Some(K::LayerTranslator) => crate::t!("Layer Translator").into_owned(),
|
||||||
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
|
Some(K::DrawingUnits) => crate::t!("Drawing Units").into_owned(),
|
||||||
|
Some(K::GeometricTolerance) => crate::t!("Geometric Tolerance").into_owned(),
|
||||||
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
|
Some(K::DraftingSettings) => crate::t!("Drafting Settings").into_owned(),
|
||||||
Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"),
|
Some(K::LayerStateEditor) => crate::tr!("modal", "edit-layer-state"),
|
||||||
Some(K::Plot) => crate::tr!("modal", "plot"),
|
Some(K::Plot) => crate::tr!("modal", "plot"),
|
||||||
|
|
@ -238,6 +239,12 @@ impl OpenCADStudio {
|
||||||
crate::ui::window::drawing_units::view_window(state, flow)
|
crate::ui::window::drawing_units::view_window(state, flow)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
super::super::ModalKind::GeometricTolerance => {
|
||||||
|
let state = self.geometric_tolerance.as_ref()?;
|
||||||
|
sized_flow(ex, 670, 530, |flow| {
|
||||||
|
crate::ui::window::geometric_tolerance::view_window(state, flow)
|
||||||
|
})
|
||||||
|
}
|
||||||
super::super::ModalKind::LayerStateManager => {
|
super::super::ModalKind::LayerStateManager => {
|
||||||
let states = self.tabs[self.active_tab].scene.document.layer_states();
|
let states = self.tabs[self.active_tab].scene.document.layer_states();
|
||||||
sized_flow(
|
sized_flow(
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ use iced::advanced::renderer;
|
||||||
use iced::advanced::widget;
|
use iced::advanced::widget;
|
||||||
use iced::advanced::{Layout, Shell, Widget};
|
use iced::advanced::{Layout, Shell, Widget};
|
||||||
use iced::widget::{
|
use iced::widget::{
|
||||||
button, column, container, mouse_area, row, scrollable, stack, text, text_input, Space,
|
button, checkbox, column, container, mouse_area, row, scrollable, stack, text, text_input,
|
||||||
|
Space,
|
||||||
};
|
};
|
||||||
use iced::{Background, Border, Color, Element, Event, Fill, Length, Rectangle, Size, Theme, Vector};
|
use iced::{Background, Border, Color, Element, Event, Fill, Length, Rectangle, Size, Theme, Vector};
|
||||||
use crate::t;
|
use crate::t;
|
||||||
|
|
@ -374,7 +375,7 @@ pub(super) fn mtext_editor_overlay<'a>(
|
||||||
iced::widget::Space::new().width(Fill).height(Fill),
|
iced::widget::Space::new().width(Fill).height(Fill),
|
||||||
t!("Text Editor"),
|
t!("Text Editor"),
|
||||||
content,
|
content,
|
||||||
Message::MTextCancel,
|
Message::MTextOk,
|
||||||
modal_offset,
|
modal_offset,
|
||||||
crate::ui::modal::ModalOptions::STANDARD,
|
crate::ui::modal::ModalOptions::STANDARD,
|
||||||
)
|
)
|
||||||
|
|
@ -450,6 +451,9 @@ fn mtext_editor_content<'a>(
|
||||||
.on_select(Message::MTextStyle)
|
.on_select(Message::MTextStyle)
|
||||||
.text_size(11)
|
.text_size(11)
|
||||||
.width(iced::Length::Fixed(96.0));
|
.width(iced::Length::Fixed(96.0));
|
||||||
|
let annotative = checkbox(ed.annotative)
|
||||||
|
.on_toggle(Message::MTextAnnotative)
|
||||||
|
.size(14);
|
||||||
let font_sel = if ed.font.trim().is_empty() {
|
let font_sel = if ed.font.trim().is_empty() {
|
||||||
"[Style default]".to_string()
|
"[Style default]".to_string()
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -474,6 +478,7 @@ fn mtext_editor_content<'a>(
|
||||||
crate::ui::color_select::ColorExtras {
|
crate::ui::color_select::ColorExtras {
|
||||||
by_layer: true,
|
by_layer: true,
|
||||||
by_block: false,
|
by_block: false,
|
||||||
|
..Default::default()
|
||||||
},
|
},
|
||||||
Message::MTextColorChanged,
|
Message::MTextColorChanged,
|
||||||
Message::MTextColorPickerToggle,
|
Message::MTextColorPickerToggle,
|
||||||
|
|
@ -486,6 +491,9 @@ fn mtext_editor_content<'a>(
|
||||||
|
|
||||||
let row1 = row![
|
let row1 = row![
|
||||||
style_pl,
|
style_pl,
|
||||||
|
row![annotative, text(t!("Annotative")).size(11)]
|
||||||
|
.spacing(3)
|
||||||
|
.align_y(iced::Alignment::Center),
|
||||||
font_pl,
|
font_pl,
|
||||||
small_input("2.5", &ed.height, Message::MTextHeight, 64.0),
|
small_input("2.5", &ed.height, Message::MTextHeight, 64.0),
|
||||||
iced::widget::Space::new().width(6),
|
iced::widget::Space::new().width(6),
|
||||||
|
|
@ -534,6 +542,14 @@ fn mtext_editor_content<'a>(
|
||||||
.text_size(11)
|
.text_size(11)
|
||||||
.width(iced::Length::Fixed(112.0));
|
.width(iced::Length::Fixed(112.0));
|
||||||
let row2 = row![
|
let row2 = row![
|
||||||
|
button(lbl("↶"))
|
||||||
|
.on_press(Message::MTextUndo)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
button(lbl("↷"))
|
||||||
|
.on_press(Message::MTextRedo)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
lbl("O"),
|
lbl("O"),
|
||||||
small_input("0", &ed.oblique, Message::MTextOblique, 48.0),
|
small_input("0", &ed.oblique, Message::MTextOblique, 48.0),
|
||||||
lbl("W"),
|
lbl("W"),
|
||||||
|
|
@ -572,6 +588,144 @@ fn mtext_editor_content<'a>(
|
||||||
.on_press(Message::MTextLineSpacing(2.0))
|
.on_press(Message::MTextLineSpacing(2.0))
|
||||||
.padding(3)
|
.padding(3)
|
||||||
.style(btn_style),
|
.style(btn_style),
|
||||||
|
button(lbl("Stack"))
|
||||||
|
.on_press(Message::MTextStack)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
button(lbl("Clear"))
|
||||||
|
.on_press(Message::MTextClearFormatting)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
button(lbl("°"))
|
||||||
|
.on_press(Message::MTextInsert("°".to_string()))
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
button(lbl("±"))
|
||||||
|
.on_press(Message::MTextInsert("±".to_string()))
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
button(lbl("⌀"))
|
||||||
|
.on_press(Message::MTextInsert("⌀".to_string()))
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.align_y(iced::Alignment::Center)
|
||||||
|
.width(width);
|
||||||
|
|
||||||
|
let column_mode = match (ed.column_type, ed.column_auto_height) {
|
||||||
|
(1, _) => "Static",
|
||||||
|
(2, true) => "Dynamic auto",
|
||||||
|
(2, false) => "Dynamic manual",
|
||||||
|
_ => "No columns",
|
||||||
|
}
|
||||||
|
.to_string();
|
||||||
|
let column_picker = iced::widget::pick_list(
|
||||||
|
Some(column_mode),
|
||||||
|
["No columns", "Static", "Dynamic auto", "Dynamic manual"]
|
||||||
|
.into_iter()
|
||||||
|
.map(str::to_string)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
|value| value.to_string(),
|
||||||
|
)
|
||||||
|
.on_select(Message::MTextColumnMode)
|
||||||
|
.text_size(11)
|
||||||
|
.width(iced::Length::Fixed(120.0));
|
||||||
|
let reverse = checkbox(ed.column_flow_reversed)
|
||||||
|
.on_toggle(Message::MTextColumnFlowReversed)
|
||||||
|
.size(14);
|
||||||
|
let row3 = row![
|
||||||
|
lbl("Columns"),
|
||||||
|
column_picker,
|
||||||
|
lbl("Count"),
|
||||||
|
small_input("2", &ed.column_count, Message::MTextColumnCount, 42.0),
|
||||||
|
lbl("Height"),
|
||||||
|
text_input("0", &ed.rect_height)
|
||||||
|
.on_input(Message::MTextColumnHeight)
|
||||||
|
.width(iced::Length::Fixed(58.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
lbl("Width"),
|
||||||
|
small_input("0", &ed.column_width, Message::MTextColumnWidth, 58.0),
|
||||||
|
lbl("Gutter"),
|
||||||
|
small_input("0", &ed.column_gutter, Message::MTextColumnGutter, 58.0),
|
||||||
|
reverse,
|
||||||
|
lbl("Reverse"),
|
||||||
|
iced::widget::Space::new().width(8),
|
||||||
|
lbl("First"),
|
||||||
|
text_input("0", &ed.paragraph_first_indent)
|
||||||
|
.on_input(|value| Message::MTextParagraphNumber(
|
||||||
|
super::super::mtext_editor::ParaNumber::FirstIndent,
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.width(iced::Length::Fixed(48.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
lbl("Left"),
|
||||||
|
text_input("0", &ed.paragraph_left_indent)
|
||||||
|
.on_input(|value| Message::MTextParagraphNumber(
|
||||||
|
super::super::mtext_editor::ParaNumber::LeftIndent,
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.width(iced::Length::Fixed(48.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
lbl("Right"),
|
||||||
|
text_input("0", &ed.paragraph_right_indent)
|
||||||
|
.on_input(|value| Message::MTextParagraphNumber(
|
||||||
|
super::super::mtext_editor::ParaNumber::RightIndent,
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.width(iced::Length::Fixed(48.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
lbl("Before"),
|
||||||
|
text_input("0", &ed.paragraph_space_before)
|
||||||
|
.on_input(|value| Message::MTextParagraphNumber(
|
||||||
|
super::super::mtext_editor::ParaNumber::SpaceBefore,
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.width(iced::Length::Fixed(48.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
lbl("After"),
|
||||||
|
text_input("0", &ed.paragraph_space_after)
|
||||||
|
.on_input(|value| Message::MTextParagraphNumber(
|
||||||
|
super::super::mtext_editor::ParaNumber::SpaceAfter,
|
||||||
|
value,
|
||||||
|
))
|
||||||
|
.width(iced::Length::Fixed(48.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
]
|
||||||
|
.spacing(4)
|
||||||
|
.align_y(iced::Alignment::Center)
|
||||||
|
.width(width);
|
||||||
|
let row4 = row![
|
||||||
|
lbl("Find"),
|
||||||
|
text_input("Find", &ed.find_text)
|
||||||
|
.on_input(Message::MTextFindText)
|
||||||
|
.width(iced::Length::Fixed(140.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
button(lbl("Next"))
|
||||||
|
.on_press(Message::MTextFindNext)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
lbl("Replace"),
|
||||||
|
text_input("Replace", &ed.replace_text)
|
||||||
|
.on_input(Message::MTextReplaceText)
|
||||||
|
.width(iced::Length::Fixed(140.0))
|
||||||
|
.padding(3)
|
||||||
|
.size(12),
|
||||||
|
button(lbl("Replace"))
|
||||||
|
.on_press(Message::MTextReplaceNext)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
|
button(lbl("Replace All"))
|
||||||
|
.on_press(Message::MTextReplaceAll)
|
||||||
|
.padding(3)
|
||||||
|
.style(btn_style),
|
||||||
]
|
]
|
||||||
.spacing(4)
|
.spacing(4)
|
||||||
.align_y(iced::Alignment::Center)
|
.align_y(iced::Alignment::Center)
|
||||||
|
|
@ -709,13 +863,13 @@ fn mtext_editor_content<'a>(
|
||||||
.into()
|
.into()
|
||||||
};
|
};
|
||||||
|
|
||||||
// ── Top action bar: Apply on the right, exactly like the style managers'
|
// ── Top action bar: Apply keeps editing; Close saves and exits. Escape
|
||||||
// toolbar strip. Closing (the modal ✕) without applying discards the
|
// remains the explicit discard path.
|
||||||
// buffer, so there is no separate Cancel.
|
|
||||||
let action_bar = container(
|
let action_bar = container(
|
||||||
row![
|
row![
|
||||||
iced::widget::Space::new().width(width),
|
iced::widget::Space::new().width(width),
|
||||||
crate::ui::style::style_manager::tb_button(t!("Apply"), Message::MTextApply, true),
|
crate::ui::style::style_manager::tb_button(t!("Apply"), Message::MTextApply, true),
|
||||||
|
crate::ui::style::style_manager::tb_button(t!("Close Text Editor"), Message::MTextOk, true),
|
||||||
]
|
]
|
||||||
.align_y(iced::Alignment::Center),
|
.align_y(iced::Alignment::Center),
|
||||||
)
|
)
|
||||||
|
|
@ -729,7 +883,7 @@ fn mtext_editor_content<'a>(
|
||||||
.padding([5, 8]);
|
.padding([5, 8]);
|
||||||
|
|
||||||
container(
|
container(
|
||||||
column![action_bar, row1, row2, width_slider, body]
|
column![action_bar, row1, row2, row3, row4, width_slider, body]
|
||||||
.spacing(6)
|
.spacing(6)
|
||||||
.width(width)
|
.width(width)
|
||||||
.height(height),
|
.height(height),
|
||||||
|
|
|
||||||
180
src/command.rs
|
|
@ -169,6 +169,38 @@ pub struct SelectionEntity {
|
||||||
pub surface_area: Option<f64>,
|
pub surface_area: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Association source with an optional sub-entity marker.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct DimensionAssociationSource {
|
||||||
|
pub handle: Handle,
|
||||||
|
pub marker: Option<i32>,
|
||||||
|
pub parameter: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DimensionAssociationSource {
|
||||||
|
pub const fn inferred(handle: Handle) -> Self {
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
marker: None,
|
||||||
|
parameter: 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn explicit(handle: Handle, marker: i32, parameter: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
handle,
|
||||||
|
marker: Some(marker),
|
||||||
|
parameter,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum DimensionAssociationInput {
|
||||||
|
Infer(Option<Handle>),
|
||||||
|
Explicit(Vec<Option<DimensionAssociationSource>>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum AreaPreviewSource {
|
pub enum AreaPreviewSource {
|
||||||
Handles(Vec<Handle>),
|
Handles(Vec<Handle>),
|
||||||
|
|
@ -542,6 +574,34 @@ impl KeywordCommand {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn match_cmd_option<'a>(
|
||||||
|
options: &'a [(&'static str, &'static str, Option<&'static str>)],
|
||||||
|
text: &str,
|
||||||
|
) -> Option<&'a (&'static str, &'static str, Option<&'static str>)> {
|
||||||
|
let t = text.trim();
|
||||||
|
let up = t.to_uppercase();
|
||||||
|
if up.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// 1. Exact match on keyword or label (case-insensitive)
|
||||||
|
if let Some(opt) = options.iter().find(|(label, k, _)| {
|
||||||
|
k.eq_ignore_ascii_case(&up) || label.eq_ignore_ascii_case(t)
|
||||||
|
}) {
|
||||||
|
return Some(opt);
|
||||||
|
}
|
||||||
|
// 2. Unambiguous prefix match on keyword or label (e.g. "A" -> "ABOVE", "L" -> "LEFT")
|
||||||
|
let matches: Vec<_> = options
|
||||||
|
.iter()
|
||||||
|
.filter(|(label, k, _)| {
|
||||||
|
k.to_uppercase().starts_with(&up) || label.to_uppercase().starts_with(&up)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if matches.len() == 1 {
|
||||||
|
return Some(matches[0]);
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
impl CadCommand for KeywordCommand {
|
impl CadCommand for KeywordCommand {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
self.name
|
self.name
|
||||||
|
|
@ -581,10 +641,7 @@ impl CadCommand for KeywordCommand {
|
||||||
// Consumed inputs that keep prompting return `Some(NeedPoint)` —
|
// Consumed inputs that keep prompting return `Some(NeedPoint)` —
|
||||||
// `None` would hand the same text to the command a second time.
|
// `None` would hand the same text to the command a second time.
|
||||||
None => {
|
None => {
|
||||||
let up = t.to_uppercase();
|
let Some((_, keyword, value_prompt)) = match_cmd_option(&self.options, t)
|
||||||
let Some((_, keyword, value_prompt)) = self.options.iter().find(|(label, k, _)| {
|
|
||||||
k.eq_ignore_ascii_case(&up) || label.eq_ignore_ascii_case(t)
|
|
||||||
})
|
|
||||||
else {
|
else {
|
||||||
// Unknown verb — keep prompting rather than dispatch garbage.
|
// Unknown verb — keep prompting rather than dispatch garbage.
|
||||||
return Some(CmdResult::NeedPoint);
|
return Some(CmdResult::NeedPoint);
|
||||||
|
|
@ -783,11 +840,18 @@ impl CadCommand for SelectThenKeywordCommand {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
match self.pending {
|
match self.pending {
|
||||||
Some((keyword, _)) => Some(CmdResult::Dispatch(format!("{} {keyword} {t}", self.name))),
|
Some((keyword, _)) => {
|
||||||
|
if self.selected.is_empty() {
|
||||||
|
Some(CmdResult::Dispatch(format!("{} {keyword} {t}", self.name)))
|
||||||
|
} else {
|
||||||
|
Some(CmdResult::Relaunch(
|
||||||
|
format!("{} {keyword} {t}", self.name),
|
||||||
|
std::mem::take(&mut self.selected),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
None => {
|
None => {
|
||||||
let up = t.to_uppercase();
|
let Some((_, keyword, value_prompt)) = match_cmd_option(&self.options, t)
|
||||||
let Some((_, keyword, value_prompt)) =
|
|
||||||
self.options.iter().find(|(_, k, _)| k.eq_ignore_ascii_case(&up))
|
|
||||||
else {
|
else {
|
||||||
// Unknown verb — consumed, keep prompting (`None` would
|
// Unknown verb — consumed, keep prompting (`None` would
|
||||||
// feed the same text to the command a second time).
|
// feed the same text to the command a second time).
|
||||||
|
|
@ -798,7 +862,16 @@ impl CadCommand for SelectThenKeywordCommand {
|
||||||
self.pending = Some((keyword, vp));
|
self.pending = Some((keyword, vp));
|
||||||
Some(CmdResult::NeedPoint)
|
Some(CmdResult::NeedPoint)
|
||||||
}
|
}
|
||||||
None => Some(CmdResult::Dispatch(format!("{} {keyword}", self.name))),
|
None => {
|
||||||
|
if self.selected.is_empty() {
|
||||||
|
Some(CmdResult::Dispatch(format!("{} {keyword}", self.name)))
|
||||||
|
} else {
|
||||||
|
Some(CmdResult::Relaunch(
|
||||||
|
format!("{} {keyword}", self.name),
|
||||||
|
std::mem::take(&mut self.selected),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1171,6 +1244,11 @@ pub enum CmdResult {
|
||||||
CommitEntitiesAndExit(Vec<EntityType>),
|
CommitEntitiesAndExit(Vec<EntityType>),
|
||||||
/// Commit an acadrust entity to the document and end the command.
|
/// Commit an acadrust entity to the document and end the command.
|
||||||
CommitAndExit(EntityType),
|
CommitAndExit(EntityType),
|
||||||
|
/// Commit a dimension using the drawing's association mode.
|
||||||
|
CommitDimension {
|
||||||
|
entity: EntityType,
|
||||||
|
association: DimensionAssociationInput,
|
||||||
|
},
|
||||||
/// Commit a Model-tab 3D solid: the acadrust entity (for selection /
|
/// Commit a Model-tab 3D solid: the acadrust entity (for selection /
|
||||||
/// persistence) plus its B-rep (cached for boolean ops + shaded
|
/// persistence) plus its B-rep (cached for boolean ops + shaded
|
||||||
/// rendering). Ends the command.
|
/// rendering). Ends the command.
|
||||||
|
|
@ -1349,6 +1427,17 @@ pub enum CmdResult {
|
||||||
handle: Option<Handle>,
|
handle: Option<Handle>,
|
||||||
initial: String,
|
initial: String,
|
||||||
height: f64,
|
height: f64,
|
||||||
|
/// Optional entity defaults collected by the interactive MTEXT
|
||||||
|
/// command (boundary, rotation, attachment, spacing and columns).
|
||||||
|
/// Existing-entity edits leave this as `None` and load the document
|
||||||
|
/// entity instead.
|
||||||
|
template: Option<Box<acadrust::MText>>,
|
||||||
|
},
|
||||||
|
/// Collect rich text without creating an MText entity.
|
||||||
|
SuspendForMTextInput {
|
||||||
|
pos: DVec3,
|
||||||
|
initial: String,
|
||||||
|
height: f64,
|
||||||
},
|
},
|
||||||
/// Open the in-place single-line TEXT editor (a plain text-entry box, no
|
/// Open the in-place single-line TEXT editor (a plain text-entry box, no
|
||||||
/// formatting toolbar). `handle` is `Some` when editing an existing Text,
|
/// formatting toolbar). `handle` is `Some` when editing an existing Text,
|
||||||
|
|
@ -1359,6 +1448,14 @@ pub enum CmdResult {
|
||||||
initial: String,
|
initial: String,
|
||||||
height: f64,
|
height: f64,
|
||||||
},
|
},
|
||||||
|
/// Suspend the active TEXT command while the in-place editor collects one
|
||||||
|
/// independent line. The prepared entity carries the chosen style,
|
||||||
|
/// justification, rotation and two-point geometry. When the editor closes,
|
||||||
|
/// the command resumes so another line can be placed directly below it.
|
||||||
|
SuspendForTextInput {
|
||||||
|
pos: DVec3,
|
||||||
|
entity: acadrust::entities::Text,
|
||||||
|
},
|
||||||
/// Apply new pattern/scale/angle to an existing hatch entity.
|
/// Apply new pattern/scale/angle to an existing hatch entity.
|
||||||
HatcheditApply {
|
HatcheditApply {
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
|
|
@ -1384,17 +1481,20 @@ pub enum CmdResult {
|
||||||
/// Translation vector applied once to every selected point.
|
/// Translation vector applied once to every selected point.
|
||||||
delta: DVec3,
|
delta: DVec3,
|
||||||
},
|
},
|
||||||
/// Create a Solid3D placeholder entity + associated MeshModel.
|
/// Extrude the profile entity `handle` along its plane normal.
|
||||||
/// `mesh_fn` is called with the entity's handle string to build the mesh.
|
|
||||||
CommitSolid3D {
|
|
||||||
mesh_fn: Box<dyn FnOnce(String) -> Option<crate::scene::model::mesh_model::MeshModel> + Send>,
|
|
||||||
},
|
|
||||||
/// Extrude the profile entity `handle` by `height` along Z.
|
|
||||||
ExtrudeEntity {
|
ExtrudeEntity {
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
height: f64,
|
height: f64,
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
},
|
},
|
||||||
|
/// Pull a closed profile or a planar solid face by a signed distance.
|
||||||
|
PresspullEntity {
|
||||||
|
handle: Handle,
|
||||||
|
pick: DVec3,
|
||||||
|
distance: f64,
|
||||||
|
drag: Option<DVec3>,
|
||||||
|
color: [f32; 4],
|
||||||
|
},
|
||||||
/// Revolve the profile entity `handle` around the given axis by `angle_deg`.
|
/// Revolve the profile entity `handle` around the given axis by `angle_deg`.
|
||||||
RevolveEntity {
|
RevolveEntity {
|
||||||
handle: Handle,
|
handle: Handle,
|
||||||
|
|
@ -1414,6 +1514,17 @@ pub enum CmdResult {
|
||||||
handles: Vec<Handle>,
|
handles: Vec<Handle>,
|
||||||
color: [f32; 4],
|
color: [f32; 4],
|
||||||
},
|
},
|
||||||
|
/// Round or bevel the straight edge nearest `pick` on a solid.
|
||||||
|
SolidEdgeBlend {
|
||||||
|
handle: Handle,
|
||||||
|
pick: DVec3,
|
||||||
|
value: f64,
|
||||||
|
fillet: bool,
|
||||||
|
},
|
||||||
|
SolidSubtract {
|
||||||
|
bases: Vec<Handle>,
|
||||||
|
cutters: Vec<Handle>,
|
||||||
|
},
|
||||||
/// INSERT landed on a block that has AttributeDefinitions.
|
/// INSERT landed on a block that has AttributeDefinitions.
|
||||||
/// The host should look up the attdefs for `block_name` from the document
|
/// The host should look up the attdefs for `block_name` from the document
|
||||||
/// and call `attreq_set_attdefs()` on the command, then loop on text input.
|
/// and call `attreq_set_attdefs()` on the command, then loop on text input.
|
||||||
|
|
@ -1655,6 +1766,16 @@ pub trait CadCommand: Send {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Live search: called on each keystroke in the command line while the
|
||||||
|
/// command is active. Return true if the input updated internal filter
|
||||||
|
/// and the UI should refresh (prompt/options). Used for INSERT/MINSERT
|
||||||
|
/// incremental block name search without requiring Enter. Performance
|
||||||
|
/// critical — implementations must use precomputed caches and partial
|
||||||
|
/// sorting.
|
||||||
|
fn on_live_input(&mut self, _input: &str) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Push the active coordinate frame in full precision. Geometry commands
|
/// Push the active coordinate frame in full precision. Geometry commands
|
||||||
/// use it for plane-local construction; inquiry and modify commands use it
|
/// use it for plane-local construction; inquiry and modify commands use it
|
||||||
/// for local deltas, angles and transformation axes.
|
/// for local deltas, angles and transformation axes.
|
||||||
|
|
@ -1717,6 +1838,11 @@ pub trait CadCommand: Send {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Accept typed coordinates while object picking.
|
||||||
|
fn entity_pick_accepts_points(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
/// Include filled hatch / DXF SOLID regions in the entity hit-test.
|
/// Include filled hatch / DXF SOLID regions in the entity hit-test.
|
||||||
///
|
///
|
||||||
/// Most entity-pick commands operate on curve geometry and intentionally
|
/// Most entity-pick commands operate on curve geometry and intentionally
|
||||||
|
|
@ -1726,6 +1852,14 @@ pub trait CadCommand: Send {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Supply the mesh surface hit instead of the working-plane projection.
|
||||||
|
fn entity_pick_uses_surface_point(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Supply the picked surface or profile direction when available.
|
||||||
|
fn set_entity_pick_direction(&mut self, _direction: Option<DVec3>) {}
|
||||||
|
|
||||||
/// Render the entity under the cursor through the normal rollover
|
/// Render the entity under the cursor through the normal rollover
|
||||||
/// highlight while this command is waiting for an entity pick.
|
/// highlight while this command is waiting for an entity pick.
|
||||||
fn entity_pick_highlights_hover(&self) -> bool {
|
fn entity_pick_highlights_hover(&self) -> bool {
|
||||||
|
|
@ -1737,6 +1871,11 @@ pub trait CadCommand: Send {
|
||||||
CmdResult::Cancel
|
CmdResult::Cancel
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resume the command with collected rich text.
|
||||||
|
fn on_editor_text(&mut self, _value: String) {}
|
||||||
|
|
||||||
|
fn on_editor_display_height(&mut self, _height: f64) {}
|
||||||
|
|
||||||
/// Called when the user clicks and `needs_entity_pick()` is true.
|
/// Called when the user clicks and `needs_entity_pick()` is true.
|
||||||
/// `handle` is the nearest wire's entity handle (Handle::NULL if nothing found).
|
/// `handle` is the nearest wire's entity handle (Handle::NULL if nothing found).
|
||||||
fn on_entity_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult {
|
fn on_entity_pick(&mut self, _handle: Handle, _pt: DVec3) -> CmdResult {
|
||||||
|
|
@ -1945,10 +2084,13 @@ pub trait CadCommand: Send {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inject the block's attribute definitions for ATTREQ attr-filling after
|
/// Inject block attribute definitions after the INSERT point is picked.
|
||||||
/// the INSERT point is picked. Carries the full definitions so the created
|
fn attreq_set_attdefs(
|
||||||
/// attributes inherit their geometry, not just tag / prompt / default (#255).
|
&mut self,
|
||||||
fn attreq_set_attdefs(&mut self, _attdefs: Vec<acadrust::entities::AttributeDefinition>) {}
|
_attdefs: Vec<acadrust::entities::AttributeDefinition>,
|
||||||
|
) -> Option<acadrust::EntityType> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the INSERT entity built so far (pending attr fill) if this is an
|
/// Returns the INSERT entity built so far (pending attr fill) if this is an
|
||||||
/// ATTREQ-aware INSERT command waiting for attdef injection.
|
/// ATTREQ-aware INSERT command waiting for attdef injection.
|
||||||
|
|
|
||||||
|
|
@ -200,6 +200,7 @@ fn build_attr_render(input: AttrTextInputs<'_>, document: &acadrust::CadDocument
|
||||||
oblique_angle,
|
oblique_angle,
|
||||||
is_backward: width_factor < 0.0,
|
is_backward: width_factor < 0.0,
|
||||||
is_upside_down: false,
|
is_upside_down: false,
|
||||||
|
is_vertical: false,
|
||||||
};
|
};
|
||||||
let layout = layout_mtext(&MTextRenderOpts {
|
let layout = layout_mtext(&MTextRenderOpts {
|
||||||
// Not an MTEXT: text in a fixed box, never columnar.
|
// Not an MTEXT: text in a fixed box, never columnar.
|
||||||
|
|
@ -213,6 +214,8 @@ fn build_attr_render(input: AttrTextInputs<'_>, document: &acadrust::CadDocument
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: 1.0,
|
line_spacing_factor: 1.0,
|
||||||
|
exact_line_spacing: false,
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
@ -304,6 +307,7 @@ fn build_attr_render(input: AttrTextInputs<'_>, document: &acadrust::CadDocument
|
||||||
origin,
|
origin,
|
||||||
color: None,
|
color: None,
|
||||||
fill_tris,
|
fill_tris,
|
||||||
|
plane: None,
|
||||||
// Carry a GlyphRun so the attribute renders as SDF glyph quads
|
// Carry a GlyphRun so the attribute renders as SDF glyph quads
|
||||||
// (same as TEXT); the strokes above are the historical fallback and
|
// (same as TEXT); the strokes above are the historical fallback and
|
||||||
// are suppressed by the per-group SDF path.
|
// are suppressed by the per-group SDF path.
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use crate::scene::model::object::{GripDef, GripShape, PropValue, Property};
|
use crate::scene::model::object::{GripDef, GripShape, PropValue, Property};
|
||||||
|
use crate::scene::model::wire_model::PatternStationPiece;
|
||||||
|
|
||||||
/// Linear / angular unit format pulled from the document header so the
|
/// Linear / angular unit format pulled from the document header so the
|
||||||
/// per-thread properties pipeline can format values consistently without
|
/// per-thread properties pipeline can format values consistently without
|
||||||
|
|
@ -573,6 +575,33 @@ pub fn edit_scalar_prop(label: &str, field: &'static str, value: f64) -> Propert
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Standard lineweight choices shared by entity-specific Properties rows.
|
||||||
|
pub fn lineweight_options() -> Vec<String> {
|
||||||
|
let mut options = vec![
|
||||||
|
"ByLayer".to_string(),
|
||||||
|
"ByBlock".to_string(),
|
||||||
|
"Default".to_string(),
|
||||||
|
];
|
||||||
|
for value in [
|
||||||
|
0, 5, 9, 13, 15, 18, 20, 25, 30, 35, 40, 50, 53, 60, 70, 80, 90, 100, 106, 120,
|
||||||
|
140, 158, 200, 211,
|
||||||
|
] {
|
||||||
|
options.push(format!("{:.2} mm", value as f64 / 100.0));
|
||||||
|
}
|
||||||
|
options
|
||||||
|
}
|
||||||
|
|
||||||
|
/// DIMLWD/DIMLWE lineweight value displayed by a Properties dropdown.
|
||||||
|
pub fn lineweight_label(value: i16) -> String {
|
||||||
|
match value {
|
||||||
|
-1 => "ByLayer".to_string(),
|
||||||
|
-2 => "ByBlock".to_string(),
|
||||||
|
-3 => "Default".to_string(),
|
||||||
|
value if value >= 0 => format!("{:.2} mm", value as f64 / 100.0),
|
||||||
|
_ => "Default".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn ro_prop(label: &str, field: &'static str, value: impl Into<String>) -> Property {
|
pub fn ro_prop(label: &str, field: &'static str, value: impl Into<String>) -> Property {
|
||||||
Property {
|
Property {
|
||||||
label: label.into(),
|
label: label.into(),
|
||||||
|
|
@ -789,6 +818,158 @@ pub(crate) fn tapered_band_points(
|
||||||
(pts, widths)
|
(pts, widths)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WideBandOutline {
|
||||||
|
pub points: Vec<[f64; 3]>,
|
||||||
|
pub stations: Vec<f32>,
|
||||||
|
pub point_segments: Vec<i32>,
|
||||||
|
pub station_pieces: Vec<PatternStationPiece>,
|
||||||
|
pub source_length: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the boundary of a variable-width band through the kernel.
|
||||||
|
pub(crate) fn wide_band_outline(
|
||||||
|
verts: &[([f64; 2], f64, f64, f64)],
|
||||||
|
is_closed: bool,
|
||||||
|
restart_per_segment: bool,
|
||||||
|
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||||
|
) -> WideBandOutline {
|
||||||
|
let source = cadkernel::geom2d::Polyline {
|
||||||
|
vertices: verts
|
||||||
|
.iter()
|
||||||
|
.map(|(position, bulge, _, _)| cadkernel::geom2d::PolylineVertex {
|
||||||
|
position: *position,
|
||||||
|
bulge: *bulge,
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
closed: is_closed,
|
||||||
|
};
|
||||||
|
let segment_count = if is_closed {
|
||||||
|
verts.len()
|
||||||
|
} else {
|
||||||
|
verts.len().saturating_sub(1)
|
||||||
|
};
|
||||||
|
let widths: Vec<[f64; 2]> = verts
|
||||||
|
.iter()
|
||||||
|
.take(segment_count)
|
||||||
|
.map(|(_, _, start, end)| [*start, *end])
|
||||||
|
.collect();
|
||||||
|
let boundary = cadkernel::geom2d::polyline_band_boundary(
|
||||||
|
&source,
|
||||||
|
&widths,
|
||||||
|
cadkernel::tessellation::DEFAULT_ANGLE,
|
||||||
|
);
|
||||||
|
let mut points = Vec::new();
|
||||||
|
let mut stations = Vec::new();
|
||||||
|
let mut point_segments = Vec::new();
|
||||||
|
for edge in boundary.edges {
|
||||||
|
if !points.is_empty() {
|
||||||
|
points.push([f64::NAN; 3]);
|
||||||
|
stations.push(0.0);
|
||||||
|
point_segments.push(-1);
|
||||||
|
}
|
||||||
|
points.extend(edge.points.into_iter().map(|point| {
|
||||||
|
let (x, y, z) = to_wcs(point[0], point[1]);
|
||||||
|
[x, y, z]
|
||||||
|
}));
|
||||||
|
stations.extend(
|
||||||
|
if restart_per_segment {
|
||||||
|
edge.segment_distances
|
||||||
|
} else {
|
||||||
|
edge.source_distances
|
||||||
|
}
|
||||||
|
.map(|distance| distance as f32),
|
||||||
|
);
|
||||||
|
point_segments.extend([edge.source_segment as i32; 2]);
|
||||||
|
}
|
||||||
|
let station_pieces = boundary
|
||||||
|
.station_pieces
|
||||||
|
.into_iter()
|
||||||
|
.map(|piece| {
|
||||||
|
let start = to_wcs(piece.points[0][0], piece.points[0][1]);
|
||||||
|
let end = to_wcs(piece.points[1][0], piece.points[1][1]);
|
||||||
|
PatternStationPiece {
|
||||||
|
source_segment: piece.source_segment as u32,
|
||||||
|
source_distances: piece.source_distances.map(|distance| distance as f32),
|
||||||
|
segment_distances: piece.segment_distances.map(|distance| distance as f32),
|
||||||
|
vector: [
|
||||||
|
(end.0 - start.0) as f32,
|
||||||
|
(end.1 - start.1) as f32,
|
||||||
|
(end.2 - start.2) as f32,
|
||||||
|
],
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
WideBandOutline {
|
||||||
|
points,
|
||||||
|
stations,
|
||||||
|
point_segments,
|
||||||
|
station_pieces,
|
||||||
|
source_length: boundary.source_length as f32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn extrude_wide_band_outline(
|
||||||
|
outline: WideBandOutline,
|
||||||
|
extrusion: [f64; 3],
|
||||||
|
) -> WideBandOutline {
|
||||||
|
let base_points = outline.points;
|
||||||
|
let base_stations = outline.stations;
|
||||||
|
let base_segments = outline.point_segments;
|
||||||
|
let mut points = base_points.clone();
|
||||||
|
let mut stations = base_stations.clone();
|
||||||
|
let mut point_segments = base_segments.clone();
|
||||||
|
points.push([f64::NAN; 3]);
|
||||||
|
stations.push(0.0);
|
||||||
|
point_segments.push(-1);
|
||||||
|
points.extend(base_points.iter().map(|point| {
|
||||||
|
if point[0].is_finite() {
|
||||||
|
[
|
||||||
|
point[0] + extrusion[0],
|
||||||
|
point[1] + extrusion[1],
|
||||||
|
point[2] + extrusion[2],
|
||||||
|
]
|
||||||
|
} else {
|
||||||
|
*point
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
stations.extend_from_slice(&base_stations);
|
||||||
|
point_segments.extend_from_slice(&base_segments);
|
||||||
|
|
||||||
|
let mut emitted = HashSet::new();
|
||||||
|
for ((&point, &station), &segment) in base_points
|
||||||
|
.iter()
|
||||||
|
.zip(&base_stations)
|
||||||
|
.zip(&base_segments)
|
||||||
|
{
|
||||||
|
if !point[0].is_finite() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let key = (point.map(f64::to_bits), station.to_bits(), segment);
|
||||||
|
if !emitted.insert(key) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
points.push([f64::NAN; 3]);
|
||||||
|
stations.push(0.0);
|
||||||
|
point_segments.push(-1);
|
||||||
|
points.push(point);
|
||||||
|
points.push([
|
||||||
|
point[0] + extrusion[0],
|
||||||
|
point[1] + extrusion[1],
|
||||||
|
point[2] + extrusion[2],
|
||||||
|
]);
|
||||||
|
stations.push(station);
|
||||||
|
stations.push(station);
|
||||||
|
point_segments.extend([segment; 2]);
|
||||||
|
}
|
||||||
|
WideBandOutline {
|
||||||
|
points,
|
||||||
|
stations,
|
||||||
|
point_segments,
|
||||||
|
station_pieces: outline.station_pieces,
|
||||||
|
source_length: outline.source_length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compute the filled boundary polygon for one polyline segment.
|
/// Compute the filled boundary polygon for one polyline segment.
|
||||||
/// For straight segments: a rectangle/trapezoid.
|
/// For straight segments: a rectangle/trapezoid.
|
||||||
/// For arc segments: an arc band (outer arc + reversed inner arc).
|
/// For arc segments: an arc band (outer arc + reversed inner arc).
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
|
|
||||||
use acadrust::types::Color;
|
use acadrust::types::Color;
|
||||||
use acadrust::xdata::{ExtendedData, XDataValue};
|
use acadrust::xdata::{ExtendedData, XDataValue};
|
||||||
use acadrust::{CadDocument, Handle};
|
use acadrust::{CadDocument, EntityType, Handle};
|
||||||
|
|
||||||
// DXF group codes of the dimension variables surfaced on the leader panel.
|
// DXF group codes of the dimension variables surfaced on the leader panel.
|
||||||
pub const DIMSCALE: i16 = 40; // overall scale (real)
|
pub const DIMSCALE: i16 = 40; // overall scale (real)
|
||||||
|
|
@ -77,7 +77,12 @@ pub const DIMATFIT: i16 = 289;
|
||||||
pub const DIMFXLON: i16 = 290;
|
pub const DIMFXLON: i16 = 290;
|
||||||
pub const DIMTFILL: i16 = 69;
|
pub const DIMTFILL: i16 = 69;
|
||||||
pub const DIMTFILLCLR: i16 = 70;
|
pub const DIMTFILLCLR: i16 = 70;
|
||||||
pub const DIMTXTDIRECTION: i16 = 295;
|
pub const DIMTXTDIRECTION: i16 = 294;
|
||||||
|
pub const DIMALTMZF: i16 = 295;
|
||||||
|
pub const DIMALTMZS: i16 = 296;
|
||||||
|
pub const DIMMZF: i16 = 297;
|
||||||
|
pub const DIMMZS: i16 = 298;
|
||||||
|
pub const DIMTALN: i16 = 392;
|
||||||
pub const DIMTXSTY: i16 = 340;
|
pub const DIMTXSTY: i16 = 340;
|
||||||
pub const DIMBLK: i16 = 342;
|
pub const DIMBLK: i16 = 342;
|
||||||
pub const DIMBLK1: i16 = 343;
|
pub const DIMBLK1: i16 = 343;
|
||||||
|
|
@ -229,3 +234,526 @@ pub fn set(doc: &mut CadDocument, handle: Handle, code: i16, value: Option<XData
|
||||||
}
|
}
|
||||||
write_pairs(doc, handle, kept);
|
write_pairs(doc, handle, kept);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn property_real_code(field: &str) -> Option<i16> {
|
||||||
|
Some(match field {
|
||||||
|
"dim_arrow_size" => DIMASZ,
|
||||||
|
"dim_line_ext" => DIMDLE,
|
||||||
|
"dim_ext_line_ext" => DIMEXE,
|
||||||
|
"dim_ext_line_offset" => DIMEXO,
|
||||||
|
"dim_ext_line_fixed_length" => DIMFXL,
|
||||||
|
"dim_text_height" => DIMTXT,
|
||||||
|
"dim_text_offset" => DIMGAP,
|
||||||
|
"dim_scale_overall" => DIMSCALE,
|
||||||
|
"dim_roundoff" => DIMRND,
|
||||||
|
"dim_scale_linear" => DIMLFAC,
|
||||||
|
"dim_alt_scale_factor" => DIMALTF,
|
||||||
|
"dim_alt_sub_units_scale" => DIMALTMZF,
|
||||||
|
"dim_alt_roundoff" => DIMALTRND,
|
||||||
|
"dim_sub_units_scale" => DIMMZF,
|
||||||
|
"dim_tolerance_limit_lower" => DIMTM,
|
||||||
|
"dim_tolerance_limit_upper" => DIMTP,
|
||||||
|
"dim_tolerance_text_height" => DIMTFAC,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn property_int_code(field: &str) -> Option<i16> {
|
||||||
|
Some(match field {
|
||||||
|
"dim_line_lineweight" => DIMLWD,
|
||||||
|
"dim_ext_line_lineweight" => DIMLWE,
|
||||||
|
"dim_ext_line_fixed" => DIMFXLON,
|
||||||
|
"dim_text_pos_vert" => DIMTAD,
|
||||||
|
"dim_text_pos_hor" => DIMJUST,
|
||||||
|
"dim_text_outside_align" => DIMTOH,
|
||||||
|
"dim_text_inside_align" => DIMTIH,
|
||||||
|
"dim_fit" => DIMATFIT,
|
||||||
|
"dim_text_inside" => DIMTIX,
|
||||||
|
"dim_text_movement" => DIMTMOVE,
|
||||||
|
"dim_line_forced" => DIMTOFL,
|
||||||
|
"dim_line_inside" => DIMSOXD,
|
||||||
|
"dim_units" => DIMLUNIT,
|
||||||
|
"dim_precision" => DIMDEC,
|
||||||
|
"dim_angle_units" => DIMAUNIT,
|
||||||
|
"dim_angle_precision" => DIMADEC,
|
||||||
|
"dim_decimal_separator" => DIMDSEP,
|
||||||
|
"dim_fractional_type" => DIMFRAC,
|
||||||
|
"dim_text_view_direction" => DIMTXTDIRECTION,
|
||||||
|
"dim_alt_enabled" => DIMALT,
|
||||||
|
"dim_alt_format" => DIMALTU,
|
||||||
|
"dim_alt_precision" => DIMALTD,
|
||||||
|
"dim_alt_tolerance_precision" => DIMALTTD,
|
||||||
|
"dim_tolerance_precision" => DIMTDEC,
|
||||||
|
"dim_tolerance_pos_vert" => DIMTOLJ,
|
||||||
|
"dim_tolerance_alignment" => DIMTALN,
|
||||||
|
"dim_arc_symbol" => DIMARCSYM,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn property_string_code(field: &str) -> Option<i16> {
|
||||||
|
Some(match field {
|
||||||
|
"dim_sub_units_suffix" => DIMMZS,
|
||||||
|
"dim_alt_sub_units_suffix" => DIMALTMZS,
|
||||||
|
_ => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn yes(value: &str) -> Option<i16> {
|
||||||
|
match value.trim().to_ascii_lowercase().as_str() {
|
||||||
|
"yes" | "on" | "true" | "1" => Some(1),
|
||||||
|
"no" | "off" | "false" | "0" => Some(0),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inherited_int(doc: &CadDocument, handle: Handle, code: i16) -> i16 {
|
||||||
|
let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
if let Some(value) = int(&dimension.base().common.extended_data, code) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
let Some(style) = doc.dim_styles.iter().find(|style| {
|
||||||
|
style.name.eq_ignore_ascii_case(&dimension.base().style_name)
|
||||||
|
|| (dimension.base().style_name.trim().is_empty()
|
||||||
|
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||||
|
}) else {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
match code {
|
||||||
|
DIMSD1 => style.dimsd1 as i16,
|
||||||
|
DIMSD2 => style.dimsd2 as i16,
|
||||||
|
DIMSE1 => style.dimse1 as i16,
|
||||||
|
DIMSE2 => style.dimse2 as i16,
|
||||||
|
DIMZIN => style.dimzin,
|
||||||
|
DIMALTZ => style.dimaltz,
|
||||||
|
DIMTZIN => style.dimtzin,
|
||||||
|
DIMALTTZ => style.dimalttz,
|
||||||
|
DIMTOL => style.dimtol as i16,
|
||||||
|
DIMLIM => style.dimlim as i16,
|
||||||
|
DIMALT => style.dimalt as i16,
|
||||||
|
DIMAZIN => style.dimazin,
|
||||||
|
DIMAUNIT => style.dimaunit,
|
||||||
|
DIMADEC => style.dimadec,
|
||||||
|
DIMTFILL => style.dimtfill,
|
||||||
|
DIMTALN => 0,
|
||||||
|
DIMARCSYM => style.dimarcsym,
|
||||||
|
_ => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inherited_real(doc: &CadDocument, handle: Handle, code: i16) -> f64 {
|
||||||
|
let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else {
|
||||||
|
return 0.0;
|
||||||
|
};
|
||||||
|
if let Some(value) = real(&dimension.base().common.extended_data, code) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
let Some(style) = doc.dim_styles.iter().find(|style| {
|
||||||
|
style.name.eq_ignore_ascii_case(&dimension.base().style_name)
|
||||||
|
|| (dimension.base().style_name.trim().is_empty()
|
||||||
|
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||||
|
}) else {
|
||||||
|
return 0.0;
|
||||||
|
};
|
||||||
|
match code {
|
||||||
|
DIMGAP => style.dimgap,
|
||||||
|
DIMCEN => style.dimcen,
|
||||||
|
DIMTP => style.dimtp,
|
||||||
|
DIMTM => style.dimtm,
|
||||||
|
_ => 0.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inherited_string(doc: &CadDocument, handle: Handle, code: i16) -> String {
|
||||||
|
let Some(EntityType::Dimension(dimension)) = doc.get_entity(handle) else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
if let Some(value) = string(&dimension.base().common.extended_data, code) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
let Some(style) = doc.dim_styles.iter().find(|style| {
|
||||||
|
style.name.eq_ignore_ascii_case(&dimension.base().style_name)
|
||||||
|
|| (dimension.base().style_name.trim().is_empty()
|
||||||
|
&& style.name.eq_ignore_ascii_case("Standard"))
|
||||||
|
}) else {
|
||||||
|
return String::new();
|
||||||
|
};
|
||||||
|
match code {
|
||||||
|
DIMPOST => style.dimpost.clone(),
|
||||||
|
DIMAPOST => style.dimapost.clone(),
|
||||||
|
_ => String::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_template(value: &str) -> (&str, &str) {
|
||||||
|
value.split_once("<>").unwrap_or(("", value))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_property(
|
||||||
|
doc: &mut CadDocument,
|
||||||
|
handle: Handle,
|
||||||
|
field: &str,
|
||||||
|
value: &str,
|
||||||
|
) -> bool {
|
||||||
|
let trimmed = value.trim();
|
||||||
|
if field == "dim_center_type" {
|
||||||
|
let current = inherited_real(doc, handle, DIMCEN);
|
||||||
|
let size = current.abs().max(0.01);
|
||||||
|
let value = match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"none" => 0.0,
|
||||||
|
"center marks" => size,
|
||||||
|
"centerlines" => -size,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
set(doc, handle, DIMCEN, Some(XDataValue::Real(value)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if field == "dim_center_size" {
|
||||||
|
let Ok(size) = trimmed.parse::<f64>() else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
if !size.is_finite() || size < 0.0 {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let current = inherited_real(doc, handle, DIMCEN);
|
||||||
|
let value = if current < 0.0 { -size } else { size };
|
||||||
|
set(doc, handle, DIMCEN, Some(XDataValue::Real(value)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let handle_field = match field {
|
||||||
|
"dim_arrowhead_1" => Some(DIMBLK1),
|
||||||
|
"dim_arrowhead_2" => Some(DIMBLK2),
|
||||||
|
"dim_linetype" => Some(DIMLTYPE),
|
||||||
|
"dim_ext_linetype_1" => Some(DIMLTEX1),
|
||||||
|
"dim_ext_linetype_2" => Some(DIMLTEX2),
|
||||||
|
"dim_text_style" => Some(DIMTXSTY),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(code) = handle_field {
|
||||||
|
let resolved = match field {
|
||||||
|
"dim_arrowhead_1" | "dim_arrowhead_2" => {
|
||||||
|
if trimmed == "Closed filled" {
|
||||||
|
Some(Handle::NULL)
|
||||||
|
} else {
|
||||||
|
doc.block_records
|
||||||
|
.iter()
|
||||||
|
.find(|record| record.name == trimmed)
|
||||||
|
.map(|record| record.handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"dim_text_style" => doc
|
||||||
|
.text_styles
|
||||||
|
.iter()
|
||||||
|
.find(|style| style.name == trimmed)
|
||||||
|
.map(|style| style.handle),
|
||||||
|
_ => doc
|
||||||
|
.line_types
|
||||||
|
.iter()
|
||||||
|
.find(|line_type| line_type.name == trimmed)
|
||||||
|
.map(|line_type| line_type.handle),
|
||||||
|
};
|
||||||
|
let Some(resolved) = resolved else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
set(doc, handle, code, Some(XDataValue::Handle(resolved)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let inverse_boolean_code = match field {
|
||||||
|
"dim_line_1" => Some(DIMSD1),
|
||||||
|
"dim_line_2" => Some(DIMSD2),
|
||||||
|
"dim_ext_line_1" => Some(DIMSE1),
|
||||||
|
"dim_ext_line_2" => Some(DIMSE2),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some(code) = inverse_boolean_code {
|
||||||
|
let Some(visible) = yes(trimmed) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
set(doc, handle, code, Some(XDataValue::Integer16((visible == 0) as i16)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let bit_field = match field {
|
||||||
|
"dim_suppress_leading_zeros" => Some((DIMZIN, 4)),
|
||||||
|
"dim_suppress_trailing_zeros" => Some((DIMZIN, 8)),
|
||||||
|
"dim_alt_suppress_leading_zeros" => Some((DIMALTZ, 4)),
|
||||||
|
"dim_alt_suppress_trailing_zeros" => Some((DIMALTZ, 8)),
|
||||||
|
"dim_tolerance_suppress_leading_zeros" => Some((DIMTZIN, 4)),
|
||||||
|
"dim_tolerance_suppress_trailing_zeros" => Some((DIMTZIN, 8)),
|
||||||
|
"dim_alt_tolerance_suppress_leading_zeros" => Some((DIMALTTZ, 4)),
|
||||||
|
"dim_alt_tolerance_suppress_trailing_zeros" => Some((DIMALTTZ, 8)),
|
||||||
|
"dim_angle_suppress_leading_zeros" => Some((DIMAZIN, 1)),
|
||||||
|
"dim_angle_suppress_trailing_zeros" => Some((DIMAZIN, 2)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some((code, bit)) = bit_field {
|
||||||
|
let Some(enabled) = yes(trimmed) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let current = inherited_int(doc, handle, code);
|
||||||
|
let value = if enabled != 0 {
|
||||||
|
current | bit
|
||||||
|
} else {
|
||||||
|
current & !bit
|
||||||
|
};
|
||||||
|
set(doc, handle, code, Some(XDataValue::Integer16(value)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let zero_base = match field {
|
||||||
|
"dim_suppress_zero_feet" => Some((DIMZIN, true)),
|
||||||
|
"dim_suppress_zero_inches" => Some((DIMZIN, false)),
|
||||||
|
"dim_alt_suppress_zero_feet" => Some((DIMALTZ, true)),
|
||||||
|
"dim_alt_suppress_zero_inches" => Some((DIMALTZ, false)),
|
||||||
|
"dim_tolerance_suppress_zero_feet" => Some((DIMTZIN, true)),
|
||||||
|
"dim_tolerance_suppress_zero_inches" => Some((DIMTZIN, false)),
|
||||||
|
"dim_alt_tolerance_suppress_zero_feet" => Some((DIMALTTZ, true)),
|
||||||
|
"dim_alt_tolerance_suppress_zero_inches" => Some((DIMALTTZ, false)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
if let Some((code, feet_field)) = zero_base {
|
||||||
|
let Some(enabled) = yes(trimmed).map(|value| value != 0) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
let current = inherited_int(doc, handle, code);
|
||||||
|
let mut suppress_feet = matches!(current & 3, 0 | 3);
|
||||||
|
let mut suppress_inches = matches!(current & 3, 0 | 2);
|
||||||
|
if feet_field {
|
||||||
|
suppress_feet = enabled;
|
||||||
|
} else {
|
||||||
|
suppress_inches = enabled;
|
||||||
|
}
|
||||||
|
let base = match (suppress_feet, suppress_inches) {
|
||||||
|
(true, true) => 0,
|
||||||
|
(false, false) => 1,
|
||||||
|
(false, true) => 2,
|
||||||
|
(true, false) => 3,
|
||||||
|
};
|
||||||
|
set(
|
||||||
|
doc,
|
||||||
|
handle,
|
||||||
|
code,
|
||||||
|
Some(XDataValue::Integer16((current & !3) | base)),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if field == "dim_text_fill_color" {
|
||||||
|
let mode = match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"none" => 0,
|
||||||
|
"background" => 1,
|
||||||
|
"color" => 2,
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
set(doc, handle, DIMTFILL, Some(XDataValue::Integer16(mode)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if matches!(
|
||||||
|
field,
|
||||||
|
"dim_prefix" | "dim_suffix" | "dim_alt_prefix" | "dim_alt_suffix"
|
||||||
|
) {
|
||||||
|
let code = if field.starts_with("dim_alt_") {
|
||||||
|
DIMAPOST
|
||||||
|
} else {
|
||||||
|
DIMPOST
|
||||||
|
};
|
||||||
|
let current = inherited_string(doc, handle, code);
|
||||||
|
let (old_prefix, old_suffix) = split_template(¤t);
|
||||||
|
let (prefix, suffix) = if field.ends_with("prefix") {
|
||||||
|
(value, old_suffix)
|
||||||
|
} else {
|
||||||
|
(old_prefix, value)
|
||||||
|
};
|
||||||
|
set(
|
||||||
|
doc,
|
||||||
|
handle,
|
||||||
|
code,
|
||||||
|
Some(XDataValue::String(format!("{prefix}<>{suffix}"))),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if field == "dim_tolerance_display" {
|
||||||
|
let current_gap = inherited_real(doc, handle, DIMGAP).abs();
|
||||||
|
let upper = inherited_real(doc, handle, DIMTP);
|
||||||
|
let lower = inherited_real(doc, handle, DIMTM);
|
||||||
|
let (tolerance, limits, basic) = match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"symmetrical" => {
|
||||||
|
set(doc, handle, DIMTM, Some(XDataValue::Real(upper)));
|
||||||
|
(1, 0, false)
|
||||||
|
}
|
||||||
|
"deviation" => {
|
||||||
|
if (upper - lower).abs() <= 1e-12 {
|
||||||
|
let distinct_lower = if upper.abs() > 1e-12 { 0.0 } else { 0.0001 };
|
||||||
|
set(
|
||||||
|
doc,
|
||||||
|
handle,
|
||||||
|
DIMTM,
|
||||||
|
Some(XDataValue::Real(distinct_lower)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
(1, 0, false)
|
||||||
|
}
|
||||||
|
"limits" => (0, 1, false),
|
||||||
|
"basic" => (0, 0, true),
|
||||||
|
"none" => (0, 0, false),
|
||||||
|
_ => return false,
|
||||||
|
};
|
||||||
|
set(doc, handle, DIMTOL, Some(XDataValue::Integer16(tolerance)));
|
||||||
|
set(doc, handle, DIMLIM, Some(XDataValue::Integer16(limits)));
|
||||||
|
let gap = if basic {
|
||||||
|
-current_gap.max(0.0001)
|
||||||
|
} else {
|
||||||
|
current_gap
|
||||||
|
};
|
||||||
|
set(doc, handle, DIMGAP, Some(XDataValue::Real(gap)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if let Some(code) = property_real_code(field) {
|
||||||
|
if trimmed.is_empty() {
|
||||||
|
set(doc, handle, code, None);
|
||||||
|
} else if let Ok(number) = trimmed.parse::<f64>() {
|
||||||
|
let number = if field == "dim_text_offset"
|
||||||
|
&& inherited_real(doc, handle, DIMGAP) < 0.0
|
||||||
|
{
|
||||||
|
-number.abs()
|
||||||
|
} else {
|
||||||
|
number
|
||||||
|
};
|
||||||
|
set(doc, handle, code, Some(XDataValue::Real(number)));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if let Some(code) = property_string_code(field) {
|
||||||
|
set(
|
||||||
|
doc,
|
||||||
|
handle,
|
||||||
|
code,
|
||||||
|
(!trimmed.is_empty()).then(|| XDataValue::String(value.to_string())),
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if let Some(code) = property_int_code(field) {
|
||||||
|
let parsed = match field {
|
||||||
|
"dim_line_lineweight" | "dim_ext_line_lineweight" => {
|
||||||
|
parse_lineweight_label(trimmed)
|
||||||
|
}
|
||||||
|
"dim_ext_line_fixed"
|
||||||
|
| "dim_text_outside_align"
|
||||||
|
| "dim_text_inside_align"
|
||||||
|
| "dim_text_inside"
|
||||||
|
| "dim_line_forced"
|
||||||
|
| "dim_line_inside"
|
||||||
|
| "dim_alt_enabled" => yes(trimmed),
|
||||||
|
"dim_decimal_separator" => trimmed
|
||||||
|
.chars()
|
||||||
|
.next()
|
||||||
|
.map(|character| character as i16),
|
||||||
|
"dim_units" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"scientific" => Some(1),
|
||||||
|
"decimal" => Some(2),
|
||||||
|
"engineering" => Some(3),
|
||||||
|
"architectural" => Some(4),
|
||||||
|
"fractional" => Some(5),
|
||||||
|
"desktop" => Some(6),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_angle_units" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"decimal degrees" => Some(0),
|
||||||
|
"degrees/minutes/seconds" => Some(1),
|
||||||
|
"gradians" => Some(2),
|
||||||
|
"radians" => Some(3),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_fractional_type" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"horizontal" => Some(0),
|
||||||
|
"diagonal" => Some(1),
|
||||||
|
"not stacked" => Some(2),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_text_view_direction" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"left-to-right" => Some(0),
|
||||||
|
"right-to-left" => Some(1),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_text_pos_vert" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"centered" => Some(0),
|
||||||
|
"above" => Some(1),
|
||||||
|
"outside" => Some(2),
|
||||||
|
"jis" => Some(3),
|
||||||
|
"below" => Some(4),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_text_pos_hor" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"centered" => Some(0),
|
||||||
|
"at extension line 1" => Some(1),
|
||||||
|
"at extension line 2" => Some(2),
|
||||||
|
"over extension line 1" => Some(3),
|
||||||
|
"over extension line 2" => Some(4),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_fit" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"both text and arrows" => Some(0),
|
||||||
|
"arrows" => Some(1),
|
||||||
|
"text" => Some(2),
|
||||||
|
"best fit" => Some(3),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_text_movement" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"keep dim line with text" => Some(0),
|
||||||
|
"move text, add leader" => Some(1),
|
||||||
|
"move text, no leader" => Some(2),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_arc_symbol" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"preceding dimension text" => Some(0),
|
||||||
|
"above dimension text" => Some(1),
|
||||||
|
"none" => Some(2),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_alt_format" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"scientific" => Some(1),
|
||||||
|
"decimal" => Some(2),
|
||||||
|
"engineering" => Some(3),
|
||||||
|
"architectural stacked" => Some(4),
|
||||||
|
"fractional stacked" => Some(5),
|
||||||
|
"architectural" => Some(6),
|
||||||
|
"fractional" => Some(7),
|
||||||
|
"desktop" => Some(8),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_tolerance_pos_vert" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"bottom" => Some(0),
|
||||||
|
"middle" => Some(1),
|
||||||
|
"top" => Some(2),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
"dim_tolerance_alignment" => match trimmed.to_ascii_lowercase().as_str() {
|
||||||
|
"align decimal separators" => Some(0),
|
||||||
|
"align operational symbols" => Some(1),
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
},
|
||||||
|
_ => trimmed.parse().ok(),
|
||||||
|
};
|
||||||
|
let Some(number) = parsed else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
set(doc, handle, code, Some(XDataValue::Integer16(number)));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_lineweight_label(value: &str) -> Option<i16> {
|
||||||
|
match value.trim() {
|
||||||
|
"ByLayer" => Some(-1),
|
||||||
|
"ByBlock" => Some(-2),
|
||||||
|
"Default" => Some(-3),
|
||||||
|
value => value
|
||||||
|
.trim_end_matches("mm")
|
||||||
|
.trim()
|
||||||
|
.parse::<f64>()
|
||||||
|
.ok()
|
||||||
|
.map(|millimetres| (millimetres * 100.0).round() as i16),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -676,6 +676,7 @@ impl LeaderTess for Leader {
|
||||||
return WireModel {
|
return WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
@ -855,6 +856,7 @@ impl LeaderTess for Leader {
|
||||||
WireModel {
|
WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,15 @@ use crate::scene::model::wire_model::TangentGeom;
|
||||||
const TAU: f64 = std::f64::consts::TAU;
|
const TAU: f64 = std::f64::consts::TAU;
|
||||||
const REVCLOUD_BULGE: f64 = 0.5;
|
const REVCLOUD_BULGE: f64 = 0.5;
|
||||||
const MAX_REVCLOUD_VERTICES: usize = 100_000;
|
const MAX_REVCLOUD_VERTICES: usize = 100_000;
|
||||||
|
const WIDTH_EPSILON: f64 = 1.0e-9;
|
||||||
|
|
||||||
|
fn effective_width(width: f64, constant_width: f64) -> f64 {
|
||||||
|
if width > WIDTH_EPSILON {
|
||||||
|
width
|
||||||
|
} else {
|
||||||
|
constant_width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Midpoint position on an arc segment defined by its bulge.
|
/// Midpoint position on an arc segment defined by its bulge.
|
||||||
fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
|
fn arc_midpoint(p0: [f64; 2], p1: [f64; 2], bulge: f64) -> [f64; 2] {
|
||||||
|
|
@ -251,7 +260,7 @@ fn thick_wide_band(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_render(pline: &LwPolyline) -> RenderEntity {
|
fn to_render(pline: &LwPolyline, fill_mode: bool) -> RenderEntity {
|
||||||
let verts = &pline.vertices;
|
let verts = &pline.vertices;
|
||||||
if verts.is_empty() {
|
if verts.is_empty() {
|
||||||
return RenderEntity {
|
return RenderEntity {
|
||||||
|
|
@ -268,9 +277,6 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||||
let normal = (pline.normal.x, pline.normal.y, pline.normal.z);
|
let normal = (pline.normal.x, pline.normal.y, pline.normal.z);
|
||||||
let count = verts.len();
|
let count = verts.len();
|
||||||
let seg_count = if pline.is_closed { count } else { count - 1 };
|
let seg_count = if pline.is_closed { count } else { count - 1 };
|
||||||
let mut tangents: Vec<TangentGeom> = Vec::new();
|
|
||||||
let mut key_verts: Vec<[f64; 3]> = Vec::new();
|
|
||||||
|
|
||||||
// Convert OCS (x, y, elevation) to a WCS point.
|
// Convert OCS (x, y, elevation) to a WCS point.
|
||||||
let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) {
|
let to_wcs = |x: f64, y: f64| -> (f64, f64, f64) {
|
||||||
crate::scene::view::transform::ocs_point_to_wcs((x, y, elev), normal)
|
crate::scene::view::transform::ocs_point_to_wcs((x, y, elev), normal)
|
||||||
|
|
@ -280,6 +286,44 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||||
[wx, wy, wz]
|
[wx, wy, wz]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let band_verts = band_verts(pline);
|
||||||
|
if !fill_mode {
|
||||||
|
let mut boundary = crate::entities::common::wide_band_outline(
|
||||||
|
&band_verts,
|
||||||
|
pline.is_closed,
|
||||||
|
!pline.plinegen,
|
||||||
|
&to_wcs,
|
||||||
|
);
|
||||||
|
if !boundary.points.is_empty() {
|
||||||
|
if pline.thickness.abs() > 1e-10 {
|
||||||
|
boundary = crate::entities::common::extrude_wide_band_outline(
|
||||||
|
boundary,
|
||||||
|
[
|
||||||
|
pline.thickness * normal.0,
|
||||||
|
pline.thickness * normal.1,
|
||||||
|
pline.thickness * normal.2,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let (tangent_geoms, key_vertices) = centerline_metadata(pline, &to_wcs);
|
||||||
|
return RenderEntity {
|
||||||
|
pick_tris: Vec::new(),
|
||||||
|
object: RenderObject::BoundaryLines {
|
||||||
|
points: boundary.points,
|
||||||
|
stations: boundary.stations,
|
||||||
|
point_segments: boundary.point_segments,
|
||||||
|
station_pieces: boundary.station_pieces,
|
||||||
|
source_length: boundary.source_length,
|
||||||
|
plinegen: pline.plinegen,
|
||||||
|
},
|
||||||
|
snap_pts: vec![],
|
||||||
|
tangent_geoms,
|
||||||
|
key_vertices,
|
||||||
|
fill_tris: vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if pline.thickness.abs() > 1e-10 {
|
if pline.thickness.abs() > 1e-10 {
|
||||||
let mut path: Vec<[f64; 3]> = Vec::new();
|
let mut path: Vec<[f64; 3]> = Vec::new();
|
||||||
let mut kv: Vec<[f64; 3]> = Vec::new();
|
let mut kv: Vec<[f64; 3]> = Vec::new();
|
||||||
|
|
@ -341,7 +385,7 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||||
// A wide polyline whose per-vertex widths VARY renders a smooth taper —
|
// A wide polyline whose per-vertex widths VARY renders a smooth taper —
|
||||||
// handled here, before the PLINEGEN split, so both cases get it. A
|
// handled here, before the PLINEGEN split, so both cases get it. A
|
||||||
// uniform-width polyline falls through to the constant-band paths below.
|
// uniform-width polyline falls through to the constant-band paths below.
|
||||||
if let Some(band_verts) = tapered_band_verts(pline) {
|
if tapered_band_verts(&band_verts).is_some() {
|
||||||
let mut kv: Vec<[f64; 3]> = Vec::new();
|
let mut kv: Vec<[f64; 3]> = Vec::new();
|
||||||
let mut tgs: Vec<TangentGeom> = Vec::new();
|
let mut tgs: Vec<TangentGeom> = Vec::new();
|
||||||
for i in 0..seg_count {
|
for i in 0..seg_count {
|
||||||
|
|
@ -370,8 +414,11 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let (pts, widths) =
|
let (pts, widths) = crate::entities::common::tapered_band_points(
|
||||||
crate::entities::common::tapered_band_points(&band_verts, pline.is_closed, &to_wcs);
|
&band_verts,
|
||||||
|
pline.is_closed,
|
||||||
|
&to_wcs,
|
||||||
|
);
|
||||||
let (fill_origin, fills) = wide_fills(pline);
|
let (fill_origin, fills) = wide_fills(pline);
|
||||||
return RenderEntity {
|
return RenderEntity {
|
||||||
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
||||||
|
|
@ -444,36 +491,7 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..seg_count {
|
let (tangents, key_verts) = centerline_metadata(pline, &to_wcs);
|
||||||
let v0 = &verts[i];
|
|
||||||
let v1 = &verts[(i + 1) % count];
|
|
||||||
let p0 = to_pt(v0);
|
|
||||||
let p1 = to_pt(v1);
|
|
||||||
let bulge = v0.bulge;
|
|
||||||
|
|
||||||
if bulge.abs() < 1e-9 {
|
|
||||||
tangents.push(TangentGeom::Line {
|
|
||||||
p1: [p0[0] as f32, p0[1] as f32, p0[2] as f32],
|
|
||||||
p2: [p1[0] as f32, p1[1] as f32, p1[2] as f32],
|
|
||||||
});
|
|
||||||
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
|
||||||
[v0.location.x, v0.location.y],
|
|
||||||
[v1.location.x, v1.location.y],
|
|
||||||
bulge as f64,
|
|
||||||
) {
|
|
||||||
let (wcx, wcy, wcz) = to_wcs(arc.center[0], arc.center[1]);
|
|
||||||
tangents.push(TangentGeom::Circle {
|
|
||||||
center: [wcx as f32, wcy as f32, wcz as f32],
|
|
||||||
radius: arc.radius as f32,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if i == 0 {
|
|
||||||
key_verts.push([p0[0], p0[1], p0[2]]);
|
|
||||||
}
|
|
||||||
key_verts.push([p1[0], p1[1], p1[2]]);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (fill_origin, fills) = wide_fills(pline);
|
let (fill_origin, fills) = wide_fills(pline);
|
||||||
RenderEntity {
|
RenderEntity {
|
||||||
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
pick_tris: crate::entities::common::wide_band_tris(fill_origin, &fills),
|
||||||
|
|
@ -491,20 +509,23 @@ fn to_render(pline: &LwPolyline) -> RenderEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The per-vertex `(location, bulge, start_width, end_width)` band description
|
/// Effective segment widths for an LwPolyline band.
|
||||||
/// for a wide LwPolyline whose width VARIES — `None` when the width is uniform
|
fn band_verts(pline: &LwPolyline) -> Vec<([f64; 2], f64, f64, f64)> {
|
||||||
/// (a constant band) so the caller keeps the cheaper constant-width path.
|
|
||||||
fn tapered_band_verts(pline: &LwPolyline) -> Option<Vec<([f64; 2], f64, f64, f64)>> {
|
|
||||||
let c = pline.constant_width;
|
let c = pline.constant_width;
|
||||||
let band: Vec<([f64; 2], f64, f64, f64)> = pline
|
pline
|
||||||
.vertices
|
.vertices
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
let sw = if v.start_width > 1e-9 { v.start_width } else { c };
|
let sw = effective_width(v.start_width, c);
|
||||||
let ew = if v.end_width > 1e-9 { v.end_width } else { c };
|
let ew = effective_width(v.end_width, c);
|
||||||
([v.location.x, v.location.y], v.bulge, sw, ew)
|
([v.location.x, v.location.y], v.bulge, sw, ew)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tapered_band_verts(
|
||||||
|
band: &[([f64; 2], f64, f64, f64)],
|
||||||
|
) -> Option<&[([f64; 2], f64, f64, f64)]> {
|
||||||
let w0 = band.first().map_or(0.0, |v| v.2);
|
let w0 = band.first().map_or(0.0, |v| v.2);
|
||||||
let varies = band
|
let varies = band
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -517,6 +538,47 @@ fn tapered_band_verts(pline: &LwPolyline) -> Option<Vec<([f64; 2], f64, f64, f64
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn centerline_metadata(
|
||||||
|
pline: &LwPolyline,
|
||||||
|
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||||
|
) -> (Vec<TangentGeom>, Vec<[f64; 3]>) {
|
||||||
|
let count = pline.vertices.len();
|
||||||
|
let segment_count = if pline.is_closed {
|
||||||
|
count
|
||||||
|
} else {
|
||||||
|
count.saturating_sub(1)
|
||||||
|
};
|
||||||
|
let mut tangents = Vec::with_capacity(segment_count);
|
||||||
|
let mut key_vertices = Vec::with_capacity(segment_count + 1);
|
||||||
|
for index in 0..segment_count {
|
||||||
|
let start = &pline.vertices[index];
|
||||||
|
let end = &pline.vertices[(index + 1) % count];
|
||||||
|
let p0 = to_wcs(start.location.x, start.location.y);
|
||||||
|
let p1 = to_wcs(end.location.x, end.location.y);
|
||||||
|
if start.bulge.abs() < 1e-9 {
|
||||||
|
tangents.push(TangentGeom::Line {
|
||||||
|
p1: [p0.0 as f32, p0.1 as f32, p0.2 as f32],
|
||||||
|
p2: [p1.0 as f32, p1.1 as f32, p1.2 as f32],
|
||||||
|
});
|
||||||
|
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
||||||
|
[start.location.x, start.location.y],
|
||||||
|
[end.location.x, end.location.y],
|
||||||
|
start.bulge,
|
||||||
|
) {
|
||||||
|
let center = to_wcs(arc.center[0], arc.center[1]);
|
||||||
|
tangents.push(TangentGeom::Circle {
|
||||||
|
center: [center.0 as f32, center.1 as f32, center.2 as f32],
|
||||||
|
radius: arc.radius as f32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if index == 0 {
|
||||||
|
key_vertices.push([p0.0, p0.1, p0.2]);
|
||||||
|
}
|
||||||
|
key_vertices.push([p1.0, p1.1, p1.2]);
|
||||||
|
}
|
||||||
|
(tangents, key_vertices)
|
||||||
|
}
|
||||||
|
|
||||||
/// Split at vertex `idx`: a closed polyline re-opens there (one piece); an
|
/// Split at vertex `idx`: a closed polyline re-opens there (one piece); an
|
||||||
/// open one splits into two (interior vertices only). `None` when invalid.
|
/// open one splits into two (interior vertices only). `None` when invalid.
|
||||||
pub(crate) fn break_at_vertex(
|
pub(crate) fn break_at_vertex(
|
||||||
|
|
@ -852,8 +914,12 @@ fn properties(pline: &LwPolyline) -> Vec<PropSection> {
|
||||||
let v = pline.vertices.get(vi);
|
let v = pline.vertices.get(vi);
|
||||||
let vx = v.map_or(0.0, |v| v.location.x);
|
let vx = v.map_or(0.0, |v| v.location.x);
|
||||||
let vy = v.map_or(0.0, |v| v.location.y);
|
let vy = v.map_or(0.0, |v| v.location.y);
|
||||||
let start_w = v.map_or(0.0, |v| v.start_width);
|
let start_w = v.map_or(0.0, |v| {
|
||||||
let end_w = v.map_or(0.0, |v| v.end_width);
|
effective_width(v.start_width, pline.constant_width)
|
||||||
|
});
|
||||||
|
let end_w = v.map_or(0.0, |v| {
|
||||||
|
effective_width(v.end_width, pline.constant_width)
|
||||||
|
});
|
||||||
let mp = <LwPolyline as crate::entities::traits::MassPropsCalc>::mass_props(pline);
|
let mp = <LwPolyline as crate::entities::traits::MassPropsCalc>::mass_props(pline);
|
||||||
let cloud_arc_length = revision_cloud_arc_length(pline);
|
let cloud_arc_length = revision_cloud_arc_length(pline);
|
||||||
let vertex_label = if n == 0 {
|
let vertex_label = if n == 0 {
|
||||||
|
|
@ -964,7 +1030,7 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||||
};
|
};
|
||||||
match field {
|
match field {
|
||||||
"elevation" => pline.elevation = v,
|
"elevation" => pline.elevation = v,
|
||||||
"global_width" => pline.constant_width = v,
|
"global_width" if v.is_finite() && v >= 0.0 => pline.constant_width = v,
|
||||||
"vertex_x" => {
|
"vertex_x" => {
|
||||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||||
vtx.location.x = v;
|
vtx.location.x = v;
|
||||||
|
|
@ -975,12 +1041,12 @@ fn apply_geom_prop(pline: &mut LwPolyline, field: &str, value: &str) {
|
||||||
vtx.location.y = v;
|
vtx.location.y = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"start_width" => {
|
"start_width" if v.is_finite() && v >= 0.0 => {
|
||||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||||
vtx.start_width = v;
|
vtx.start_width = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"end_width" => {
|
"end_width" if v.is_finite() && v >= 0.0 => {
|
||||||
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
if let Some(vtx) = pline.vertices.get_mut(vi) {
|
||||||
vtx.end_width = v;
|
vtx.end_width = v;
|
||||||
}
|
}
|
||||||
|
|
@ -1079,8 +1145,8 @@ fn apply_transform(pline: &mut LwPolyline, t: &EntityTransform) {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderConvertible for LwPolyline {
|
impl RenderConvertible for LwPolyline {
|
||||||
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||||
Some(to_render(self))
|
Some(to_render(self, document.header.fill_mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1201,16 +1267,8 @@ impl crate::entities::traits::Grippable for LwPolyline {
|
||||||
new_v.location.x = midpoint[0];
|
new_v.location.x = midpoint[0];
|
||||||
new_v.location.y = midpoint[1];
|
new_v.location.y = midpoint[1];
|
||||||
new_v.vertex_id = 0;
|
new_v.vertex_id = 0;
|
||||||
let effective_start = if v0.start_width > 1e-9 {
|
let effective_start = effective_width(v0.start_width, self.constant_width);
|
||||||
v0.start_width
|
let effective_end = effective_width(v0.end_width, self.constant_width);
|
||||||
} else {
|
|
||||||
self.constant_width
|
|
||||||
};
|
|
||||||
let effective_end = if v0.end_width > 1e-9 {
|
|
||||||
v0.end_width
|
|
||||||
} else {
|
|
||||||
self.constant_width
|
|
||||||
};
|
|
||||||
let middle_width = (effective_start + effective_end) * 0.5;
|
let middle_width = (effective_start + effective_end) * 0.5;
|
||||||
self.vertices[i0].end_width = middle_width;
|
self.vertices[i0].end_width = middle_width;
|
||||||
new_v.start_width = middle_width;
|
new_v.start_width = middle_width;
|
||||||
|
|
@ -1271,7 +1329,6 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec<
|
||||||
// Feeding the stored width in whole draws every wide polyline twice as wide
|
// Feeding the stored width in whole draws every wide polyline twice as wide
|
||||||
// as the file asks for, which on a donut (a closed 2-vertex bulge-1 polyline)
|
// as the file asks for, which on a donut (a closed 2-vertex bulge-1 polyline)
|
||||||
// shows up as a disc 1.5× its real radius.
|
// shows up as a disc 1.5× its real radius.
|
||||||
let hw_const = pl.constant_width as f32 * 0.5;
|
|
||||||
let verts = &pl.vertices;
|
let verts = &pl.vertices;
|
||||||
let n = verts.len();
|
let n = verts.len();
|
||||||
if n < 2 {
|
if n < 2 {
|
||||||
|
|
@ -1283,16 +1340,8 @@ pub(crate) fn wide_fills(pl: &acadrust::entities::LwPolyline) -> ([f64; 2], Vec<
|
||||||
for i in 0..seg_count {
|
for i in 0..seg_count {
|
||||||
let v0 = &verts[i];
|
let v0 = &verts[i];
|
||||||
let v1 = &verts[(i + 1) % n];
|
let v1 = &verts[(i + 1) % n];
|
||||||
let hw0 = if v0.start_width > 1e-9 {
|
let hw0 = effective_width(v0.start_width, pl.constant_width) as f32 * 0.5;
|
||||||
v0.start_width as f32 * 0.5
|
let hw1 = effective_width(v0.end_width, pl.constant_width) as f32 * 0.5;
|
||||||
} else {
|
|
||||||
hw_const
|
|
||||||
};
|
|
||||||
let hw1 = if v0.end_width > 1e-9 {
|
|
||||||
v0.end_width as f32 * 0.5
|
|
||||||
} else {
|
|
||||||
hw_const
|
|
||||||
};
|
|
||||||
if hw0 < 1e-6 && hw1 < 1e-6 {
|
if hw0 < 1e-6 && hw1 < 1e-6 {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@ use crate::entities::common::{
|
||||||
edit_angle_prop as edit_angle, edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip,
|
edit_angle_prop as edit_angle, edit_prop as edit, num_prop as num_row, ro_prop as ro, square_grip, triangle_grip,
|
||||||
};
|
};
|
||||||
use crate::entities::text_support::{
|
use crate::entities::text_support::{
|
||||||
layout_mtext, resolve_text_style, GlyphBox, MTextColumns, MTextRenderOpts, MTextVAnchor,
|
clamp_mtext_column_count, layout_mtext, resolve_text_style, GlyphBox, MTextColumns,
|
||||||
|
MTextRenderOpts, MTextVAnchor,
|
||||||
};
|
};
|
||||||
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
use crate::entities::traits::{Grippable, PropertyEditable, Transformable, RenderConvertible};
|
||||||
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
use crate::scene::convert::acad_to_render::{RenderEntity, RenderObject};
|
||||||
|
|
@ -24,10 +25,19 @@ fn columns_of(t: &MText) -> MTextColumns {
|
||||||
if c.column_type == 0 {
|
if c.column_type == 0 {
|
||||||
return MTextColumns::default();
|
return MTextColumns::default();
|
||||||
}
|
}
|
||||||
|
let count = clamp_mtext_column_count(c.column_count) as usize;
|
||||||
MTextColumns {
|
MTextColumns {
|
||||||
count: c.column_count.max(0) as usize,
|
count,
|
||||||
width: c.width as f32,
|
width: c.width as f32,
|
||||||
gutter: c.gutter as f32,
|
gutter: c.gutter as f32,
|
||||||
|
flow_reversed: c.flow_reversed,
|
||||||
|
auto_height: c.auto_height,
|
||||||
|
heights: c
|
||||||
|
.heights
|
||||||
|
.iter()
|
||||||
|
.take(count)
|
||||||
|
.map(|height| *height as f32)
|
||||||
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -120,7 +130,14 @@ pub fn glyph_boxes(t: &MText, document: &acadrust::CadDocument) -> Vec<GlyphBox>
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: t.line_spacing_factor as f32,
|
line_spacing_factor: t.line_spacing_factor as f32,
|
||||||
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
|
exact_line_spacing: matches!(
|
||||||
|
t.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: t.rectangle_height.unwrap_or(0.0) as f32,
|
||||||
|
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom)
|
||||||
|
|| matches!(t.drawing_direction, DrawingDirection::ByStyle)
|
||||||
|
&& resolved_style.is_vertical,
|
||||||
want_glyph_boxes: true,
|
want_glyph_boxes: true,
|
||||||
columns: columns_of(t),
|
columns: columns_of(t),
|
||||||
});
|
});
|
||||||
|
|
@ -169,7 +186,14 @@ fn to_render(t: &MText, document: &acadrust::CadDocument) -> RenderEntity {
|
||||||
attach_h_anchor,
|
attach_h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: t.line_spacing_factor as f32,
|
line_spacing_factor: t.line_spacing_factor as f32,
|
||||||
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom),
|
exact_line_spacing: matches!(
|
||||||
|
t.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: t.rectangle_height.unwrap_or(0.0) as f32,
|
||||||
|
vertical_text: matches!(t.drawing_direction, DrawingDirection::TopToBottom)
|
||||||
|
|| matches!(t.drawing_direction, DrawingDirection::ByStyle)
|
||||||
|
&& resolved_style.is_vertical,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
columns: columns_of(t),
|
columns: columns_of(t),
|
||||||
});
|
});
|
||||||
|
|
@ -231,8 +255,43 @@ fn grips(t: &MText) -> Vec<GripDef> {
|
||||||
t.insertion_point.z,
|
t.insertion_point.z,
|
||||||
);
|
);
|
||||||
let (dir, k) = width_grip_axis(t);
|
let (dir, k) = width_grip_axis(t);
|
||||||
let width_grip = p + dir * (k * t.rectangle_width.max(0.0));
|
let column_count = clamp_mtext_column_count(t.column_data.column_count);
|
||||||
vec![square_grip(0, p), triangle_grip(1, width_grip)]
|
let columns_active =
|
||||||
|
t.column_data.column_type != 0 && column_count > 1 && t.column_data.width > 0.0;
|
||||||
|
let block_width = if columns_active {
|
||||||
|
t.column_data.width * column_count as f64
|
||||||
|
+ t.column_data.gutter * (column_count - 1) as f64
|
||||||
|
} else {
|
||||||
|
t.rectangle_width.max(0.0)
|
||||||
|
};
|
||||||
|
let width_grip = p + dir * (k * block_width);
|
||||||
|
let mut result = vec![square_grip(0, p), triangle_grip(1, width_grip)];
|
||||||
|
|
||||||
|
let height = t
|
||||||
|
.rectangle_height
|
||||||
|
.or_else(|| t.column_data.heights.first().copied())
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
if height > 0.0 && !(columns_active && t.column_data.auto_height) {
|
||||||
|
let (sin, cos) = t.rotation.sin_cos();
|
||||||
|
let down = glam::DVec3::new(sin, -cos, 0.0);
|
||||||
|
let (_, vertical) = attach_anchors(t);
|
||||||
|
let factor = match vertical {
|
||||||
|
MTextVAnchor::Top
|
||||||
|
| MTextVAnchor::MiddleOfTopLine
|
||||||
|
| MTextVAnchor::BottomOfTopLine => 1.0,
|
||||||
|
MTextVAnchor::Middle => 0.5,
|
||||||
|
MTextVAnchor::Bottom | MTextVAnchor::MiddleOfBottomLine => -1.0,
|
||||||
|
};
|
||||||
|
result.push(triangle_grip(2, p + down * (factor * height)));
|
||||||
|
}
|
||||||
|
if columns_active {
|
||||||
|
result.push(triangle_grip(3, p + dir * (k * t.column_data.width)));
|
||||||
|
result.push(triangle_grip(
|
||||||
|
4,
|
||||||
|
p + dir * (k * (t.column_data.width + t.column_data.gutter)),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
fn columns_str(c: &acadrust::entities::MTextColumnData) -> &'static str {
|
fn columns_str(c: &acadrust::entities::MTextColumnData) -> &'static str {
|
||||||
|
|
@ -248,10 +307,9 @@ fn properties(t: &MText, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
// by the line-spacing factor.
|
// by the line-spacing factor.
|
||||||
let line_space_distance = t.height * 1.666_666_666_666_667 * t.line_spacing_factor;
|
let line_space_distance = t.height * 1.666_666_666_666_667 * t.line_spacing_factor;
|
||||||
let text_frame_on = (t.background_fill_flags & 0x10) != 0;
|
let text_frame_on = (t.background_fill_flags & 0x10) != 0;
|
||||||
// Defined width is only live without columns; defined height is live for
|
// Defined width is reported by Properties but is not edited there. Defined
|
||||||
// static columns or manual-height dynamic columns, grayed otherwise.
|
// height remains live for static columns or manual-height dynamic columns.
|
||||||
let col_type = t.column_data.column_type;
|
let col_type = t.column_data.column_type;
|
||||||
let width_editable = col_type == 0;
|
|
||||||
let height_editable = col_type == 1 || (col_type == 2 && !t.column_data.auto_height);
|
let height_editable = col_type == 1 || (col_type == 2 && !t.column_data.auto_height);
|
||||||
vec![
|
vec![
|
||||||
PropSection {
|
PropSection {
|
||||||
|
|
@ -353,7 +411,7 @@ fn properties(t: &MText, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
num_row(t!("Defined width").as_ref(), "rect_w", t.rectangle_width, width_editable),
|
num_row(t!("Defined width").as_ref(), "rect_w", t.rectangle_width, false),
|
||||||
num_row(
|
num_row(
|
||||||
t!("Defined height").as_ref(),
|
t!("Defined height").as_ref(),
|
||||||
"rect_h",
|
"rect_h",
|
||||||
|
|
@ -371,20 +429,6 @@ fn properties(t: &MText, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
.collect(),
|
.collect(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Count / width / gutter are live only when columns are on.
|
|
||||||
num_row(
|
|
||||||
t!("Column count").as_ref(),
|
|
||||||
"col_count",
|
|
||||||
t.column_data.column_count as f64,
|
|
||||||
col_type != 0,
|
|
||||||
),
|
|
||||||
num_row(t!("Column width").as_ref(), "col_width", t.column_data.width, col_type != 0),
|
|
||||||
num_row(
|
|
||||||
t!("Column gutter").as_ref(),
|
|
||||||
"col_gutter",
|
|
||||||
t.column_data.gutter,
|
|
||||||
col_type != 0,
|
|
||||||
),
|
|
||||||
Property {
|
Property {
|
||||||
label: t!("Text frame").into_owned(),
|
label: t!("Text frame").into_owned(),
|
||||||
field: "text_frame",
|
field: "text_frame",
|
||||||
|
|
@ -502,21 +546,20 @@ fn apply_geom_prop(t: &mut MText, field: &str, value: &str) {
|
||||||
"ins_y" => t.insertion_point.y = v,
|
"ins_y" => t.insertion_point.y = v,
|
||||||
"ins_z" => t.insertion_point.z = v,
|
"ins_z" => t.insertion_point.z = v,
|
||||||
"height" if v > 0.0 => t.height = v,
|
"height" if v > 0.0 => t.height = v,
|
||||||
"rect_w" if v > 0.0 => t.rectangle_width = v,
|
"rect_h" if v >= 0.0 => t.rectangle_height = (v > 0.0).then_some(v),
|
||||||
"rect_h" if v > 0.0 => t.rectangle_height = Some(v),
|
|
||||||
"rotation" => t.rotation = v.to_radians(),
|
"rotation" => t.rotation = v.to_radians(),
|
||||||
"line_spacing" if v > 0.0 => t.line_spacing_factor = v,
|
"line_spacing" if (0.25..=4.0).contains(&v) => t.line_spacing_factor = v,
|
||||||
// Editing the absolute distance back-solves the line-spacing factor so
|
// Editing the absolute distance back-solves the line-spacing factor so
|
||||||
// the two stay consistent (distance = height × 5/3 × factor).
|
// the two stay consistent (distance = height × 5/3 × factor).
|
||||||
"line_space_distance" if v > 0.0 => {
|
"line_space_distance" if v > 0.0 => {
|
||||||
let denom = t.height * 1.666_666_666_666_667;
|
let denom = t.height * 1.666_666_666_666_667;
|
||||||
if denom > 0.0 {
|
if denom > 0.0 {
|
||||||
t.line_spacing_factor = v / denom;
|
let factor = v / denom;
|
||||||
|
if (0.25..=4.0).contains(&factor) {
|
||||||
|
t.line_spacing_factor = factor;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"col_count" if v >= 1.0 => t.column_data.column_count = v.round() as i32,
|
|
||||||
"col_width" if v > 0.0 => t.column_data.width = v,
|
|
||||||
"col_gutter" if v >= 0.0 => t.column_data.gutter = v,
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -541,7 +584,60 @@ fn apply_grip(t: &mut MText, grip_id: usize, apply: GripApply) {
|
||||||
let dx = p.x as f64 - t.insertion_point.x;
|
let dx = p.x as f64 - t.insertion_point.x;
|
||||||
let dy = p.y as f64 - t.insertion_point.y;
|
let dy = p.y as f64 - t.insertion_point.y;
|
||||||
let projected = dx * dir.x + dy * dir.y;
|
let projected = dx * dir.x + dy * dir.y;
|
||||||
t.rectangle_width = (projected / k).max(0.01);
|
let width = (projected / k).max(0.0);
|
||||||
|
let column_count = clamp_mtext_column_count(t.column_data.column_count);
|
||||||
|
if t.column_data.column_type != 0 && column_count > 1 {
|
||||||
|
t.column_data.column_count = column_count;
|
||||||
|
let gaps = (column_count - 1) as f64;
|
||||||
|
t.column_data.width =
|
||||||
|
((width - t.column_data.gutter * gaps) / column_count as f64)
|
||||||
|
.max(0.01);
|
||||||
|
} else {
|
||||||
|
t.rectangle_width = width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(2, GripApply::Absolute(p)) => {
|
||||||
|
let (sin, cos) = t.rotation.sin_cos();
|
||||||
|
let down = glam::DVec3::new(sin, -cos, 0.0);
|
||||||
|
let delta = glam::DVec3::new(
|
||||||
|
p.x as f64 - t.insertion_point.x,
|
||||||
|
p.y as f64 - t.insertion_point.y,
|
||||||
|
p.z as f64 - t.insertion_point.z,
|
||||||
|
);
|
||||||
|
let (_, vertical) = attach_anchors(t);
|
||||||
|
let factor = match vertical {
|
||||||
|
MTextVAnchor::Top
|
||||||
|
| MTextVAnchor::MiddleOfTopLine
|
||||||
|
| MTextVAnchor::BottomOfTopLine => 1.0,
|
||||||
|
MTextVAnchor::Middle => 0.5,
|
||||||
|
MTextVAnchor::Bottom | MTextVAnchor::MiddleOfBottomLine => -1.0,
|
||||||
|
};
|
||||||
|
let height = (delta.dot(down) / factor).max(0.01);
|
||||||
|
t.rectangle_height = Some(height);
|
||||||
|
if t.column_data.column_type != 0 && !t.column_data.auto_height {
|
||||||
|
let column_count = clamp_mtext_column_count(t.column_data.column_count);
|
||||||
|
t.column_data.column_count = column_count;
|
||||||
|
t.column_data.heights = vec![height; column_count as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(3, GripApply::Absolute(p)) => {
|
||||||
|
let (dir, k) = width_grip_axis(t);
|
||||||
|
let delta = glam::DVec3::new(
|
||||||
|
p.x as f64 - t.insertion_point.x,
|
||||||
|
p.y as f64 - t.insertion_point.y,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
t.column_data.width = (delta.dot(dir) / k).max(0.01);
|
||||||
|
}
|
||||||
|
(4, GripApply::Absolute(p)) => {
|
||||||
|
let (dir, k) = width_grip_axis(t);
|
||||||
|
let delta = glam::DVec3::new(
|
||||||
|
p.x as f64 - t.insertion_point.x,
|
||||||
|
p.y as f64 - t.insertion_point.y,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
t.column_data.gutter =
|
||||||
|
(delta.dot(dir) / k - t.column_data.width).max(0.0);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -182,9 +182,24 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
|
||||||
style: &resolved,
|
style: &resolved,
|
||||||
// Side-anchored on the leader-facing edge so the text reads
|
// Side-anchored on the leader-facing edge so the text reads
|
||||||
// outward; flips live with the leader/text side.
|
// outward; flips live with the leader/text side.
|
||||||
attach_h_anchor: if text_sign >= 0.0 { 0.0 } else { 1.0 },
|
attach_h_anchor: match ctx.text_attachment_point {
|
||||||
v_anchor: mleader_v_anchor(ctx.text_left_attachment),
|
acadrust::entities::multileader::TextAttachmentPointType::Left => 0.0,
|
||||||
|
acadrust::entities::multileader::TextAttachmentPointType::Center => 0.5,
|
||||||
|
acadrust::entities::multileader::TextAttachmentPointType::Right => 1.0,
|
||||||
|
},
|
||||||
|
v_anchor: mleader_v_anchor(
|
||||||
|
if text_sign >= 0.0 {
|
||||||
|
ctx.text_left_attachment
|
||||||
|
} else {
|
||||||
|
ctx.text_right_attachment
|
||||||
|
},
|
||||||
|
),
|
||||||
line_spacing_factor: ctx.line_spacing_factor as f32,
|
line_spacing_factor: ctx.line_spacing_factor as f32,
|
||||||
|
exact_line_spacing: matches!(
|
||||||
|
ctx.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: false,
|
vertical_text: false,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
}))
|
}))
|
||||||
|
|
@ -200,11 +215,19 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
|
||||||
} else {
|
} else {
|
||||||
0.0
|
0.0
|
||||||
};
|
};
|
||||||
// text_location is the leader-facing (near) edge; the landing runs one
|
|
||||||
// dogleg from there back toward the leader.
|
let landing_gap = ml.context.landing_gap.max(0.0);
|
||||||
let landing_pt = [
|
|
||||||
text_loc.x - text_sign * dogleg * tdx,
|
// Landing endpoints.
|
||||||
text_loc.y - text_sign * dogleg * tdy,
|
let elbow_pt = [
|
||||||
|
text_loc.x - text_sign * (dogleg + landing_gap) * tdx,
|
||||||
|
text_loc.y - text_sign * (dogleg + landing_gap) * tdy,
|
||||||
|
text_loc.z,
|
||||||
|
];
|
||||||
|
|
||||||
|
let text_edge_pt = [
|
||||||
|
text_loc.x - text_sign * landing_gap * tdx,
|
||||||
|
text_loc.y - text_sign * landing_gap * tdy,
|
||||||
text_loc.z,
|
text_loc.z,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -225,13 +248,17 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
|
||||||
first = false;
|
first = false;
|
||||||
|
|
||||||
// Build the full control-point list: line.points + landing point
|
// Build the full control-point list: line.points + landing point
|
||||||
let mut ctrl: Vec<[f64; 3]> = line.points.iter().map(|p| p3(p)).collect();
|
let mut ctrl: Vec<[f64; 3]> =
|
||||||
let last_f = *ctrl.last().unwrap_or(&landing_pt);
|
line.points.iter().map(|p| p3(p)).collect();
|
||||||
let dist = ((last_f[0] - landing_pt[0]).powi(2)
|
|
||||||
+ (last_f[1] - landing_pt[1]).powi(2))
|
let last_f = *ctrl.last().unwrap_or(&elbow_pt);
|
||||||
|
|
||||||
|
let dist = ((last_f[0] - elbow_pt[0]).powi(2)
|
||||||
|
+ (last_f[1] - elbow_pt[1]).powi(2))
|
||||||
.sqrt();
|
.sqrt();
|
||||||
|
|
||||||
if dist > 1e-9 {
|
if dist > 1e-9 {
|
||||||
ctrl.push(landing_pt);
|
ctrl.push(elbow_pt);
|
||||||
}
|
}
|
||||||
for &c in &ctrl {
|
for &c in &ctrl {
|
||||||
key_verts.push(c);
|
key_verts.push(c);
|
||||||
|
|
@ -295,8 +322,8 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
|
||||||
// Horizontal landing from the leader end to the text's near edge.
|
// Horizontal landing from the leader end to the text's near edge.
|
||||||
if dogleg > 0.0 {
|
if dogleg > 0.0 {
|
||||||
points.push(nan);
|
points.push(nan);
|
||||||
points.push(landing_pt);
|
points.push(elbow_pt);
|
||||||
points.push([text_loc.x, text_loc.y, text_loc.z]);
|
points.push(text_edge_pt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -339,9 +366,7 @@ fn to_render(ml: &MultiLeader, document: &acadrust::CadDocument) -> Option<Rende
|
||||||
|
|
||||||
// ── Grips ──────────────────────────────────────────────────────────────────
|
// ── Grips ──────────────────────────────────────────────────────────────────
|
||||||
//
|
//
|
||||||
// IDs are assigned in two passes:
|
// Leader vertices come first, followed by optional elbow, text, and width grips.
|
||||||
// 0 .. total_line_pts - 1 : square grips on every LeaderLine vertex
|
|
||||||
// total_line_pts : diamond grip on context.text_location (if MText)
|
|
||||||
|
|
||||||
/// Side the text reads toward (+1 right / -1 left) and the text box's far
|
/// Side the text reads toward (+1 right / -1 left) and the text box's far
|
||||||
/// edge (the wrap-width grip position). The box width is the explicit wrap
|
/// edge (the wrap-width grip position). The box width is the explicit wrap
|
||||||
|
|
@ -392,6 +417,7 @@ fn text_box_geom(ml: &MultiLeader) -> ([f64; 2], [f64; 3]) {
|
||||||
oblique_angle: 0.0,
|
oblique_angle: 0.0,
|
||||||
is_backward: false,
|
is_backward: false,
|
||||||
is_upside_down: false,
|
is_upside_down: false,
|
||||||
|
is_vertical: false,
|
||||||
};
|
};
|
||||||
let layout = layout_mtext(&MTextRenderOpts {
|
let layout = layout_mtext(&MTextRenderOpts {
|
||||||
columns: Default::default(),
|
columns: Default::default(),
|
||||||
|
|
@ -404,6 +430,11 @@ fn text_box_geom(ml: &MultiLeader) -> ([f64; 2], [f64; 3]) {
|
||||||
attach_h_anchor: 0.0,
|
attach_h_anchor: 0.0,
|
||||||
v_anchor: MTextVAnchor::Top,
|
v_anchor: MTextVAnchor::Top,
|
||||||
line_spacing_factor: ctx.line_spacing_factor as f32,
|
line_spacing_factor: ctx.line_spacing_factor as f32,
|
||||||
|
exact_line_spacing: matches!(
|
||||||
|
ctx.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: 0.0,
|
||||||
vertical_text: vertical,
|
vertical_text: vertical,
|
||||||
want_glyph_boxes: false,
|
want_glyph_boxes: false,
|
||||||
});
|
});
|
||||||
|
|
@ -428,27 +459,131 @@ fn text_box_geom(ml: &MultiLeader) -> ([f64; 2], [f64; 3]) {
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
fn mleader_landing_geom(
|
||||||
|
ml: &MultiLeader,
|
||||||
|
) -> Option<(DVec3, DVec3, f64, f64, f64)> {
|
||||||
|
if ml.content_type != LeaderContentType::MText
|
||||||
|
|| !ml.enable_landing
|
||||||
|
|| !ml.enable_dogleg
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = DVec3::new(
|
||||||
|
ml.context.text_location.x,
|
||||||
|
ml.context.text_location.y,
|
||||||
|
ml.context.text_location.z,
|
||||||
|
);
|
||||||
|
|
||||||
|
let td = ml.context.text_direction;
|
||||||
|
|
||||||
|
let axis = {
|
||||||
|
let d = DVec3::new(td.x, td.y, td.z);
|
||||||
|
|
||||||
|
if d.length_squared() > 1.0e-18 {
|
||||||
|
d.normalize()
|
||||||
|
} else {
|
||||||
|
let a = ml.context.text_rotation;
|
||||||
|
|
||||||
|
DVec3::new(a.cos(), a.sin(), 0.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let leader_ref = ml
|
||||||
|
.context
|
||||||
|
.leader_roots
|
||||||
|
.first()
|
||||||
|
.and_then(|root| root.lines.first())
|
||||||
|
.and_then(|line| line.points.last())
|
||||||
|
.map(|p| DVec3::new(p.x, p.y, p.z))
|
||||||
|
.or_else(|| {
|
||||||
|
ml.context.leader_roots.first().map(|root| {
|
||||||
|
DVec3::new(
|
||||||
|
root.connection_point.x,
|
||||||
|
root.connection_point.y,
|
||||||
|
root.connection_point.z,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or(text);
|
||||||
|
|
||||||
|
let sign = if (text - leader_ref).dot(axis) >= 0.0 {
|
||||||
|
1.0
|
||||||
|
} else {
|
||||||
|
-1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let dogleg = ml
|
||||||
|
.context
|
||||||
|
.leader_roots
|
||||||
|
.first()
|
||||||
|
.map(|root| root.landing_distance.max(0.0))
|
||||||
|
.unwrap_or_else(|| ml.dogleg_length.max(0.0));
|
||||||
|
|
||||||
|
let gap = ml.context.landing_gap.max(0.0);
|
||||||
|
|
||||||
|
if dogleg <= 1.0e-9 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let elbow = text - axis * (sign * (dogleg + gap));
|
||||||
|
|
||||||
|
Some((elbow, axis, sign, dogleg, gap))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_mleader_text_location(ml: &mut MultiLeader, location: DVec3) {
|
||||||
|
let location = acadrust::types::Vector3::new(location.x, location.y, location.z);
|
||||||
|
ml.context.text_location = location;
|
||||||
|
ml.context.content_base_point = location;
|
||||||
|
}
|
||||||
|
|
||||||
fn grips(ml: &MultiLeader) -> Vec<GripDef> {
|
fn grips(ml: &MultiLeader) -> Vec<GripDef> {
|
||||||
let mut result: Vec<GripDef> = Vec::new();
|
let mut result = Vec::new();
|
||||||
let mut id = 0usize;
|
let mut id = 0usize;
|
||||||
|
|
||||||
for root in &ml.context.leader_roots {
|
for root in &ml.context.leader_roots {
|
||||||
for line in &root.lines {
|
for line in &root.lines {
|
||||||
for p in &line.points {
|
for p in &line.points {
|
||||||
result.push(square_grip(id, glam::DVec3::new(p.x, p.y, p.z)));
|
result.push(square_grip(
|
||||||
|
id,
|
||||||
|
DVec3::new(p.x, p.y, p.z),
|
||||||
|
));
|
||||||
|
|
||||||
id += 1;
|
id += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ml.content_type == LeaderContentType::MText {
|
if ml.content_type == LeaderContentType::MText {
|
||||||
|
if let Some((elbow, _, _, _, _)) = mleader_landing_geom(ml)
|
||||||
|
{
|
||||||
|
result.push(square_grip(
|
||||||
|
id,
|
||||||
|
elbow,
|
||||||
|
));
|
||||||
|
|
||||||
|
id += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let tl = &ml.context.text_location;
|
let tl = &ml.context.text_location;
|
||||||
// Text-location grip, then the wrap-width grip at the box's far edge.
|
|
||||||
result.push(center_grip(id, glam::DVec3::new(tl.x, tl.y, tl.z)));
|
result.push(center_grip(
|
||||||
|
id,
|
||||||
|
DVec3::new(tl.x, tl.y, tl.z),
|
||||||
|
));
|
||||||
|
|
||||||
id += 1;
|
id += 1;
|
||||||
|
|
||||||
let (_, far) = text_box_geom(ml);
|
let (_, far) = text_box_geom(ml);
|
||||||
result.push(triangle_grip(id, glam::DVec3::new(far[0], far[1], far[2])));
|
|
||||||
|
result.push(triangle_grip(
|
||||||
|
id,
|
||||||
|
DVec3::new(
|
||||||
|
far[0],
|
||||||
|
far[1],
|
||||||
|
far[2],
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
@ -458,16 +593,29 @@ fn grips(ml: &MultiLeader) -> Vec<GripDef> {
|
||||||
/// text grip's "Move with Leader" action so the leader follows the text.
|
/// text grip's "Move with Leader" action so the leader follows the text.
|
||||||
pub(crate) const MOVE_ALL_GRIP: usize = usize::MAX;
|
pub(crate) const MOVE_ALL_GRIP: usize = usize::MAX;
|
||||||
|
|
||||||
fn apply_grip(ml: &mut MultiLeader, grip_id: usize, apply: GripApply) {
|
fn apply_grip(
|
||||||
|
ml: &mut MultiLeader,
|
||||||
|
grip_id: usize,
|
||||||
|
apply: GripApply,
|
||||||
|
) {
|
||||||
if grip_id == MOVE_ALL_GRIP {
|
if grip_id == MOVE_ALL_GRIP {
|
||||||
let (dx, dy, dz) = match apply {
|
let (dx, dy, dz) = match apply {
|
||||||
GripApply::Translate(d) => (d.x as f64, d.y as f64, d.z as f64),
|
GripApply::Translate(d) => (
|
||||||
|
d.x as f64,
|
||||||
|
d.y as f64,
|
||||||
|
d.z as f64,
|
||||||
|
),
|
||||||
|
|
||||||
GripApply::Absolute(a) => (
|
GripApply::Absolute(a) => (
|
||||||
a.x as f64 - ml.context.text_location.x,
|
a.x as f64
|
||||||
a.y as f64 - ml.context.text_location.y,
|
- ml.context.text_location.x,
|
||||||
a.z as f64 - ml.context.text_location.z,
|
a.y as f64
|
||||||
|
- ml.context.text_location.y,
|
||||||
|
a.z as f64
|
||||||
|
- ml.context.text_location.z,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
for root in &mut ml.context.leader_roots {
|
for root in &mut ml.context.leader_roots {
|
||||||
for line in &mut root.lines {
|
for line in &mut root.lines {
|
||||||
for p in &mut line.points {
|
for p in &mut line.points {
|
||||||
|
|
@ -476,13 +624,21 @@ fn apply_grip(ml: &mut MultiLeader, grip_id: usize, apply: GripApply) {
|
||||||
p.z += dz;
|
p.z += dz;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
root.connection_point.x += dx;
|
root.connection_point.x += dx;
|
||||||
root.connection_point.y += dy;
|
root.connection_point.y += dy;
|
||||||
root.connection_point.z += dz;
|
root.connection_point.z += dz;
|
||||||
}
|
}
|
||||||
ml.context.text_location.x += dx;
|
|
||||||
ml.context.text_location.y += dy;
|
set_mleader_text_location(
|
||||||
ml.context.text_location.z += dz;
|
ml,
|
||||||
|
DVec3::new(
|
||||||
|
ml.context.text_location.x + dx,
|
||||||
|
ml.context.text_location.y + dy,
|
||||||
|
ml.context.text_location.z + dz,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -498,55 +654,130 @@ fn apply_grip(ml: &mut MultiLeader, grip_id: usize, apply: GripApply) {
|
||||||
p.y = a.y as f64;
|
p.y = a.y as f64;
|
||||||
p.z = a.z as f64;
|
p.z = a.z as f64;
|
||||||
}
|
}
|
||||||
|
|
||||||
GripApply::Translate(d) => {
|
GripApply::Translate(d) => {
|
||||||
p.x += d.x as f64;
|
p.x += d.x as f64;
|
||||||
p.y += d.y as f64;
|
p.y += d.y as f64;
|
||||||
p.z += d.z as f64;
|
p.z += d.z as f64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text-location grip (idx == n_vertices), then the wrap-width grip.
|
if ml.content_type != LeaderContentType::MText {
|
||||||
if ml.content_type == LeaderContentType::MText {
|
return;
|
||||||
if idx == grip_id {
|
}
|
||||||
let tl = &mut ml.context.text_location;
|
|
||||||
match apply {
|
let landing = mleader_landing_geom(ml);
|
||||||
GripApply::Absolute(a) => {
|
|
||||||
tl.x = a.x as f64;
|
if let Some((old_elbow, axis, old_sign, dogleg, gap)) = landing {
|
||||||
tl.y = a.y as f64;
|
if grip_id == idx {
|
||||||
tl.z = a.z as f64;
|
let target = match apply {
|
||||||
}
|
GripApply::Absolute(a) => DVec3::new(a.x as f64, a.y as f64, a.z as f64),
|
||||||
GripApply::Translate(d) => {
|
GripApply::Translate(d) => {
|
||||||
tl.x += d.x as f64;
|
old_elbow + DVec3::new(d.x as f64, d.y as f64, d.z as f64)
|
||||||
tl.y += d.y as f64;
|
|
||||||
tl.z += d.z as f64;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
let arrow = ml
|
||||||
|
.context
|
||||||
|
.leader_roots
|
||||||
|
.first()
|
||||||
|
.and_then(|root| root.lines.first())
|
||||||
|
.and_then(|line| line.points.first())
|
||||||
|
.map(|p| DVec3::new(p.x, p.y, p.z))
|
||||||
|
.unwrap_or(old_elbow);
|
||||||
|
let side_distance = (target - arrow).dot(axis);
|
||||||
|
let flip_tol = (dogleg * 0.10).max(1.0e-6);
|
||||||
|
let new_sign = if side_distance > flip_tol {
|
||||||
|
1.0
|
||||||
|
} else if side_distance < -flip_tol {
|
||||||
|
-1.0
|
||||||
|
} else {
|
||||||
|
old_sign
|
||||||
|
};
|
||||||
|
ml.context.text_attachment_point = if new_sign >= 0.0 {
|
||||||
|
acadrust::entities::multileader::TextAttachmentPointType::Left
|
||||||
|
} else {
|
||||||
|
acadrust::entities::multileader::TextAttachmentPointType::Right
|
||||||
|
};
|
||||||
|
set_mleader_text_location(ml, target + axis * (new_sign * (dogleg + gap)));
|
||||||
|
|
||||||
|
if let Some(root) = ml.context.leader_roots.first_mut() {
|
||||||
|
root.connection_point.x = target.x;
|
||||||
|
root.connection_point.y = target.y;
|
||||||
|
root.connection_point.z = target.z;
|
||||||
|
root.direction.x = axis.x * new_sign;
|
||||||
|
root.direction.y = axis.y * new_sign;
|
||||||
|
root.direction.z = axis.z * new_sign;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
idx += 1;
|
idx += 1;
|
||||||
if idx == grip_id {
|
}
|
||||||
// Dragging the box edge sets the MText wrap limit, projected on
|
|
||||||
// the box's flow axis (rotated baseline, or down the column for
|
if grip_id == idx {
|
||||||
// vertical flow).
|
let old_end = DVec3::new(
|
||||||
let (dir, far) = text_box_geom(ml);
|
ml.context.text_location.x,
|
||||||
let (nx, ny) = match apply {
|
ml.context.text_location.y,
|
||||||
GripApply::Absolute(a) => (a.x as f64, a.y as f64),
|
ml.context.text_location.z,
|
||||||
GripApply::Translate(d) => (far[0] + d.x as f64, far[1] + d.y as f64),
|
);
|
||||||
};
|
let target = match apply {
|
||||||
let tl = &ml.context.text_location;
|
GripApply::Absolute(a) => DVec3::new(a.x as f64, a.y as f64, a.z as f64),
|
||||||
// `dir` carries the attachment-side factor (may be ±half), so
|
GripApply::Translate(d) => {
|
||||||
// normalise by its squared length: width = proj(flow)/k.
|
old_end + DVec3::new(d.x as f64, d.y as f64, d.z as f64)
|
||||||
let d2 = (dir[0] * dir[0] + dir[1] * dir[1]).max(1e-12);
|
}
|
||||||
let proj = ((nx - tl.x) * dir[0] + (ny - tl.y) * dir[1]) / d2;
|
};
|
||||||
let min_w = ml.text_height.max(1.0) * 0.5;
|
|
||||||
ml.context.text_width = proj.max(min_w);
|
if let Some((elbow, axis, sign, _, gap)) = landing {
|
||||||
|
let requested_total = (target - elbow).dot(axis) * sign;
|
||||||
|
let new_length = (requested_total - gap).max(1.0e-6);
|
||||||
|
set_mleader_text_location(
|
||||||
|
ml,
|
||||||
|
elbow + axis * (sign * (new_length + gap)),
|
||||||
|
);
|
||||||
|
ml.dogleg_length = new_length;
|
||||||
|
for root in &mut ml.context.leader_roots {
|
||||||
|
root.landing_distance = new_length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
set_mleader_text_location(ml, target);
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
idx += 1;
|
||||||
|
|
||||||
|
if grip_id == idx {
|
||||||
|
let (dir, far) = text_box_geom(ml);
|
||||||
|
|
||||||
|
let (nx, ny) = match apply {
|
||||||
|
GripApply::Absolute(a) => (
|
||||||
|
a.x as f64,
|
||||||
|
a.y as f64,
|
||||||
|
),
|
||||||
|
|
||||||
|
GripApply::Translate(d) => (
|
||||||
|
far[0] + d.x as f64,
|
||||||
|
far[1] + d.y as f64,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
let tl = &ml.context.text_location;
|
||||||
|
|
||||||
|
let d2 = (dir[0] * dir[0] + dir[1] * dir[1]).max(1.0e-12);
|
||||||
|
|
||||||
|
let proj = ((nx - tl.x) * dir[0] + (ny - tl.y) * dir[1]) / d2;
|
||||||
|
|
||||||
|
let min_w = ml.text_height.max(1.0) * 0.5;
|
||||||
|
|
||||||
|
ml.context.text_width = proj.max(min_w);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -842,16 +1073,19 @@ fn apply_geom_prop(ml: &mut MultiLeader, field: &str, value: &str) {
|
||||||
"text_x" => {
|
"text_x" => {
|
||||||
if let Some(v) = f64(value) {
|
if let Some(v) = f64(value) {
|
||||||
ml.context.text_location.x = v;
|
ml.context.text_location.x = v;
|
||||||
|
ml.context.content_base_point.x = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"text_y" => {
|
"text_y" => {
|
||||||
if let Some(v) = f64(value) {
|
if let Some(v) = f64(value) {
|
||||||
ml.context.text_location.y = v;
|
ml.context.text_location.y = v;
|
||||||
|
ml.context.content_base_point.y = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"text_z" => {
|
"text_z" => {
|
||||||
if let Some(v) = f64(value) {
|
if let Some(v) = f64(value) {
|
||||||
ml.context.text_location.z = v;
|
ml.context.text_location.z = v;
|
||||||
|
ml.context.content_base_point.z = v;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"text_frame" => {
|
"text_frame" => {
|
||||||
|
|
@ -1090,6 +1324,7 @@ fn apply_transform(ml: &mut MultiLeader, t: &EntityTransform) {
|
||||||
p1,
|
p1,
|
||||||
p2,
|
p2,
|
||||||
);
|
);
|
||||||
|
entity.context.content_base_point = entity.context.text_location;
|
||||||
reflect_xy_direction(
|
reflect_xy_direction(
|
||||||
&mut entity.context.text_direction.x,
|
&mut entity.context.text_direction.x,
|
||||||
&mut entity.context.text_direction.y,
|
&mut entity.context.text_direction.y,
|
||||||
|
|
@ -1133,53 +1368,83 @@ impl crate::entities::traits::Grippable for MultiLeader {
|
||||||
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
fn apply_grip(&mut self, grip_id: usize, apply: GripApply) {
|
||||||
apply_grip(self, grip_id, apply);
|
apply_grip(self, grip_id, apply);
|
||||||
}
|
}
|
||||||
fn grip_menu(&self, grip_id: usize) -> Vec<crate::scene::model::object::GripMenuItem> {
|
fn grip_menu(
|
||||||
use crate::scene::model::object::{GripMenuAction, GripMenuItem};
|
&self,
|
||||||
|
grip_id: usize,
|
||||||
|
) -> Vec<crate::scene::model::object::GripMenuItem> {
|
||||||
|
use crate::scene::model::object::{
|
||||||
|
GripMenuAction,
|
||||||
|
GripMenuItem,
|
||||||
|
};
|
||||||
|
|
||||||
let n_vertices: usize = self
|
let n_vertices: usize = self
|
||||||
.context
|
.context
|
||||||
.leader_roots
|
.leader_roots
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|r| r.lines.iter())
|
.flat_map(|root| root.lines.iter())
|
||||||
.map(|l| l.points.len())
|
.map(|line| line.points.len())
|
||||||
.sum();
|
.sum();
|
||||||
if self.content_type == LeaderContentType::MText && grip_id >= n_vertices {
|
|
||||||
if grip_id == n_vertices {
|
if self.content_type
|
||||||
// Text-location grip.
|
== LeaderContentType::MText
|
||||||
vec![
|
{
|
||||||
|
let has_elbow =
|
||||||
|
mleader_landing_geom(self).is_some();
|
||||||
|
|
||||||
|
let elbow_id =
|
||||||
|
n_vertices;
|
||||||
|
|
||||||
|
let landing_id =
|
||||||
|
n_vertices
|
||||||
|
+ usize::from(has_elbow);
|
||||||
|
|
||||||
|
let width_id =
|
||||||
|
landing_id + 1;
|
||||||
|
|
||||||
|
if has_elbow
|
||||||
|
&& grip_id == elbow_id
|
||||||
|
{
|
||||||
|
return vec![
|
||||||
GripMenuItem {
|
GripMenuItem {
|
||||||
label: "Stretch",
|
label: "Stretch",
|
||||||
action: GripMenuAction::Stretch,
|
action:
|
||||||
|
GripMenuAction::Stretch,
|
||||||
},
|
},
|
||||||
GripMenuItem {
|
];
|
||||||
label: "Move with Leader",
|
}
|
||||||
action: GripMenuAction::MoveWithLeader,
|
|
||||||
},
|
if grip_id == landing_id {
|
||||||
GripMenuItem {
|
return vec![
|
||||||
label: "Move Independent",
|
GripMenuItem {
|
||||||
action: GripMenuAction::MoveIndependent,
|
label: "Stretch",
|
||||||
},
|
action:
|
||||||
]
|
GripMenuAction::Stretch,
|
||||||
} else {
|
},
|
||||||
// Wrap-width grip: drag only.
|
];
|
||||||
Vec::new()
|
}
|
||||||
|
|
||||||
|
if grip_id == width_id {
|
||||||
|
return Vec::new();
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
// Leader-line vertex.
|
|
||||||
vec![
|
|
||||||
GripMenuItem {
|
|
||||||
label: "Stretch",
|
|
||||||
action: GripMenuAction::Stretch,
|
|
||||||
},
|
|
||||||
GripMenuItem {
|
|
||||||
label: "Add Leader",
|
|
||||||
action: GripMenuAction::AddLeader,
|
|
||||||
},
|
|
||||||
GripMenuItem {
|
|
||||||
label: "Remove Leader",
|
|
||||||
action: GripMenuAction::RemoveLeader,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vec![
|
||||||
|
GripMenuItem {
|
||||||
|
label: "Stretch",
|
||||||
|
action:
|
||||||
|
GripMenuAction::Stretch,
|
||||||
|
},
|
||||||
|
GripMenuItem {
|
||||||
|
label: "Add Leader",
|
||||||
|
action:
|
||||||
|
GripMenuAction::AddLeader,
|
||||||
|
},
|
||||||
|
GripMenuItem {
|
||||||
|
label: "Remove Leader",
|
||||||
|
action:
|
||||||
|
GripMenuAction::RemoveLeader,
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
fn apply_grip_menu(&mut self, grip_id: usize, action: crate::scene::model::object::GripMenuAction) {
|
||||||
use crate::scene::model::object::GripMenuAction as A;
|
use crate::scene::model::object::GripMenuAction as A;
|
||||||
|
|
@ -1323,16 +1588,40 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
|
|
||||||
// ── Scaling ──────────────────────────────────────────────────────────────
|
// ── Scaling ──────────────────────────────────────────────────────────────
|
||||||
// Used only when a context omits an already-resolved content size.
|
// Used only when a context omits an already-resolved content size.
|
||||||
let fallback_content_scale = (ml.scale_factor as f32)
|
let annotative =
|
||||||
* if crate::scene::annotative::mleader_is_annotative(document, ml) {
|
crate::scene::annotative::mleader_is_annotative(document, ml);
|
||||||
anno_scale
|
|
||||||
|
// Context sizes are resolved at their stored annotation scale.
|
||||||
|
let base_scale = if ml.scale_factor.abs() > 1.0e-12 {
|
||||||
|
ml.scale_factor as f32
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
|
|
||||||
|
let stored_context_scale = ml.context.scale_factor as f32;
|
||||||
|
|
||||||
|
let context_scale_correction =
|
||||||
|
if annotative && stored_context_scale.abs() > 1.0e-12 {
|
||||||
|
let correction =
|
||||||
|
(base_scale * anno_scale) / stored_context_scale;
|
||||||
|
|
||||||
|
if correction.is_finite() && correction > 0.0 {
|
||||||
|
correction
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
1.0
|
1.0
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let fallback_content_scale =
|
||||||
|
base_scale * if annotative { anno_scale } else { 1.0 };
|
||||||
|
|
||||||
// The active context stores the resolved world-space arrow size.
|
// The active context stores the resolved world-space arrow size.
|
||||||
// Reapplying the entity scale here makes context-sized arrows grow twice.
|
// Reapplying the entity scale here makes context-sized arrows grow twice.
|
||||||
let arrow_size = ml.context.arrowhead_size as f32;
|
let arrow_size =
|
||||||
|
ml.context.arrowhead_size as f32
|
||||||
|
* context_scale_correction;
|
||||||
let draw_arrow = arrow_size > 0.0;
|
let draw_arrow = arrow_size > 0.0;
|
||||||
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
let invisible = ml.path_type == MultiLeaderPathType::Invisible;
|
||||||
// arrowhead_handle resolves through the block records to a named arrow
|
// arrowhead_handle resolves through the block records to a named arrow
|
||||||
|
|
@ -1357,7 +1646,25 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
|
|
||||||
// Which side the text grip sits on, recomputed every frame so the text
|
// Which side the text grip sits on, recomputed every frame so the text
|
||||||
// alignment and the landing mirror live when the arrow or text moves.
|
// alignment and the landing mirror live when the arrow or text moves.
|
||||||
let text_loc_w = ml.context.text_location;
|
let mut text_loc_w = ml.context.text_location;
|
||||||
|
|
||||||
|
// Scale the annotation-side offset around the fixed elbow.
|
||||||
|
if annotative
|
||||||
|
&& (context_scale_correction - 1.0).abs() > 1.0e-6
|
||||||
|
{
|
||||||
|
if let Some(root) = ml.context.leader_roots.first() {
|
||||||
|
let anchor = root.connection_point;
|
||||||
|
let k = context_scale_correction as f64;
|
||||||
|
|
||||||
|
text_loc_w = acadrust::types::Vector3::new(
|
||||||
|
anchor.x
|
||||||
|
+ (text_loc_w.x - anchor.x) * k,
|
||||||
|
anchor.y
|
||||||
|
+ (text_loc_w.y - anchor.y) * k,
|
||||||
|
text_loc_w.z,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
let leader_ref_w = ml
|
let leader_ref_w = ml
|
||||||
.context
|
.context
|
||||||
.leader_roots
|
.leader_roots
|
||||||
|
|
@ -1471,7 +1778,9 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
// stray line up the side of the text.
|
// stray line up the side of the text.
|
||||||
// Landing distance belongs to the selected leader-root context
|
// Landing distance belongs to the selected leader-root context
|
||||||
// and is already resolved in world units.
|
// and is already resolved in world units.
|
||||||
let d = root.landing_distance;
|
let d =
|
||||||
|
root.landing_distance
|
||||||
|
* context_scale_correction as f64;
|
||||||
// The dogleg runs along the leader root's stored direction —
|
// The dogleg runs along the leader root's stored direction —
|
||||||
// for a rotated leader that is the angled baseline, not world
|
// for a rotated leader that is the angled baseline, not world
|
||||||
// X. Roots without a usable direction keep the legacy
|
// X. Roots without a usable direction keep the legacy
|
||||||
|
|
@ -1505,6 +1814,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
wires.push(WireModel {
|
wires.push(WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
@ -1633,11 +1943,13 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
// scale_factor + annotation scale applied.
|
// scale_factor + annotation scale applied.
|
||||||
let height = if ctx.text_height > 0.0 {
|
let height = if ctx.text_height > 0.0 {
|
||||||
ctx.text_height as f32
|
ctx.text_height as f32
|
||||||
|
* context_scale_correction
|
||||||
} else {
|
} else {
|
||||||
ml.text_height as f32 * fallback_content_scale
|
ml.text_height as f32
|
||||||
|
* fallback_content_scale
|
||||||
};
|
};
|
||||||
|
|
||||||
let ins = &ctx.text_location;
|
let ins = &text_loc_w;
|
||||||
// Subtract world_offset in f64 before casting to f32: drawings often
|
// Subtract world_offset in f64 before casting to f32: drawings often
|
||||||
// sit at large absolute coordinates and casting first then subtracting
|
// sit at large absolute coordinates and casting first then subtracting
|
||||||
// throws away the precision needed for the rotated sub-glyph offsets.
|
// throws away the precision needed for the rotated sub-glyph offsets.
|
||||||
|
|
@ -1730,12 +2042,19 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
value: &ctx.text_string,
|
value: &ctx.text_string,
|
||||||
insertion: [local_ins_x as f64, local_ins_y as f64, z as f64],
|
insertion: [local_ins_x as f64, local_ins_y as f64, z as f64],
|
||||||
height,
|
height,
|
||||||
rect_w: ctx.text_width as f32,
|
rect_w:
|
||||||
|
ctx.text_width as f32
|
||||||
|
* context_scale_correction,
|
||||||
rotation: rot,
|
rotation: rot,
|
||||||
style: &style,
|
style: &style,
|
||||||
attach_h_anchor: h_anchor,
|
attach_h_anchor: h_anchor,
|
||||||
v_anchor,
|
v_anchor,
|
||||||
line_spacing_factor: ctx.line_spacing_factor as f32,
|
line_spacing_factor: ctx.line_spacing_factor as f32,
|
||||||
|
exact_line_spacing: matches!(
|
||||||
|
ctx.line_spacing_style,
|
||||||
|
acadrust::entities::LineSpacingStyle::Exactly
|
||||||
|
),
|
||||||
|
rectangle_height: 0.0,
|
||||||
// Vertical flow (top-to-bottom) is stored per-context.
|
// Vertical flow (top-to-bottom) is stored per-context.
|
||||||
vertical_text: matches!(
|
vertical_text: matches!(
|
||||||
ctx.text_flow_direction,
|
ctx.text_flow_direction,
|
||||||
|
|
@ -1845,6 +2164,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
wires.push(WireModel {
|
wires.push(WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
@ -1918,6 +2238,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
wires.push(WireModel {
|
wires.push(WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
@ -2006,6 +2327,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
wires.push(WireModel {
|
wires.push(WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
@ -2049,6 +2371,7 @@ impl MultiLeaderTess for MultiLeader {
|
||||||
wires.push(WireModel {
|
wires.push(WireModel {
|
||||||
point_marker: None,
|
point_marker: None,
|
||||||
taper_widths: Vec::new(),
|
taper_widths: Vec::new(),
|
||||||
|
pattern_stations: Vec::new(),
|
||||||
world_width: 0.0,
|
world_width: 0.0,
|
||||||
depth_override: None,
|
depth_override: None,
|
||||||
display_visible: true,
|
display_visible: true,
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ pub fn drawn_vertices2d(
|
||||||
(kept.len() >= 2).then_some(kept)
|
(kept.len() >= 2).then_some(kept)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
fn tessellate_polyline2d(pl: &Polyline2D, fill_mode: bool) -> RenderEntity {
|
||||||
let filtered = drawn_vertices2d(pl);
|
let filtered = drawn_vertices2d(pl);
|
||||||
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
||||||
if verts.is_empty() {
|
if verts.is_empty() {
|
||||||
|
|
@ -224,6 +224,47 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||||
[wx, wy, wz]
|
[wx, wy, wz]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if !fill_mode {
|
||||||
|
let continuous = pl.flags.bits()
|
||||||
|
& acadrust::entities::PolylineFlags::LINETYPE_CONTINUOUS.bits()
|
||||||
|
!= 0;
|
||||||
|
let mut boundary = crate::entities::common::wide_band_outline(
|
||||||
|
&band_verts_2d(pl),
|
||||||
|
pl.is_closed(),
|
||||||
|
!continuous,
|
||||||
|
&to_wcs,
|
||||||
|
);
|
||||||
|
if !boundary.points.is_empty() {
|
||||||
|
if pl.thickness.abs() > 1e-10 {
|
||||||
|
boundary = crate::entities::common::extrude_wide_band_outline(
|
||||||
|
boundary,
|
||||||
|
[
|
||||||
|
pl.thickness * normal.0,
|
||||||
|
pl.thickness * normal.1,
|
||||||
|
pl.thickness * normal.2,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let (tangent_geoms, key_vertices) =
|
||||||
|
centerline_metadata_2d(verts, pl.is_closed(), &to_wcs);
|
||||||
|
return RenderEntity {
|
||||||
|
pick_tris: Vec::new(),
|
||||||
|
object: RenderObject::BoundaryLines {
|
||||||
|
points: boundary.points,
|
||||||
|
stations: boundary.stations,
|
||||||
|
point_segments: boundary.point_segments,
|
||||||
|
station_pieces: boundary.station_pieces,
|
||||||
|
source_length: boundary.source_length,
|
||||||
|
plinegen: continuous,
|
||||||
|
},
|
||||||
|
snap_pts: vec![],
|
||||||
|
tangent_geoms,
|
||||||
|
key_vertices,
|
||||||
|
fill_tris: vec![],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if pl.thickness.abs() > 1e-10 {
|
if pl.thickness.abs() > 1e-10 {
|
||||||
let (nx, ny, nz) = normal;
|
let (nx, ny, nz) = normal;
|
||||||
let t = pl.thickness;
|
let t = pl.thickness;
|
||||||
|
|
@ -348,13 +389,14 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||||
key_verts.push([p1[0], p1[1], p1[2]]);
|
key_verts.push([p1[0], p1[1], p1[2]]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let band_verts = band_verts_2d(pl);
|
||||||
let (fill_origin, fills) = wide_fills(pl);
|
let (fill_origin, fills) = wide_fills(pl);
|
||||||
// A wide Polyline2D whose per-vertex widths VARY renders a smooth taper; a
|
// A wide Polyline2D whose per-vertex widths VARY renders a smooth taper; a
|
||||||
// uniform-width one keeps the constant-band Contour.
|
// uniform-width one keeps the constant-band Contour.
|
||||||
let object = match tapered_band_verts_2d(pl) {
|
let object = match tapered_band_verts_2d(&band_verts) {
|
||||||
Some(band_verts) => {
|
Some(band_verts) => {
|
||||||
let (pts, widths) = crate::entities::common::tapered_band_points(
|
let (pts, widths) = crate::entities::common::tapered_band_points(
|
||||||
&band_verts,
|
band_verts,
|
||||||
pl.is_closed(),
|
pl.is_closed(),
|
||||||
&to_wcs,
|
&to_wcs,
|
||||||
);
|
);
|
||||||
|
|
@ -376,16 +418,15 @@ fn tessellate_polyline2d(pl: &Polyline2D) -> RenderEntity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Per-vertex `(location, bulge, start_width, end_width)` band description for a
|
/// Effective segment widths for a Polyline2D band.
|
||||||
/// wide Polyline2D whose width VARIES — `None` when the width is uniform.
|
fn band_verts_2d(
|
||||||
fn tapered_band_verts_2d(
|
|
||||||
pl: &acadrust::entities::Polyline2D,
|
pl: &acadrust::entities::Polyline2D,
|
||||||
) -> Option<Vec<([f64; 2], f64, f64, f64)>> {
|
) -> Vec<([f64; 2], f64, f64, f64)> {
|
||||||
let default_start = pl.start_width;
|
let default_start = pl.start_width;
|
||||||
let default_end = pl.end_width;
|
let default_end = pl.end_width;
|
||||||
let filtered = drawn_vertices2d(pl);
|
let filtered = drawn_vertices2d(pl);
|
||||||
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
let verts: &[acadrust::entities::Vertex2D] = filtered.as_deref().unwrap_or(&pl.vertices);
|
||||||
let band: Vec<([f64; 2], f64, f64, f64)> = verts
|
verts
|
||||||
.iter()
|
.iter()
|
||||||
.map(|v| {
|
.map(|v| {
|
||||||
let sw = if v.start_width > 1e-9 {
|
let sw = if v.start_width > 1e-9 {
|
||||||
|
|
@ -400,7 +441,12 @@ fn tapered_band_verts_2d(
|
||||||
};
|
};
|
||||||
([v.location.x, v.location.y], v.bulge, sw, ew)
|
([v.location.x, v.location.y], v.bulge, sw, ew)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tapered_band_verts_2d(
|
||||||
|
band: &[([f64; 2], f64, f64, f64)],
|
||||||
|
) -> Option<&[([f64; 2], f64, f64, f64)]> {
|
||||||
let w0 = band.first().map_or(0.0, |v| v.2);
|
let w0 = band.first().map_or(0.0, |v| v.2);
|
||||||
let varies = band
|
let varies = band
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -412,9 +458,51 @@ fn tapered_band_verts_2d(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn centerline_metadata_2d(
|
||||||
|
verts: &[acadrust::entities::Vertex2D],
|
||||||
|
closed: bool,
|
||||||
|
to_wcs: &dyn Fn(f64, f64) -> (f64, f64, f64),
|
||||||
|
) -> (Vec<TangentGeom>, Vec<[f64; 3]>) {
|
||||||
|
let count = verts.len();
|
||||||
|
let segment_count = if closed {
|
||||||
|
count
|
||||||
|
} else {
|
||||||
|
count.saturating_sub(1)
|
||||||
|
};
|
||||||
|
let mut tangents = Vec::with_capacity(segment_count);
|
||||||
|
let mut key_vertices = Vec::with_capacity(segment_count + 1);
|
||||||
|
for index in 0..segment_count {
|
||||||
|
let start = &verts[index];
|
||||||
|
let end = &verts[(index + 1) % count];
|
||||||
|
let p0 = to_wcs(start.location.x, start.location.y);
|
||||||
|
let p1 = to_wcs(end.location.x, end.location.y);
|
||||||
|
if start.bulge.abs() < 1e-9 {
|
||||||
|
tangents.push(TangentGeom::Line {
|
||||||
|
p1: [p0.0 as f32, p0.1 as f32, p0.2 as f32],
|
||||||
|
p2: [p1.0 as f32, p1.1 as f32, p1.2 as f32],
|
||||||
|
});
|
||||||
|
} else if let Some(arc) = crate::entities::common::BulgeArc::from_bulge(
|
||||||
|
[start.location.x, start.location.y],
|
||||||
|
[end.location.x, end.location.y],
|
||||||
|
start.bulge,
|
||||||
|
) {
|
||||||
|
let center = to_wcs(arc.center[0], arc.center[1]);
|
||||||
|
tangents.push(TangentGeom::Circle {
|
||||||
|
center: [center.0 as f32, center.1 as f32, center.2 as f32],
|
||||||
|
radius: arc.radius as f32,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if index == 0 {
|
||||||
|
key_vertices.push([p0.0, p0.1, p0.2]);
|
||||||
|
}
|
||||||
|
key_vertices.push([p1.0, p1.1, p1.2]);
|
||||||
|
}
|
||||||
|
(tangents, key_vertices)
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderConvertible for Polyline2D {
|
impl RenderConvertible for Polyline2D {
|
||||||
fn to_render(&self, _document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||||
Some(tessellate_polyline2d(self))
|
Some(tessellate_polyline2d(self, document.header.fill_mode))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ impl RenderConvertible for RasterImage {
|
||||||
origin: ins,
|
origin: ins,
|
||||||
color: None,
|
color: None,
|
||||||
fill_tris: vec![],
|
fill_tris: vec![],
|
||||||
|
plane: None,
|
||||||
run: None,
|
run: None,
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
|
@ -218,6 +219,7 @@ impl RenderConvertible for RasterImage {
|
||||||
origin,
|
origin,
|
||||||
color: None,
|
color: None,
|
||||||
fill_tris,
|
fill_tris,
|
||||||
|
plane: None,
|
||||||
run: Some(GlyphRun {
|
run: Some(GlyphRun {
|
||||||
text: path.to_string(),
|
text: path.to_string(),
|
||||||
font,
|
font,
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,24 @@ pub(crate) fn sync_text_alignment_point(t: &mut Text) {
|
||||||
(HA::Left, VA::Baseline)
|
(HA::Left, VA::Baseline)
|
||||||
);
|
);
|
||||||
if needs_alignment_point {
|
if needs_alignment_point {
|
||||||
if t.alignment_point.is_none() {
|
if matches!(t.horizontal_alignment, HA::Aligned | HA::Fit) {
|
||||||
|
let point = t.alignment_point.unwrap_or(t.insertion_point);
|
||||||
|
let dx = point.x - t.insertion_point.x;
|
||||||
|
let dy = point.y - t.insertion_point.y;
|
||||||
|
if dx.hypot(dy) <= 1.0e-9 {
|
||||||
|
let span = t.height.max(1.0e-6)
|
||||||
|
* t.width_factor.abs().max(0.01)
|
||||||
|
* t.value.chars().count().max(1) as f64
|
||||||
|
* 0.6;
|
||||||
|
t.alignment_point = Some(acadrust::types::Vector3::new(
|
||||||
|
t.insertion_point.x + t.rotation.cos() * span,
|
||||||
|
t.insertion_point.y + t.rotation.sin() * span,
|
||||||
|
t.insertion_point.z,
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
t.alignment_point = Some(point);
|
||||||
|
}
|
||||||
|
} else if t.alignment_point.is_none() {
|
||||||
t.alignment_point = Some(t.insertion_point);
|
t.alignment_point = Some(t.insertion_point);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -51,6 +68,21 @@ pub(crate) fn sync_text_alignment_point(t: &mut Text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn two_point_span(t: &Text) -> Option<(f64, f64)> {
|
||||||
|
if !matches!(t.horizontal_alignment, HA::Aligned | HA::Fit) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let point = t.alignment_point?;
|
||||||
|
let dx = point.x - t.insertion_point.x;
|
||||||
|
let dy = point.y - t.insertion_point.y;
|
||||||
|
let distance = dx.hypot(dy);
|
||||||
|
(distance > 1.0e-9).then(|| (distance, dy.atan2(dx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn displayed_rotation(t: &Text) -> f64 {
|
||||||
|
two_point_span(t).map_or(t.rotation, |(_, angle)| angle)
|
||||||
|
}
|
||||||
|
|
||||||
/// Resolved placement of a TEXT run: the baseline-anchored run origin (WCS xy)
|
/// Resolved placement of a TEXT run: the baseline-anchored run origin (WCS xy)
|
||||||
/// plus every parameter needed to lay the glyphs out. Shared by `to_render` (the
|
/// plus every parameter needed to lay the glyphs out. Shared by `to_render` (the
|
||||||
/// stroke path) and the SDF-quad text collector so both place text identically.
|
/// stroke path) and the SDF-quad text collector so both place text identically.
|
||||||
|
|
@ -98,8 +130,12 @@ pub(crate) fn acad_text_encode(value: &str) -> String {
|
||||||
out
|
out
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_render(t: &Text, document: &acadrust::CadDocument) -> RenderEntity {
|
pub(crate) fn to_render_at_scale(
|
||||||
let p = text_run_placement(t, document);
|
t: &Text,
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
annotation_scale: f32,
|
||||||
|
) -> RenderEntity {
|
||||||
|
let p = text_run_placement_at_scale(t, document, annotation_scale);
|
||||||
let snap_pt = glam::DVec3::new(p.wcs_insertion[0], p.wcs_insertion[1], p.wcs_insertion[2]);
|
let snap_pt = glam::DVec3::new(p.wcs_insertion[0], p.wcs_insertion[1], p.wcs_insertion[2]);
|
||||||
// Parse `%%` codes via acadrust, re-encoded for the stroke tessellator.
|
// Parse `%%` codes via acadrust, re-encoded for the stroke tessellator.
|
||||||
let value = acad_text_encode(&p.value);
|
let value = acad_text_encode(&p.value);
|
||||||
|
|
@ -120,6 +156,7 @@ fn to_render(t: &Text, document: &acadrust::CadDocument) -> RenderEntity {
|
||||||
origin: p.origin,
|
origin: p.origin,
|
||||||
color: None,
|
color: None,
|
||||||
fill_tris,
|
fill_tris,
|
||||||
|
plane: None,
|
||||||
run: Some(GlyphRun {
|
run: Some(GlyphRun {
|
||||||
text: value,
|
text: value,
|
||||||
font: p.font.clone(),
|
font: p.font.clone(),
|
||||||
|
|
@ -140,7 +177,16 @@ fn to_render(t: &Text, document: &acadrust::CadDocument) -> RenderEntity {
|
||||||
|
|
||||||
/// Compute a TEXT entity's run placement (origin + layout params). Extracted
|
/// Compute a TEXT entity's run placement (origin + layout params). Extracted
|
||||||
/// from `to_render` verbatim so the stroke and SDF-quad paths agree exactly.
|
/// from `to_render` verbatim so the stroke and SDF-quad paths agree exactly.
|
||||||
pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPlacement {
|
pub fn text_run_placement_at_scale(
|
||||||
|
t: &Text,
|
||||||
|
document: &acadrust::CadDocument,
|
||||||
|
annotation_scale: f32,
|
||||||
|
) -> TextPlacement {
|
||||||
|
let annotation_scale = if annotation_scale.is_finite() && annotation_scale > 1.0e-9 {
|
||||||
|
annotation_scale
|
||||||
|
} else {
|
||||||
|
1.0
|
||||||
|
};
|
||||||
let normal = (t.normal.x, t.normal.y, t.normal.z);
|
let normal = (t.normal.x, t.normal.y, t.normal.z);
|
||||||
let (wsx, wsy, wsz) = crate::scene::view::transform::ocs_point_to_wcs(
|
let (wsx, wsy, wsz) = crate::scene::view::transform::ocs_point_to_wcs(
|
||||||
(
|
(
|
||||||
|
|
@ -152,10 +198,9 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
);
|
);
|
||||||
let resolved_style = resolve_text_style(&t.style, document);
|
let resolved_style = resolve_text_style(&t.style, document);
|
||||||
let font_name = resolved_style.font_name;
|
let font_name = resolved_style.font_name;
|
||||||
// AutoCAD text geometry rule: the entity stores the FINAL width factor /
|
// The entity stores the final width factor and oblique angle copied from
|
||||||
// oblique angle, copied from the style at creation and persisting through
|
// its style at creation. Only fall back to the style when an omitted field
|
||||||
// style edits. Use it as-is. Only fall back to the style when the entity
|
// was read as zero.
|
||||||
// value is missing (the parser reports 0.0 for default-omitted fields).
|
|
||||||
let base_wf = if t.width_factor.abs() > 1e-9 {
|
let base_wf = if t.width_factor.abs() > 1e-9 {
|
||||||
(t.width_factor as f32).clamp(0.01, 100.0)
|
(t.width_factor as f32).clamp(0.01, 100.0)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -168,17 +213,44 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
// mirror, and XOR keeps a double mirror an involution.
|
// mirror, and XOR keeps a double mirror an involution.
|
||||||
let eff_backward = resolved_style.is_backward ^ (t.generation_flags & 0x2 != 0);
|
let eff_backward = resolved_style.is_backward ^ (t.generation_flags & 0x2 != 0);
|
||||||
let eff_upside = resolved_style.is_upside_down ^ (t.generation_flags & 0x4 != 0);
|
let eff_upside = resolved_style.is_upside_down ^ (t.generation_flags & 0x4 != 0);
|
||||||
let width_factor = if eff_backward { -base_wf } else { base_wf };
|
let mut width_factor = if eff_backward { -base_wf } else { base_wf };
|
||||||
let rotation = if eff_upside {
|
|
||||||
t.rotation as f32 + std::f32::consts::PI
|
|
||||||
} else {
|
|
||||||
t.rotation as f32
|
|
||||||
};
|
|
||||||
let oblique_angle = if t.oblique_angle.abs() > 1e-9 {
|
let oblique_angle = if t.oblique_angle.abs() > 1e-9 {
|
||||||
t.oblique_angle as f32
|
t.oblique_angle as f32
|
||||||
} else {
|
} else {
|
||||||
resolved_style.oblique_angle
|
resolved_style.oblique_angle
|
||||||
};
|
};
|
||||||
|
let value_for_bounds = resolve_dxf_special_chars(&t.value);
|
||||||
|
let mut height = t.height.max(1.0e-9) as f32;
|
||||||
|
let mut base_rotation = t.rotation as f32;
|
||||||
|
|
||||||
|
// Aligned and Fit are true two-point modes. Both derive their baseline
|
||||||
|
// direction from the endpoints. Aligned scales height uniformly; Fit keeps
|
||||||
|
// the height and changes only the horizontal factor.
|
||||||
|
if let Some((span, angle)) = two_point_span(t) {
|
||||||
|
base_rotation = angle as f32;
|
||||||
|
if let Some(base_bounds) = text_local_bounds(
|
||||||
|
&font_name,
|
||||||
|
&value_for_bounds,
|
||||||
|
height,
|
||||||
|
width_factor,
|
||||||
|
oblique_angle,
|
||||||
|
) {
|
||||||
|
if base_bounds.advance > 1.0e-6 {
|
||||||
|
let scale =
|
||||||
|
(span as f32 / annotation_scale / base_bounds.advance).max(1.0e-6);
|
||||||
|
if matches!(t.horizontal_alignment, HA::Aligned) {
|
||||||
|
height *= scale;
|
||||||
|
} else {
|
||||||
|
width_factor *= scale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rotation = if eff_upside {
|
||||||
|
base_rotation + std::f32::consts::PI
|
||||||
|
} else {
|
||||||
|
base_rotation
|
||||||
|
};
|
||||||
// Anchor stays f64: large coordinates (UTM etc.) lose ~0.5 units of
|
// Anchor stays f64: large coordinates (UTM etc.) lose ~0.5 units of
|
||||||
// precision when cast to f32, which snaps text baselines onto a coarse
|
// precision when cast to f32, which snaps text baselines onto a coarse
|
||||||
// grid and makes adjacent rows collide. Only the small local offsets
|
// grid and makes adjacent rows collide. Only the small local offsets
|
||||||
|
|
@ -188,17 +260,16 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
&t.vertical_alignment,
|
&t.vertical_alignment,
|
||||||
&t.alignment_point,
|
&t.alignment_point,
|
||||||
) {
|
) {
|
||||||
(HA::Aligned | HA::Middle | HA::Fit, _, Some(a)) => [a.x, a.y],
|
(HA::Aligned | HA::Fit, _, _) => [t.insertion_point.x, t.insertion_point.y],
|
||||||
|
(HA::Middle, _, Some(a)) => [a.x, a.y],
|
||||||
(HA::Center | HA::Right, _, Some(a)) => [a.x, a.y],
|
(HA::Center | HA::Right, _, Some(a)) => [a.x, a.y],
|
||||||
(_, VA::Bottom | VA::Middle | VA::Top, Some(a)) => [a.x, a.y],
|
(_, VA::Bottom | VA::Middle | VA::Top, Some(a)) => [a.x, a.y],
|
||||||
_ => [t.insertion_point.x, t.insertion_point.y],
|
_ => [t.insertion_point.x, t.insertion_point.y],
|
||||||
};
|
};
|
||||||
// Strip %%u/%%o for bounds (they add no width); resolve %%d/%%c/%%p for correct advance.
|
|
||||||
let value_for_bounds = resolve_dxf_special_chars(&t.value);
|
|
||||||
let bounds = text_local_bounds(
|
let bounds = text_local_bounds(
|
||||||
&font_name,
|
&font_name,
|
||||||
&value_for_bounds,
|
&value_for_bounds,
|
||||||
t.height as f32,
|
height,
|
||||||
width_factor,
|
width_factor,
|
||||||
oblique_angle,
|
oblique_angle,
|
||||||
);
|
);
|
||||||
|
|
@ -213,7 +284,8 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
let ax = match t.horizontal_alignment {
|
let ax = match t.horizontal_alignment {
|
||||||
HA::Left => 0.0,
|
HA::Left => 0.0,
|
||||||
HA::Center | HA::Middle => b.advance * 0.5 * sign,
|
HA::Center | HA::Middle => b.advance * 0.5 * sign,
|
||||||
HA::Right | HA::Aligned | HA::Fit => b.advance * sign,
|
HA::Right => b.advance * sign,
|
||||||
|
HA::Aligned | HA::Fit => 0.0,
|
||||||
};
|
};
|
||||||
// Vertical anchor uses the inked extent (cap / baseline geometry).
|
// Vertical anchor uses the inked extent (cap / baseline geometry).
|
||||||
let ay = match t.vertical_alignment {
|
let ay = match t.vertical_alignment {
|
||||||
|
|
@ -236,7 +308,7 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
];
|
];
|
||||||
TextPlacement {
|
TextPlacement {
|
||||||
origin,
|
origin,
|
||||||
height: t.height as f32,
|
height,
|
||||||
rotation,
|
rotation,
|
||||||
width_factor,
|
width_factor,
|
||||||
oblique_angle,
|
oblique_angle,
|
||||||
|
|
@ -247,12 +319,19 @@ pub fn text_run_placement(t: &Text, document: &acadrust::CadDocument) -> TextPla
|
||||||
}
|
}
|
||||||
|
|
||||||
fn grips(t: &Text) -> Vec<GripDef> {
|
fn grips(t: &Text) -> Vec<GripDef> {
|
||||||
let p = glam::DVec3::new(
|
let insertion = glam::DVec3::new(
|
||||||
t.insertion_point.x,
|
t.insertion_point.x,
|
||||||
t.insertion_point.y,
|
t.insertion_point.y,
|
||||||
t.insertion_point.z,
|
t.insertion_point.z,
|
||||||
);
|
);
|
||||||
vec![square_grip(0, p)]
|
let mut grips = vec![square_grip(0, insertion)];
|
||||||
|
if let Some(point) = t.alignment_point {
|
||||||
|
grips.push(square_grip(
|
||||||
|
1,
|
||||||
|
glam::DVec3::new(point.x, point.y, point.z),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
grips
|
||||||
}
|
}
|
||||||
|
|
||||||
fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
|
fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
|
|
@ -263,15 +342,25 @@ fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
// both points are live.
|
// both points are live.
|
||||||
let is_plain_left = matches!(t.horizontal_alignment, HA::Left)
|
let is_plain_left = matches!(t.horizontal_alignment, HA::Left)
|
||||||
&& matches!(t.vertical_alignment, VA::Baseline);
|
&& matches!(t.vertical_alignment, VA::Baseline);
|
||||||
let pos_editable = is_plain_left || matches!(t.horizontal_alignment, HA::Aligned | HA::Fit);
|
let is_aligned = matches!(t.horizontal_alignment, HA::Aligned);
|
||||||
let align_editable = !is_plain_left;
|
let is_two_point = matches!(t.horizontal_alignment, HA::Aligned | HA::Fit);
|
||||||
// The alignment point is meaningless (reset to the origin) for plain-Left text.
|
// The visible Properties palette exposes Text alignment as calculated
|
||||||
|
// coordinates and Position as the editable placement point. For aligned
|
||||||
|
// and fit text Position targets the first endpoint; for every other
|
||||||
|
// non-left mode it targets the alignment anchor that actually places the
|
||||||
|
// glyph run.
|
||||||
|
let position_uses_alignment = !is_plain_left && !is_two_point;
|
||||||
let ap = t.alignment_point.unwrap_or(t.insertion_point);
|
let ap = t.alignment_point.unwrap_or(t.insertion_point);
|
||||||
let (ax, ay, az) = if align_editable {
|
let (ax, ay, az) = if !is_plain_left {
|
||||||
(ap.x, ap.y, ap.z)
|
(ap.x, ap.y, ap.z)
|
||||||
} else {
|
} else {
|
||||||
(0.0, 0.0, 0.0)
|
(0.0, 0.0, 0.0)
|
||||||
};
|
};
|
||||||
|
let position = if position_uses_alignment {
|
||||||
|
ap
|
||||||
|
} else {
|
||||||
|
t.insertion_point
|
||||||
|
};
|
||||||
vec![
|
vec![
|
||||||
PropSection {
|
PropSection {
|
||||||
title: t!("Text").into_owned(),
|
title: t!("Text").into_owned(),
|
||||||
|
|
@ -331,22 +420,31 @@ fn properties(t: &Text, text_style_names: &[String]) -> Vec<PropSection> {
|
||||||
t!("Height").as_ref(),
|
t!("Height").as_ref(),
|
||||||
"height",
|
"height",
|
||||||
t.height,
|
t.height,
|
||||||
crate::entities::common::style_fixed_height(&t.style).is_none(),
|
!is_aligned
|
||||||
|
&& crate::entities::common::style_fixed_height(&t.style).is_none(),
|
||||||
),
|
),
|
||||||
edit_angle(t!("Rotation").as_ref(), "rotation", t.rotation.to_degrees()),
|
if is_two_point {
|
||||||
|
ro(
|
||||||
|
t!("Rotation").as_ref(),
|
||||||
|
"rotation",
|
||||||
|
crate::entities::common::format_angle(displayed_rotation(t)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
edit_angle(t!("Rotation").as_ref(), "rotation", t.rotation.to_degrees())
|
||||||
|
},
|
||||||
edit(t!("Width factor").as_ref(), "width_factor", t.width_factor),
|
edit(t!("Width factor").as_ref(), "width_factor", t.width_factor),
|
||||||
edit_angle(t!("Obliquing").as_ref(), "oblique_angle", t.oblique_angle.to_degrees()),
|
edit_angle(t!("Obliquing").as_ref(), "oblique_angle", t.oblique_angle.to_degrees()),
|
||||||
num_row(t!("Text alignment X").as_ref(), "align_x", ax, align_editable),
|
num_row(t!("Text alignment X").as_ref(), "align_x", ax, false),
|
||||||
num_row(t!("Text alignment Y").as_ref(), "align_y", ay, align_editable),
|
num_row(t!("Text alignment Y").as_ref(), "align_y", ay, false),
|
||||||
num_row(t!("Text alignment Z").as_ref(), "align_z", az, align_editable),
|
num_row(t!("Text alignment Z").as_ref(), "align_z", az, false),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
PropSection {
|
PropSection {
|
||||||
title: t!("Geometry").into_owned(),
|
title: t!("Geometry").into_owned(),
|
||||||
props: vec![
|
props: vec![
|
||||||
num_row(t!("Position X").as_ref(), "ins_x", t.insertion_point.x, pos_editable),
|
num_row(t!("Position X").as_ref(), "ins_x", position.x, true),
|
||||||
num_row(t!("Position Y").as_ref(), "ins_y", t.insertion_point.y, pos_editable),
|
num_row(t!("Position Y").as_ref(), "ins_y", position.y, true),
|
||||||
num_row(t!("Position Z").as_ref(), "ins_z", t.insertion_point.z, pos_editable),
|
num_row(t!("Position Z").as_ref(), "ins_z", position.z, true),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
PropSection {
|
PropSection {
|
||||||
|
|
@ -439,37 +537,64 @@ fn apply_geom_prop(t: &mut Text, field: &str, value: &str) {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
match field {
|
match field {
|
||||||
"ins_x" => t.insertion_point.x = v,
|
"ins_x" | "ins_y" | "ins_z" => {
|
||||||
"ins_y" => t.insertion_point.y = v,
|
let plain_left = matches!(t.horizontal_alignment, HA::Left)
|
||||||
"ins_z" => t.insertion_point.z = v,
|
&& matches!(t.vertical_alignment, VA::Baseline);
|
||||||
"align_x" | "align_y" | "align_z" => {
|
let two_point = matches!(t.horizontal_alignment, HA::Aligned | HA::Fit);
|
||||||
let ins = t.insertion_point;
|
let target = if !plain_left && !two_point {
|
||||||
let ap = t.alignment_point.get_or_insert(ins);
|
let insertion = t.insertion_point;
|
||||||
|
t.alignment_point.get_or_insert(insertion)
|
||||||
|
} else {
|
||||||
|
&mut t.insertion_point
|
||||||
|
};
|
||||||
match field {
|
match field {
|
||||||
"align_x" => ap.x = v,
|
"ins_x" => target.x = v,
|
||||||
"align_y" => ap.y = v,
|
"ins_y" => target.y = v,
|
||||||
_ => ap.z = v,
|
_ => target.z = v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"height" if v > 0.0 => t.height = v,
|
"align_x" | "align_y" | "align_z" => {
|
||||||
"rotation" => t.rotation = v.to_radians(),
|
// Calculated display rows are intentionally not writable.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
"height"
|
||||||
|
if v > 0.0 && !matches!(t.horizontal_alignment, HA::Aligned) =>
|
||||||
|
{
|
||||||
|
t.height = v
|
||||||
|
}
|
||||||
|
"rotation" if !matches!(t.horizontal_alignment, HA::Aligned | HA::Fit) => {
|
||||||
|
t.rotation = v.to_radians()
|
||||||
|
}
|
||||||
"width_factor" if v > 0.0 => t.width_factor = v,
|
"width_factor" if v > 0.0 => t.width_factor = v,
|
||||||
"oblique_angle" => t.oblique_angle = v.to_radians(),
|
"oblique_angle" if (-85.0..=85.0).contains(&v) => {
|
||||||
|
t.oblique_angle = v.to_radians()
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_grip(t: &mut Text, _grip_id: usize, apply: GripApply) {
|
fn apply_grip(t: &mut Text, grip_id: usize, apply: GripApply) {
|
||||||
match apply {
|
match apply {
|
||||||
GripApply::Absolute(p) => {
|
GripApply::Absolute(p) => {
|
||||||
t.insertion_point.x = p.x as f64;
|
let target = if grip_id == 1 {
|
||||||
t.insertion_point.y = p.y as f64;
|
let insertion = t.insertion_point;
|
||||||
t.insertion_point.z = p.z as f64;
|
t.alignment_point.get_or_insert(insertion)
|
||||||
|
} else {
|
||||||
|
&mut t.insertion_point
|
||||||
|
};
|
||||||
|
target.x = p.x;
|
||||||
|
target.y = p.y;
|
||||||
|
target.z = p.z;
|
||||||
}
|
}
|
||||||
GripApply::Translate(d) => {
|
GripApply::Translate(d) => {
|
||||||
t.insertion_point.x += d.x as f64;
|
t.insertion_point.x += d.x;
|
||||||
t.insertion_point.y += d.y as f64;
|
t.insertion_point.y += d.y;
|
||||||
t.insertion_point.z += d.z as f64;
|
t.insertion_point.z += d.z;
|
||||||
|
if let Some(point) = t.alignment_point.as_mut() {
|
||||||
|
point.x += d.x;
|
||||||
|
point.y += d.y;
|
||||||
|
point.z += d.z;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -495,7 +620,7 @@ fn apply_transform(t: &mut Text, tr: &EntityTransform) {
|
||||||
|
|
||||||
impl RenderConvertible for Text {
|
impl RenderConvertible for Text {
|
||||||
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
fn to_render(&self, document: &acadrust::CadDocument) -> Option<RenderEntity> {
|
||||||
Some(to_render(self, document))
|
Some(to_render_at_scale(self, document, 1.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -551,7 +676,9 @@ impl Grippable for Text {
|
||||||
value: f64,
|
value: f64,
|
||||||
) {
|
) {
|
||||||
use crate::scene::model::object::GripMenuAction as A;
|
use crate::scene::model::object::GripMenuAction as A;
|
||||||
if matches!(action, A::RotateText) {
|
if matches!(action, A::RotateText)
|
||||||
|
&& !matches!(self.horizontal_alignment, HA::Aligned | HA::Fit)
|
||||||
|
{
|
||||||
self.rotation = value.to_radians();
|
self.rotation = value.to_radians();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||