Add the native Drawing workspace UI scaffold (#5349)
* docs: plan drawing workspace UI implementation
* assets: add drawing icon drafts for owner review
* feat(ui): add drawing workspace scaffold with shared ribbon and docked settings
Expose planned Drawing tools without introducing drawing documents, paper rendering or CAD behavior. Reuse existing source BOM, properties and flat-pattern services; retain placeholders for unapproved icon drafts.
Validation: full UI, kernel, icon, clippy, wasm and layout checks passed. Full cargo xtask ci reports three geometry failures that also reproduce on unchanged drawings base 3b982ca: counterbored chamfered-disc ring cut, robustness seed 36 and seed 71. No geometry source or golden fixtures changed.
* Preserve default icon palette in Drawing scaffold tests
* Preserve Drawing providers and complete scaffold translations
@@ -173,6 +173,12 @@ and the [foundation ledger](docs/drawing-foundations.md) track the bounded imple
|
||||
packages. The first geometry increment defines validated section records in a plane-local
|
||||
frame; the ledger separates that contract from section producers and the Drawing workspace.
|
||||
|
||||
Drawing UI scaffold: the native workspace now supplies the shared four-tab ribbon,
|
||||
source Browser, compact dockable sheet strip and resizable Sheet Settings/options
|
||||
panel. Planned tools remain unavailable; only existing source services execute.
|
||||
Drawing files and paper rendering are still deferred. See the
|
||||
[UI implementation roadmap](docs/drawing-workspace-ui.md).
|
||||
|
||||
Desktop view DXF export runs in the file-operation worker with five-stage progress and
|
||||
cooperative cancellation. Curve tolerance (0.0001–1 mm, default 0.01 mm) is remembered alongside
|
||||
the hidden-edge setting. Cancelled exports preserve the model and the previous destination;
|
||||
|
||||
@@ -178,6 +178,7 @@ pattern_rect false
|
||||
perforate false
|
||||
perspective false
|
||||
pipe false
|
||||
placeholder true
|
||||
plane false
|
||||
plane_along_path false
|
||||
plane_angle false
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Align drawing annotations</title>
|
||||
<path d="M12 6L12 58" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 12L51.5 12A2.5 2.5 0 0 1 54 14.5L54 17.5A2.5 2.5 0 0 1 51.5 20L22.5 20A2.5 2.5 0 0 1 20 17.5L20 14.5A2.5 2.5 0 0 1 22.5 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 28L40.5 28A2.5 2.5 0 0 1 43 30.5L43 33.5A2.5 2.5 0 0 1 40.5 36L22.5 36A2.5 2.5 0 0 1 20 33.5L20 30.5A2.5 2.5 0 0 1 22.5 28Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 44L47.5 44A2.5 2.5 0 0 1 50 46.5L50 49.5A2.5 2.5 0 0 1 47.5 52L22.5 52A2.5 2.5 0 0 1 20 49.5L20 46.5A2.5 2.5 0 0 1 22.5 44Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 962 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Align drawing views</title>
|
||||
<path d="M10.5 24L23.5 24A2.5 2.5 0 0 1 26 26.5L26 43.5A2.5 2.5 0 0 1 23.5 46L10.5 46A2.5 2.5 0 0 1 8 43.5L8 26.5A2.5 2.5 0 0 1 10.5 24Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M39.5 12L52.5 12A2.5 2.5 0 0 1 55 14.5L55 43.5A2.5 2.5 0 0 1 52.5 46L39.5 46A2.5 2.5 0 0 1 37 43.5L37 14.5A2.5 2.5 0 0 1 39.5 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6 52L58 52" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17 29L17 40M46 18L46 39" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 846 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing balloon</title>
|
||||
<path d="M9.5 37L29.5 37A2.5 2.5 0 0 1 32 39.5L32 52.5A2.5 2.5 0 0 1 29.5 55L9.5 55A2.5 2.5 0 0 1 7 52.5L7 39.5A2.5 2.5 0 0 1 9.5 37Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 45L35 30L40 25" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 50L19 37L28 45Z" fill="#236ead"/>
|
||||
<circle cx="45" cy="17" r="12" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 651 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Base drawing view</title>
|
||||
<path d="M9.5 10L54.5 10A2.5 2.5 0 0 1 57 12.5L57 51.5A2.5 2.5 0 0 1 54.5 54L9.5 54A2.5 2.5 0 0 1 7 51.5L7 12.5A2.5 2.5 0 0 1 9.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17.5 19L42.5 19A2.5 2.5 0 0 1 45 21.5L45 39.5A2.5 2.5 0 0 1 42.5 42L17.5 42A2.5 2.5 0 0 1 15 39.5L15 21.5A2.5 2.5 0 0 1 17.5 19Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="30" cy="30.5" r="6" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M47 46L53 46M50 43L50 49" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 851 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing centre mark</title>
|
||||
<circle cx="32" cy="32" r="18" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 6L32 21M32 27L32 37M32 43L32 58M6 32L21 32M27 32L37 32M43 32L58 32" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 420 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Check drawing</title>
|
||||
<path d="M14.5 7L49.5 7A2.5 2.5 0 0 1 52 9.5L52 54.5A2.5 2.5 0 0 1 49.5 57L14.5 57A2.5 2.5 0 0 1 12 54.5L12 9.5A2.5 2.5 0 0 1 14.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21 19L42 19M21 29L35 29" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27 44L37 53L57 30" fill="none" stroke="#ffffff" stroke-width="6.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27 44L37 53L57 30" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 730 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing datum</title>
|
||||
<path d="M6 53L58 53" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 52L24 38L40 38Z" fill="#236ead"/>
|
||||
<path d="M32 38L32 27" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21.5 7L42.5 7A2.5 2.5 0 0 1 45 9.5L45 24.5A2.5 2.5 0 0 1 42.5 27L21.5 27A2.5 2.5 0 0 1 19 24.5L19 9.5A2.5 2.5 0 0 1 21.5 7Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Detail drawing view</title>
|
||||
<path d="M9.5 11L30.5 11A2.5 2.5 0 0 1 33 13.5L33 45.5A2.5 2.5 0 0 1 30.5 48L9.5 48A2.5 2.5 0 0 1 7 45.5L7 13.5A2.5 2.5 0 0 1 9.5 11Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M13 18L23 18L23 33L29 33" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="23" cy="30" r="9" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M31 35L39 41" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47" cy="46" r="12" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M40 39L46 39L46 48L53 48" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing dimension</title>
|
||||
<path d="M10 10L10 55M54 10L54 55" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 14L54 14L54 36L10 36Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 49L44 49" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M11 49L23 43L23 55Z" fill="#236ead"/>
|
||||
<path d="M53 49L41 55L41 43Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 598 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Edit drawing table</title>
|
||||
<path d="M10.5 10L53.5 10A2.5 2.5 0 0 1 56 12.5L56 51.5A2.5 2.5 0 0 1 53.5 54L10.5 54A2.5 2.5 0 0 1 8 51.5L8 12.5A2.5 2.5 0 0 1 10.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L54 12L54 23L10 23Z" fill="#b8c3cf"/>
|
||||
<path d="M8 24L56 24M8 39L56 39M23 24L23 54" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M25 26L54 26L54 37L25 37Z" fill="#b9ddf8"/>
|
||||
<path d="M32 32L47 32M42 27L47 32L42 37" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 741 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Hole callout</title>
|
||||
<circle cx="18" cy="43" r="13" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="18" cy="43" r="6" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27 35L39 22L57 22" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23 40L27 28L34 35Z" fill="#236ead"/>
|
||||
<circle cx="44" cy="10" r="4.5" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M39 16L49 4" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Insert drawing image</title>
|
||||
<path d="M9.5 11L54.5 11A2.5 2.5 0 0 1 57 13.5L57 50.5A2.5 2.5 0 0 1 54.5 53L9.5 53A2.5 2.5 0 0 1 7 50.5L7 13.5A2.5 2.5 0 0 1 9.5 11Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="43" cy="23" r="5" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 45L24 27L36 41L42 34L54 47Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 622 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing leader</title>
|
||||
<path d="M8.5 34L33.5 34A2.5 2.5 0 0 1 36 36.5L36 51.5A2.5 2.5 0 0 1 33.5 54L8.5 54A2.5 2.5 0 0 1 6 51.5L6 36.5A2.5 2.5 0 0 1 8.5 34Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18 45L39 18L56 18" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 49L19 35L26 41Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing parts list</title>
|
||||
<path d="M10.5 10L53.5 10A2.5 2.5 0 0 1 56 12.5L56 51.5A2.5 2.5 0 0 1 53.5 54L10.5 54A2.5 2.5 0 0 1 8 51.5L8 12.5A2.5 2.5 0 0 1 10.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L54 12L54 23L10 23Z" fill="#b8c3cf"/>
|
||||
<path d="M8 24L56 24M8 39L56 39M23 24L23 54" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.5" cy="31.5" r="3.5" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.5" cy="46.5" r="3.5" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 31L48 31M30 46L48 46" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 958 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Print drawing</title>
|
||||
<path d="M18.5 7L45.5 7A2.5 2.5 0 0 1 48 9.5L48 29.5A2.5 2.5 0 0 1 45.5 32L18.5 32A2.5 2.5 0 0 1 16 29.5L16 9.5A2.5 2.5 0 0 1 18.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M8.5 24L55.5 24A2.5 2.5 0 0 1 58 26.5L58 49.5A2.5 2.5 0 0 1 55.5 52L8.5 52A2.5 2.5 0 0 1 6 49.5L6 26.5A2.5 2.5 0 0 1 8.5 24Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="48" cy="32" r="2.5" fill="#236ead" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.5 37L45.5 37A2.5 2.5 0 0 1 48 39.5L48 55.5A2.5 2.5 0 0 1 45.5 58L18.5 58A2.5 2.5 0 0 1 16 55.5L16 39.5A2.5 2.5 0 0 1 18.5 37Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23 44L41 44M23 51L36 51" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Projected drawing view</title>
|
||||
<path d="M9.5 33L31.5 33A2.5 2.5 0 0 1 34 35.5L34 52.5A2.5 2.5 0 0 1 31.5 55L9.5 55A2.5 2.5 0 0 1 7 52.5L7 35.5A2.5 2.5 0 0 1 9.5 33Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="20.5" cy="44" r="5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9.5 8L31.5 8A2.5 2.5 0 0 1 34 10.5L34 19.5A2.5 2.5 0 0 1 31.5 22L9.5 22A2.5 2.5 0 0 1 7 19.5L7 10.5A2.5 2.5 0 0 1 9.5 8Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9 25L9 30M32 25L32 30M38 44L54 44" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="3 5"/>
|
||||
<path d="M44 28L44 13" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M44 7L48 14L40 14Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Browse drawing resources</title>
|
||||
<path d="M15 7L51 7A3 3 0 0 1 54 10L54 48A3 3 0 0 1 51 51L15 51A3 3 0 0 1 12 48L12 10A3 3 0 0 1 15 7Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L46 12A3 3 0 0 1 49 15L49 53A3 3 0 0 1 46 56L10 56A3 3 0 0 1 7 53L7 15A3 3 0 0 1 10 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M14 20L42 20L42 47L14 47Z" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M14 38L42 38M28 38L28 47" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22 27L34 27L34 36L22 36Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 933 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Add drawing revision</title>
|
||||
<path d="M14.5 7L49.5 7A2.5 2.5 0 0 1 52 9.5L52 54.5A2.5 2.5 0 0 1 49.5 57L14.5 57A2.5 2.5 0 0 1 12 54.5L12 9.5A2.5 2.5 0 0 1 14.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 19L43 19M20 29L37 29" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M43 33L58 57L28 57Z" fill="#ffffff" stroke="#ffffff" stroke-width="4.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M43 33L58 57L28 57Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 745 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing revision table</title>
|
||||
<path d="M10.5 10L53.5 10A2.5 2.5 0 0 1 56 12.5L56 51.5A2.5 2.5 0 0 1 53.5 54L10.5 54A2.5 2.5 0 0 1 8 51.5L8 12.5A2.5 2.5 0 0 1 10.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L54 12L54 23L10 23Z" fill="#b8c3cf"/>
|
||||
<path d="M8 24L56 24M8 39L56 39M23 24L23 54" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 27L10 35L20 35ZM15 42L10 50L20 50Z" fill="#236ead"/>
|
||||
<path d="M31 31L49 31M31 46L49 46" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 752 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Section drawing view</title>
|
||||
<path d="M8 15L56 15L56 50L37 50L37 27L27 27L27 50L8 50Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 38L26 22M10 25L20 15M10 50L24 36M39 35L54 20M39 48L54 33M49 50L56 43" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Space drawing annotations</title>
|
||||
<path d="M28.5 9L53.5 9A2.5 2.5 0 0 1 56 11.5L56 14.5A2.5 2.5 0 0 1 53.5 17L28.5 17A2.5 2.5 0 0 1 26 14.5L26 11.5A2.5 2.5 0 0 1 28.5 9Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28.5 28L53.5 28A2.5 2.5 0 0 1 56 30.5L56 33.5A2.5 2.5 0 0 1 53.5 36L28.5 36A2.5 2.5 0 0 1 26 33.5L26 30.5A2.5 2.5 0 0 1 28.5 28Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28.5 47L53.5 47A2.5 2.5 0 0 1 56 49.5L56 52.5A2.5 2.5 0 0 1 53.5 55L28.5 55A2.5 2.5 0 0 1 26 52.5L26 49.5A2.5 2.5 0 0 1 28.5 47Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 19L15 25M15 38L15 44" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 14L10 21L20 21ZM15 30L10 23L20 23ZM15 33L10 40L20 40ZM15 49L10 42L20 42Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Update drawing</title>
|
||||
<path d="M14.5 7L49.5 7A2.5 2.5 0 0 1 52 9.5L52 54.5A2.5 2.5 0 0 1 49.5 57L14.5 57A2.5 2.5 0 0 1 12 54.5L12 9.5A2.5 2.5 0 0 1 14.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 16L41.5 16A2.5 2.5 0 0 1 44 18.5L44 28.5A2.5 2.5 0 0 1 41.5 31L22.5 31A2.5 2.5 0 0 1 20 28.5L20 18.5A2.5 2.5 0 0 1 22.5 16Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M38 35A11 11 0 1 0 45 45" fill="none" stroke="#ffffff" stroke-width="6.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M38 35A11 11 0 1 0 45 45" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M44 40L32 37L40 28Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 900 B |
@@ -0,0 +1,834 @@
|
||||
{
|
||||
"ar": {
|
||||
"0 objects": "0 عناصر",
|
||||
"ASSEMBLY": "التجميع",
|
||||
"Add Drawing Revision": "إضافة مراجعة للوحة",
|
||||
"Add Revision": "إضافة مراجعة",
|
||||
"Add sheet (not implemented)": "إضافة ورقة (غير منفّذ)",
|
||||
"Align Annotations": "محاذاة التعليقات التوضيحية",
|
||||
"Align Notes": "محاذاة الملاحظات",
|
||||
"Align Views": "محاذاة المساقط",
|
||||
"Annotate": "إضافة تعليقات توضيحية",
|
||||
"Apply (not implemented)": "تطبيق (غير منفّذ)",
|
||||
"Auto (Theme)": "تلقائي (السمة)",
|
||||
"Balloon": "بالون",
|
||||
"Base View": "المسقط الأساسي",
|
||||
"Border & template": "الإطار والقالب",
|
||||
"Browse Drawing Resources": "تصفح موارد اللوحات",
|
||||
"CHANGES": "التغييرات",
|
||||
"Centre Mark": "علامة المركز",
|
||||
"Check Drawing": "فحص اللوحة",
|
||||
"Close drawing panel": "إغلاق لوحة إعدادات الرسم",
|
||||
"DRAWING ANNOTATE": "تعليقات اللوحة",
|
||||
"DRAWING LAYOUT": "تخطيط اللوحة",
|
||||
"DRAWING PARTS": "أجزاء اللوحة",
|
||||
"DRAWING REVIEW": "مراجعة اللوحة",
|
||||
"Datum": "مرجع",
|
||||
"Detail View": "مسقط تفصيلي",
|
||||
"Dock at bottom": "إرساء في الأسفل",
|
||||
"Dock at top": "إرساء في الأعلى",
|
||||
"Dock beneath Browser": "إرساء أسفل المتصفح",
|
||||
"Dock left": "إرساء يسارًا",
|
||||
"Dock right": "إرساء يمينًا",
|
||||
"Dock sheet tabs": "إرساء علامات تبويب الأوراق",
|
||||
"Drafting defaults": "إعدادات الرسم الافتراضية",
|
||||
"Drawing & title block": "اللوحة وخانة العنوان",
|
||||
"Drawing Dimension": "بُعد اللوحة",
|
||||
"Drawing Line": "خط اللوحة",
|
||||
"Drawing Note": "ملاحظة اللوحة",
|
||||
"Drawing defaults (not available)": "إعدادات اللوحة الافتراضية (غير متاحة)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "لم تُنفّذ ملفات اللوحات ولا عارض الورق بعد. تعرض الأدوات المخطط لها خياراتها حيثما حُدّدت.",
|
||||
"Drawing workspace": "مساحة عمل اللوحات",
|
||||
"Edit Drawing Table": "تعديل جدول اللوحة",
|
||||
"Edit Table": "تعديل الجدول",
|
||||
"Export Drawing": "تصدير اللوحة",
|
||||
"Finish the current operation before entering Drawing": "أكمل العملية الحالية قبل الدخول إلى مساحة اللوحات",
|
||||
"Hole Callout": "بيان الثقب",
|
||||
"Image": "صورة",
|
||||
"Inherited from parent view": "موروث من المسقط الأصل",
|
||||
"Insert Image": "إدراج صورة",
|
||||
"Leader": "خط إشارة",
|
||||
"Leader Note": "ملاحظة بخط إشارة",
|
||||
"Manage sheets (not implemented)": "إدارة الأوراق (غير منفّذ)",
|
||||
"Manage sheets and docking": "إدارة الأوراق والإرساء",
|
||||
"NOTES": "الملاحظات",
|
||||
"No drawing document": "لا يوجد مستند لوحة",
|
||||
"No drawing geometry available": "لا تتوفر هندسة للوحة",
|
||||
"No drawing objects": "لا توجد عناصر في اللوحة",
|
||||
"No drawing sheet is available yet.": "لا تتوفر ورقة لوحة بعد.",
|
||||
"No drawing view available": "لا يتوفر مسقط للوحة",
|
||||
"No sheet available": "لا تتوفر ورقة",
|
||||
"Not available": "غير متاح",
|
||||
"Not configured": "غير مُعدّ",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "غير منفّذ. تصف هذه الخيارات الواجهة المخطط لها؛ لا تتغير أي بيانات للوحة.",
|
||||
"Note": "ملاحظة",
|
||||
"Paper & layout": "الورق والتخطيط",
|
||||
"Paper colour": "لون الورق",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "سيتوفر لون الورق عند تنفيذ عارض الورقة.",
|
||||
"Parent view": "المسقط الأصل",
|
||||
"Parts & Tables": "الأجزاء والجداول",
|
||||
"Parts List": "قائمة الأجزاء",
|
||||
"Print": "طباعة",
|
||||
"Print Drawing": "طباعة اللوحة",
|
||||
"Profile & units": "ملف التعريف والوحدات",
|
||||
"Projected View": "مسقط مشتق بالإسقاط",
|
||||
"RESOURCES": "الموارد",
|
||||
"REVISION": "المراجعة",
|
||||
"References": "المراجع",
|
||||
"Resources": "الموارد",
|
||||
"Return to Design to use this command.": "عُد إلى مساحة التصميم لاستخدام هذا الأمر.",
|
||||
"Review": "مراجعة",
|
||||
"Revision Table": "جدول المراجعات",
|
||||
"Revision Tbl.": "جدول المراجعات",
|
||||
"SOURCE": "المصدر",
|
||||
"SYMBOLS": "الرموز",
|
||||
"Screen appearance": "مظهر الشاشة",
|
||||
"Section View": "مسقط مقطعي",
|
||||
"Sheet Settings": "إعدادات الورقة",
|
||||
"Sheet unavailable": "الورقة غير متاحة",
|
||||
"Size & orientation": "الحجم والاتجاه",
|
||||
"Source BOM": "قائمة مواد المصدر",
|
||||
"Source Flat": "النمط المسطح للمصدر",
|
||||
"Source design": "التصميم المصدر",
|
||||
"Source properties": "خصائص المصدر",
|
||||
"Space": "توزيع المسافات",
|
||||
"Space Annotations": "توزيع المسافات بين التعليقات التوضيحية",
|
||||
"Space Notes": "توزيع المسافات بين الملاحظات",
|
||||
"TABLES": "الجداول",
|
||||
"Text style": "نمط النص",
|
||||
"Title": "العنوان",
|
||||
"Title block": "خانة العنوان",
|
||||
"UI preview": "معاينة الواجهة",
|
||||
"Units & precision": "الوحدات والدقة",
|
||||
"Update Drawing": "تحديث اللوحة",
|
||||
"View": "مسقط"
|
||||
},
|
||||
"he": {
|
||||
"0 objects": "0 עצמים",
|
||||
"ASSEMBLY": "מכלול",
|
||||
"Add Drawing Revision": "הוספת גרסה לשרטוט",
|
||||
"Add Revision": "הוספת גרסה",
|
||||
"Add sheet (not implemented)": "הוספת גיליון (טרם מומש)",
|
||||
"Align Annotations": "יישור הערות הסבר",
|
||||
"Align Notes": "יישור הערות",
|
||||
"Align Views": "יישור מבטים",
|
||||
"Annotate": "הוספת הערות הסבר",
|
||||
"Apply (not implemented)": "החלה (טרם מומש)",
|
||||
"Auto (Theme)": "אוטומטי (ערכת נושא)",
|
||||
"Balloon": "בלון",
|
||||
"Base View": "מבט בסיס",
|
||||
"Border & template": "מסגרת ותבנית",
|
||||
"Browse Drawing Resources": "עיון במשאבי שרטוט",
|
||||
"CHANGES": "שינויים",
|
||||
"Centre Mark": "סימון מרכז",
|
||||
"Check Drawing": "בדיקת שרטוט",
|
||||
"Close drawing panel": "סגירת לוח השרטוט",
|
||||
"DRAWING ANNOTATE": "הערות הסבר בשרטוט",
|
||||
"DRAWING LAYOUT": "פריסת שרטוט",
|
||||
"DRAWING PARTS": "חלקים בשרטוט",
|
||||
"DRAWING REVIEW": "סקירת שרטוט",
|
||||
"Datum": "ייחוס",
|
||||
"Detail View": "מבט מפורט",
|
||||
"Dock at bottom": "עיגון בתחתית",
|
||||
"Dock at top": "עיגון בראש החלון",
|
||||
"Dock beneath Browser": "עיגון מתחת לדפדפן",
|
||||
"Dock left": "עיגון משמאל",
|
||||
"Dock right": "עיגון מימין",
|
||||
"Dock sheet tabs": "עיגון לשוניות הגיליונות",
|
||||
"Drafting defaults": "ברירות מחדל לשרטוט",
|
||||
"Drawing & title block": "שרטוט וטבלת כותרת",
|
||||
"Drawing Dimension": "מידת שרטוט",
|
||||
"Drawing Line": "קו שרטוט",
|
||||
"Drawing Note": "הערת שרטוט",
|
||||
"Drawing defaults (not available)": "ברירות מחדל לשרטוט (לא זמינות)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "קובצי שרטוט ומנגנון רינדור הנייר טרם מומשו. כלים מתוכננים מציגים את האפשרויות שלהם במקומות שבהם הוגדרו.",
|
||||
"Drawing workspace": "סביבת עבודה לשרטוט",
|
||||
"Edit Drawing Table": "עריכת טבלת שרטוט",
|
||||
"Edit Table": "עריכת טבלה",
|
||||
"Export Drawing": "ייצוא שרטוט",
|
||||
"Finish the current operation before entering Drawing": "יש לסיים את הפעולה הנוכחית לפני המעבר לסביבת השרטוט",
|
||||
"Hole Callout": "ציון פרטי קדח",
|
||||
"Image": "תמונה",
|
||||
"Inherited from parent view": "בירושה ממבט האב",
|
||||
"Insert Image": "הוספת תמונה",
|
||||
"Leader": "קו הפניה",
|
||||
"Leader Note": "הערה עם קו הפניה",
|
||||
"Manage sheets (not implemented)": "ניהול גיליונות (טרם מומש)",
|
||||
"Manage sheets and docking": "ניהול גיליונות ועיגון",
|
||||
"NOTES": "הערות",
|
||||
"No drawing document": "אין מסמך שרטוט",
|
||||
"No drawing geometry available": "אין גאומטריית שרטוט זמינה",
|
||||
"No drawing objects": "אין עצמים בשרטוט",
|
||||
"No drawing sheet is available yet.": "אין עדיין גיליון שרטוט זמין.",
|
||||
"No drawing view available": "אין מבט שרטוט זמין",
|
||||
"No sheet available": "אין גיליון זמין",
|
||||
"Not available": "לא זמין",
|
||||
"Not configured": "לא מוגדר",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "טרם מומש. האפשרויות האלה מתארות את הממשק המתוכנן; נתוני השרטוט אינם משתנים.",
|
||||
"Note": "הערה",
|
||||
"Paper & layout": "נייר ופריסה",
|
||||
"Paper colour": "צבע הנייר",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "צבע הנייר יהיה זמין כאשר ימומש מנגנון רינדור הגיליון.",
|
||||
"Parent view": "מבט אב",
|
||||
"Parts & Tables": "חלקים וטבלאות",
|
||||
"Parts List": "רשימת חלקים",
|
||||
"Print": "הדפסה",
|
||||
"Print Drawing": "הדפסת שרטוט",
|
||||
"Profile & units": "פרופיל ויחידות",
|
||||
"Projected View": "מבט מוטל",
|
||||
"RESOURCES": "משאבים",
|
||||
"REVISION": "גרסה",
|
||||
"References": "הפניות",
|
||||
"Resources": "משאבים",
|
||||
"Return to Design to use this command.": "יש לחזור לסביבת התכן כדי להשתמש בפקודה זו.",
|
||||
"Review": "סקירה",
|
||||
"Revision Table": "טבלת גרסאות",
|
||||
"Revision Tbl.": "טבלת גרסאות",
|
||||
"SOURCE": "מקור",
|
||||
"SYMBOLS": "סמלים",
|
||||
"Screen appearance": "מראה המסך",
|
||||
"Section View": "מבט חתך",
|
||||
"Sheet Settings": "הגדרות גיליון",
|
||||
"Sheet unavailable": "הגיליון אינו זמין",
|
||||
"Size & orientation": "גודל וכיוון",
|
||||
"Source BOM": "רשימת חלקים של המקור",
|
||||
"Source Flat": "פריסת המקור",
|
||||
"Source design": "תכן המקור",
|
||||
"Source properties": "מאפייני המקור",
|
||||
"Space": "ריווח",
|
||||
"Space Annotations": "ריווח הערות הסבר",
|
||||
"Space Notes": "ריווח הערות",
|
||||
"TABLES": "טבלאות",
|
||||
"Text style": "סגנון טקסט",
|
||||
"Title": "כותרת",
|
||||
"Title block": "טבלת כותרת",
|
||||
"UI preview": "תצוגה מקדימה של הממשק",
|
||||
"Units & precision": "יחידות ודיוק",
|
||||
"Update Drawing": "עדכון שרטוט",
|
||||
"View": "מבט"
|
||||
},
|
||||
"vi": {
|
||||
"0 objects": "0 đối tượng",
|
||||
"ASSEMBLY": "CỤM LẮP RÁP",
|
||||
"Add Drawing Revision": "Thêm lần sửa đổi bản vẽ",
|
||||
"Add Revision": "Thêm lần sửa đổi",
|
||||
"Add sheet (not implemented)": "Thêm tờ (chưa triển khai)",
|
||||
"Align Annotations": "Căn chỉnh chú thích",
|
||||
"Align Notes": "Căn chỉnh ghi chú",
|
||||
"Align Views": "Căn chỉnh hình chiếu",
|
||||
"Annotate": "Thêm chú thích",
|
||||
"Apply (not implemented)": "Áp dụng (chưa triển khai)",
|
||||
"Auto (Theme)": "Tự động (theo giao diện)",
|
||||
"Balloon": "Bóng chú thích",
|
||||
"Base View": "Hình chiếu cơ sở",
|
||||
"Border & template": "Khung viền và mẫu",
|
||||
"Browse Drawing Resources": "Duyệt tài nguyên bản vẽ",
|
||||
"CHANGES": "THAY ĐỔI",
|
||||
"Centre Mark": "Dấu tâm",
|
||||
"Check Drawing": "Kiểm tra bản vẽ",
|
||||
"Close drawing panel": "Đóng bảng bản vẽ",
|
||||
"DRAWING ANNOTATE": "CHÚ THÍCH BẢN VẼ",
|
||||
"DRAWING LAYOUT": "BỐ CỤC BẢN VẼ",
|
||||
"DRAWING PARTS": "CHI TIẾT BẢN VẼ",
|
||||
"DRAWING REVIEW": "RÀ SOÁT BẢN VẼ",
|
||||
"Datum": "Chuẩn",
|
||||
"Detail View": "Hình chiếu chi tiết",
|
||||
"Dock at bottom": "Gắn ở cạnh dưới",
|
||||
"Dock at top": "Gắn ở cạnh trên",
|
||||
"Dock beneath Browser": "Gắn bên dưới cây thư mục",
|
||||
"Dock left": "Gắn bên trái",
|
||||
"Dock right": "Gắn bên phải",
|
||||
"Dock sheet tabs": "Gắn các thẻ tờ bản vẽ",
|
||||
"Drafting defaults": "Thiết lập mặc định khi lập bản vẽ",
|
||||
"Drawing & title block": "Bản vẽ và khung tên",
|
||||
"Drawing Dimension": "Kích thước bản vẽ",
|
||||
"Drawing Line": "Đường bản vẽ",
|
||||
"Drawing Note": "Ghi chú bản vẽ",
|
||||
"Drawing defaults (not available)": "Thiết lập mặc định của bản vẽ (chưa có)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "Tệp bản vẽ và bộ kết xuất trang giấy chưa được triển khai. Các công cụ dự kiến hiển thị tùy chọn ở những nơi đã được định nghĩa.",
|
||||
"Drawing workspace": "Không gian làm việc bản vẽ",
|
||||
"Edit Drawing Table": "Sửa bảng trong bản vẽ",
|
||||
"Edit Table": "Sửa bảng",
|
||||
"Export Drawing": "Xuất bản vẽ",
|
||||
"Finish the current operation before entering Drawing": "Hoàn tất thao tác hiện tại trước khi chuyển sang không gian Bản vẽ",
|
||||
"Hole Callout": "Chú dẫn lỗ",
|
||||
"Image": "Hình ảnh",
|
||||
"Inherited from parent view": "Kế thừa từ hình chiếu gốc",
|
||||
"Insert Image": "Chèn hình ảnh",
|
||||
"Leader": "Đường dẫn chú thích",
|
||||
"Leader Note": "Ghi chú có đường dẫn",
|
||||
"Manage sheets (not implemented)": "Quản lý tờ (chưa triển khai)",
|
||||
"Manage sheets and docking": "Quản lý tờ và vị trí gắn",
|
||||
"NOTES": "GHI CHÚ",
|
||||
"No drawing document": "Không có tài liệu bản vẽ",
|
||||
"No drawing geometry available": "Chưa có hình học bản vẽ",
|
||||
"No drawing objects": "Không có đối tượng bản vẽ",
|
||||
"No drawing sheet is available yet.": "Chưa có tờ bản vẽ nào.",
|
||||
"No drawing view available": "Chưa có hình chiếu bản vẽ",
|
||||
"No sheet available": "Chưa có tờ",
|
||||
"Not available": "Chưa có",
|
||||
"Not configured": "Chưa cấu hình",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "Chưa triển khai. Các tùy chọn này mô tả giao diện dự kiến; không có dữ liệu bản vẽ nào bị thay đổi.",
|
||||
"Note": "Ghi chú",
|
||||
"Paper & layout": "Giấy và bố cục",
|
||||
"Paper colour": "Màu giấy",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "Có thể chọn màu giấy khi bộ kết xuất tờ bản vẽ được triển khai.",
|
||||
"Parent view": "Hình chiếu gốc",
|
||||
"Parts & Tables": "Chi tiết và bảng",
|
||||
"Parts List": "Danh sách chi tiết",
|
||||
"Print": "In",
|
||||
"Print Drawing": "In bản vẽ",
|
||||
"Profile & units": "Hồ sơ và đơn vị",
|
||||
"Projected View": "Hình chiếu dẫn xuất",
|
||||
"RESOURCES": "TÀI NGUYÊN",
|
||||
"REVISION": "LẦN SỬA ĐỔI",
|
||||
"References": "Tham chiếu",
|
||||
"Resources": "Tài nguyên",
|
||||
"Return to Design to use this command.": "Quay lại không gian Thiết kế để dùng lệnh này.",
|
||||
"Review": "Rà soát",
|
||||
"Revision Table": "Bảng sửa đổi",
|
||||
"Revision Tbl.": "Bảng sửa đổi",
|
||||
"SOURCE": "NGUỒN",
|
||||
"SYMBOLS": "KÝ HIỆU",
|
||||
"Screen appearance": "Diện mạo trên màn hình",
|
||||
"Section View": "Hình cắt",
|
||||
"Sheet Settings": "Cài đặt tờ",
|
||||
"Sheet unavailable": "Tờ không khả dụng",
|
||||
"Size & orientation": "Kích thước và hướng",
|
||||
"Source BOM": "Bảng kê vật tư nguồn",
|
||||
"Source Flat": "Bản trải phẳng nguồn",
|
||||
"Source design": "Thiết kế nguồn",
|
||||
"Source properties": "Thuộc tính nguồn",
|
||||
"Space": "Giãn cách",
|
||||
"Space Annotations": "Giãn cách chú thích",
|
||||
"Space Notes": "Giãn cách ghi chú",
|
||||
"TABLES": "BẢNG",
|
||||
"Text style": "Kiểu chữ",
|
||||
"Title": "Tiêu đề",
|
||||
"Title block": "Khung tên",
|
||||
"UI preview": "Xem trước giao diện",
|
||||
"Units & precision": "Đơn vị và độ chính xác",
|
||||
"Update Drawing": "Cập nhật bản vẽ",
|
||||
"View": "Hình chiếu"
|
||||
},
|
||||
"en": {
|
||||
"0 objects": "0 objects",
|
||||
"ASSEMBLY": "ASSEMBLY",
|
||||
"Add Drawing Revision": "Add Drawing Revision",
|
||||
"Add Revision": "Add Revision",
|
||||
"Add sheet (not implemented)": "Add sheet (not implemented)",
|
||||
"Align Annotations": "Align Annotations",
|
||||
"Align Notes": "Align Notes",
|
||||
"Align Views": "Align Views",
|
||||
"Annotate": "Annotate",
|
||||
"Apply (not implemented)": "Apply (not implemented)",
|
||||
"Auto (Theme)": "Auto (Theme)",
|
||||
"Balloon": "Balloon",
|
||||
"Base View": "Base View",
|
||||
"Border & template": "Border & template",
|
||||
"Browse Drawing Resources": "Browse Drawing Resources",
|
||||
"CHANGES": "CHANGES",
|
||||
"Centre Mark": "Centre Mark",
|
||||
"Check Drawing": "Check Drawing",
|
||||
"Close drawing panel": "Close drawing panel",
|
||||
"DRAWING ANNOTATE": "DRAWING ANNOTATE",
|
||||
"DRAWING LAYOUT": "DRAWING LAYOUT",
|
||||
"DRAWING PARTS": "DRAWING PARTS",
|
||||
"DRAWING REVIEW": "DRAWING REVIEW",
|
||||
"Datum": "Datum",
|
||||
"Detail View": "Detail View",
|
||||
"Dock at bottom": "Dock at bottom",
|
||||
"Dock at top": "Dock at top",
|
||||
"Dock beneath Browser": "Dock beneath Browser",
|
||||
"Dock left": "Dock left",
|
||||
"Dock right": "Dock right",
|
||||
"Dock sheet tabs": "Dock sheet tabs",
|
||||
"Drafting defaults": "Drafting defaults",
|
||||
"Drawing & title block": "Drawing & title block",
|
||||
"Drawing Dimension": "Drawing Dimension",
|
||||
"Drawing Line": "Drawing Line",
|
||||
"Drawing Note": "Drawing Note",
|
||||
"Drawing defaults (not available)": "Drawing defaults (not available)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.",
|
||||
"Drawing workspace": "Drawing workspace",
|
||||
"Edit Drawing Table": "Edit Drawing Table",
|
||||
"Edit Table": "Edit Table",
|
||||
"Export Drawing": "Export Drawing",
|
||||
"Finish the current operation before entering Drawing": "Finish the current operation before entering Drawing",
|
||||
"Hole Callout": "Hole Callout",
|
||||
"Image": "Image",
|
||||
"Inherited from parent view": "Inherited from parent view",
|
||||
"Insert Image": "Insert Image",
|
||||
"Leader": "Leader",
|
||||
"Leader Note": "Leader Note",
|
||||
"Manage sheets (not implemented)": "Manage sheets (not implemented)",
|
||||
"Manage sheets and docking": "Manage sheets and docking",
|
||||
"NOTES": "NOTES",
|
||||
"No drawing document": "No drawing document",
|
||||
"No drawing geometry available": "No drawing geometry available",
|
||||
"No drawing objects": "No drawing objects",
|
||||
"No drawing sheet is available yet.": "No drawing sheet is available yet.",
|
||||
"No drawing view available": "No drawing view available",
|
||||
"No sheet available": "No sheet available",
|
||||
"Not available": "Not available",
|
||||
"Not configured": "Not configured",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "Not implemented. These options describe the planned interface; no drawing data is changed.",
|
||||
"Note": "Note",
|
||||
"Paper & layout": "Paper & layout",
|
||||
"Paper colour": "Paper colour",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "Paper colour will be available when the sheet renderer is implemented.",
|
||||
"Parent view": "Parent view",
|
||||
"Parts & Tables": "Parts & Tables",
|
||||
"Parts List": "Parts List",
|
||||
"Print": "Print",
|
||||
"Print Drawing": "Print Drawing",
|
||||
"Profile & units": "Profile & units",
|
||||
"Projected View": "Projected View",
|
||||
"RESOURCES": "RESOURCES",
|
||||
"REVISION": "REVISION",
|
||||
"References": "References",
|
||||
"Resources": "Resources",
|
||||
"Return to Design to use this command.": "Return to Design to use this command.",
|
||||
"Review": "Review",
|
||||
"Revision Table": "Revision Table",
|
||||
"Revision Tbl.": "Revision Tbl.",
|
||||
"SOURCE": "SOURCE",
|
||||
"SYMBOLS": "SYMBOLS",
|
||||
"Screen appearance": "Screen appearance",
|
||||
"Section View": "Section View",
|
||||
"Sheet Settings": "Sheet Settings",
|
||||
"Sheet unavailable": "Sheet unavailable",
|
||||
"Size & orientation": "Size & orientation",
|
||||
"Source BOM": "Source BOM",
|
||||
"Source Flat": "Source Flat",
|
||||
"Source design": "Source design",
|
||||
"Source properties": "Source properties",
|
||||
"Space": "Space",
|
||||
"Space Annotations": "Space Annotations",
|
||||
"Space Notes": "Space Notes",
|
||||
"TABLES": "TABLES",
|
||||
"Text style": "Text style",
|
||||
"Title": "Title",
|
||||
"Title block": "Title block",
|
||||
"UI preview": "UI preview",
|
||||
"Units & precision": "Units & precision",
|
||||
"Update Drawing": "Update Drawing",
|
||||
"View": "View"
|
||||
},
|
||||
"ja": {
|
||||
"0 objects": "0個のオブジェクト",
|
||||
"ASSEMBLY": "アセンブリ",
|
||||
"Add Drawing Revision": "図面の改訂を追加",
|
||||
"Add Revision": "改訂を追加",
|
||||
"Add sheet (not implemented)": "シートを追加(未実装)",
|
||||
"Align Annotations": "注釈を整列",
|
||||
"Align Notes": "注記を整列",
|
||||
"Align Views": "ビューを整列",
|
||||
"Annotate": "注釈",
|
||||
"Apply (not implemented)": "適用(未実装)",
|
||||
"Auto (Theme)": "自動(テーマ)",
|
||||
"Balloon": "バルーン",
|
||||
"Base View": "基準ビュー",
|
||||
"Border & template": "図枠とテンプレート",
|
||||
"Browse Drawing Resources": "図面リソースを参照",
|
||||
"CHANGES": "変更",
|
||||
"Centre Mark": "中心マーク",
|
||||
"Check Drawing": "図面をチェック",
|
||||
"Close drawing panel": "図面パネルを閉じる",
|
||||
"DRAWING ANNOTATE": "図面の注釈",
|
||||
"DRAWING LAYOUT": "図面のレイアウト",
|
||||
"DRAWING PARTS": "図面の部品",
|
||||
"DRAWING REVIEW": "図面の確認",
|
||||
"Datum": "データム",
|
||||
"Detail View": "詳細ビュー",
|
||||
"Dock at bottom": "下部にドッキング",
|
||||
"Dock at top": "上部にドッキング",
|
||||
"Dock beneath Browser": "ブラウザーの下にドッキング",
|
||||
"Dock left": "左にドッキング",
|
||||
"Dock right": "右にドッキング",
|
||||
"Dock sheet tabs": "シートタブをドッキング",
|
||||
"Drafting defaults": "製図の既定値",
|
||||
"Drawing & title block": "図面と表題欄",
|
||||
"Drawing Dimension": "図面寸法",
|
||||
"Drawing Line": "図面の線分",
|
||||
"Drawing Note": "図面の注記",
|
||||
"Drawing defaults (not available)": "図面の既定値(利用不可)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "図面ファイルと用紙レンダラーは未実装です。予定されているツールでは、設定項目が定義済みの場合に表示されます。",
|
||||
"Drawing workspace": "図面ワークスペース",
|
||||
"Edit Drawing Table": "図面の表を編集",
|
||||
"Edit Table": "表を編集",
|
||||
"Export Drawing": "図面をエクスポート",
|
||||
"Finish the current operation before entering Drawing": "現在の操作を完了してから図面ワークスペースに移動してください",
|
||||
"Hole Callout": "穴注記",
|
||||
"Image": "画像",
|
||||
"Inherited from parent view": "親ビューから継承",
|
||||
"Insert Image": "画像を挿入",
|
||||
"Leader": "引出線",
|
||||
"Leader Note": "引出線付き注記",
|
||||
"Manage sheets (not implemented)": "シートを管理(未実装)",
|
||||
"Manage sheets and docking": "シートとドッキングを管理",
|
||||
"NOTES": "注記",
|
||||
"No drawing document": "図面ドキュメントがありません",
|
||||
"No drawing geometry available": "利用可能な図面ジオメトリがありません",
|
||||
"No drawing objects": "図面オブジェクトがありません",
|
||||
"No drawing sheet is available yet.": "図面シートはまだ利用できません。",
|
||||
"No drawing view available": "利用可能な図面ビューがありません",
|
||||
"No sheet available": "利用可能なシートがありません",
|
||||
"Not available": "利用不可",
|
||||
"Not configured": "未設定",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "未実装です。これらの設定項目は予定されているインターフェースを示すもので、図面データは変更されません。",
|
||||
"Note": "注記",
|
||||
"Paper & layout": "用紙とレイアウト",
|
||||
"Paper colour": "用紙の色",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "用紙の色はシートレンダラーの実装後に設定できます。",
|
||||
"Parent view": "親ビュー",
|
||||
"Parts & Tables": "部品と表",
|
||||
"Parts List": "部品表",
|
||||
"Print": "印刷",
|
||||
"Print Drawing": "図面を印刷",
|
||||
"Profile & units": "製図規格と単位",
|
||||
"Projected View": "投影ビュー",
|
||||
"RESOURCES": "リソース",
|
||||
"REVISION": "改訂",
|
||||
"References": "参照",
|
||||
"Resources": "リソース",
|
||||
"Return to Design to use this command.": "このコマンドを使用するには、デザインワークスペースに戻ってください。",
|
||||
"Review": "確認",
|
||||
"Revision Table": "改訂表",
|
||||
"Revision Tbl.": "改訂表",
|
||||
"SOURCE": "参照元",
|
||||
"SYMBOLS": "記号",
|
||||
"Screen appearance": "画面表示",
|
||||
"Section View": "断面ビュー",
|
||||
"Sheet Settings": "シート設定",
|
||||
"Sheet unavailable": "シートを利用できません",
|
||||
"Size & orientation": "サイズと向き",
|
||||
"Source BOM": "参照元の部品表",
|
||||
"Source Flat": "参照元の展開図",
|
||||
"Source design": "参照元のデザイン",
|
||||
"Source properties": "参照元のプロパティ",
|
||||
"Space": "等間隔",
|
||||
"Space Annotations": "注釈を等間隔に",
|
||||
"Space Notes": "注記を等間隔に",
|
||||
"TABLES": "表",
|
||||
"Text style": "文字スタイル",
|
||||
"Title": "タイトル",
|
||||
"Title block": "表題欄",
|
||||
"UI preview": "UIプレビュー",
|
||||
"Units & precision": "単位と精度",
|
||||
"Update Drawing": "図面を更新",
|
||||
"View": "ビュー"
|
||||
},
|
||||
"zh": {
|
||||
"0 objects": "0 个对象",
|
||||
"ASSEMBLY": "装配",
|
||||
"Add Drawing Revision": "添加图纸修订",
|
||||
"Add Revision": "添加修订",
|
||||
"Add sheet (not implemented)": "添加图纸页(未实现)",
|
||||
"Align Annotations": "对齐标注",
|
||||
"Align Notes": "对齐注释",
|
||||
"Align Views": "对齐视图",
|
||||
"Annotate": "标注",
|
||||
"Apply (not implemented)": "应用(未实现)",
|
||||
"Auto (Theme)": "自动(跟随主题)",
|
||||
"Balloon": "零件序号",
|
||||
"Base View": "基本视图",
|
||||
"Border & template": "图框与模板",
|
||||
"Browse Drawing Resources": "浏览制图资源",
|
||||
"CHANGES": "更改",
|
||||
"Centre Mark": "中心标记",
|
||||
"Check Drawing": "检查图纸",
|
||||
"Close drawing panel": "关闭制图面板",
|
||||
"DRAWING ANNOTATE": "图纸标注",
|
||||
"DRAWING LAYOUT": "图纸布局",
|
||||
"DRAWING PARTS": "图纸零件",
|
||||
"DRAWING REVIEW": "图纸审阅",
|
||||
"Datum": "基准",
|
||||
"Detail View": "局部放大视图",
|
||||
"Dock at bottom": "停靠到底部",
|
||||
"Dock at top": "停靠到顶部",
|
||||
"Dock beneath Browser": "停靠到浏览器下方",
|
||||
"Dock left": "停靠到左侧",
|
||||
"Dock right": "停靠到右侧",
|
||||
"Dock sheet tabs": "停靠图纸页选项卡",
|
||||
"Drafting defaults": "制图默认设置",
|
||||
"Drawing & title block": "图纸与标题栏",
|
||||
"Drawing Dimension": "图纸尺寸标注",
|
||||
"Drawing Line": "图纸线段",
|
||||
"Drawing Note": "图纸注释",
|
||||
"Drawing defaults (not available)": "图纸默认设置(不可用)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "图纸文件和纸张渲染器尚未实现。规划中的工具会显示已定义的选项。",
|
||||
"Drawing workspace": "工程图工作区",
|
||||
"Edit Drawing Table": "编辑图纸表格",
|
||||
"Edit Table": "编辑表格",
|
||||
"Export Drawing": "导出图纸",
|
||||
"Finish the current operation before entering Drawing": "请先完成当前操作,再进入工程图工作区",
|
||||
"Hole Callout": "孔标注",
|
||||
"Image": "图像",
|
||||
"Inherited from parent view": "继承父视图",
|
||||
"Insert Image": "插入图像",
|
||||
"Leader": "引线",
|
||||
"Leader Note": "引线注释",
|
||||
"Manage sheets (not implemented)": "管理图纸页(未实现)",
|
||||
"Manage sheets and docking": "管理图纸页与停靠位置",
|
||||
"NOTES": "注释",
|
||||
"No drawing document": "没有图纸文档",
|
||||
"No drawing geometry available": "没有可用的图纸几何",
|
||||
"No drawing objects": "没有图纸对象",
|
||||
"No drawing sheet is available yet.": "目前尚无可用的图纸页。",
|
||||
"No drawing view available": "没有可用的图纸视图",
|
||||
"No sheet available": "没有可用的图纸页",
|
||||
"Not available": "不可用",
|
||||
"Not configured": "未配置",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "尚未实现。这些选项展示规划中的界面,不会更改任何图纸数据。",
|
||||
"Note": "注释",
|
||||
"Paper & layout": "纸张与布局",
|
||||
"Paper colour": "纸张颜色",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "图纸页渲染器实现后即可设置纸张颜色。",
|
||||
"Parent view": "父视图",
|
||||
"Parts & Tables": "零件与表格",
|
||||
"Parts List": "零件明细表",
|
||||
"Print": "打印",
|
||||
"Print Drawing": "打印图纸",
|
||||
"Profile & units": "制图规范与单位",
|
||||
"Projected View": "投影视图",
|
||||
"RESOURCES": "资源",
|
||||
"REVISION": "修订",
|
||||
"References": "引用",
|
||||
"Resources": "资源",
|
||||
"Return to Design to use this command.": "请返回设计工作区使用此命令。",
|
||||
"Review": "审阅",
|
||||
"Revision Table": "修订表",
|
||||
"Revision Tbl.": "修订表",
|
||||
"SOURCE": "源设计",
|
||||
"SYMBOLS": "符号",
|
||||
"Screen appearance": "屏幕外观",
|
||||
"Section View": "剖视图",
|
||||
"Sheet Settings": "图纸页设置",
|
||||
"Sheet unavailable": "图纸页不可用",
|
||||
"Size & orientation": "尺寸与方向",
|
||||
"Source BOM": "源设计 BOM",
|
||||
"Source Flat": "源设计展开图",
|
||||
"Source design": "源设计",
|
||||
"Source properties": "源设计属性",
|
||||
"Space": "均布",
|
||||
"Space Annotations": "等间距分布标注",
|
||||
"Space Notes": "等间距分布注释",
|
||||
"TABLES": "表格",
|
||||
"Text style": "文字样式",
|
||||
"Title": "标题",
|
||||
"Title block": "标题栏",
|
||||
"UI preview": "界面预览",
|
||||
"Units & precision": "单位与精度",
|
||||
"Update Drawing": "更新图纸",
|
||||
"View": "视图"
|
||||
},
|
||||
"de": {
|
||||
"0 objects": "0 Objekte",
|
||||
"ASSEMBLY": "BAUGRUPPE",
|
||||
"Add Drawing Revision": "Zeichnungsrevision hinzufügen",
|
||||
"Add Revision": "Revision hinzufügen",
|
||||
"Add sheet (not implemented)": "Blatt hinzufügen (nicht implementiert)",
|
||||
"Align Annotations": "Beschriftungen ausrichten",
|
||||
"Align Notes": "Hinweise ausrichten",
|
||||
"Align Views": "Ansichten ausrichten",
|
||||
"Annotate": "Beschriften",
|
||||
"Apply (not implemented)": "Anwenden (nicht implementiert)",
|
||||
"Auto (Theme)": "Automatisch (Design)",
|
||||
"Balloon": "Positionssymbol",
|
||||
"Base View": "Basisansicht",
|
||||
"Border & template": "Rahmen & Vorlage",
|
||||
"Browse Drawing Resources": "Zeichnungsressourcen durchsuchen",
|
||||
"CHANGES": "ÄNDERUNGEN",
|
||||
"Centre Mark": "Mittelpunktmarkierung",
|
||||
"Check Drawing": "Zeichnung prüfen",
|
||||
"Close drawing panel": "Zeichnungspanel schließen",
|
||||
"DRAWING ANNOTATE": "ZEICHNUNG BESCHRIFTEN",
|
||||
"DRAWING LAYOUT": "ZEICHNUNGSLAYOUT",
|
||||
"DRAWING PARTS": "ZEICHNUNGSTEILE",
|
||||
"DRAWING REVIEW": "ZEICHNUNGSPRÜFUNG",
|
||||
"Datum": "Bezug",
|
||||
"Detail View": "Detailansicht",
|
||||
"Dock at bottom": "Unten andocken",
|
||||
"Dock at top": "Oben andocken",
|
||||
"Dock beneath Browser": "Unter dem Browser andocken",
|
||||
"Dock left": "Links andocken",
|
||||
"Dock right": "Rechts andocken",
|
||||
"Dock sheet tabs": "Blattregister andocken",
|
||||
"Drafting defaults": "Vorgaben für die Zeichnungserstellung",
|
||||
"Drawing & title block": "Zeichnung & Schriftfeld",
|
||||
"Drawing Dimension": "Zeichnungsbemaßung",
|
||||
"Drawing Line": "Zeichnungslinie",
|
||||
"Drawing Note": "Zeichnungshinweis",
|
||||
"Drawing defaults (not available)": "Zeichnungsvorgaben (nicht verfügbar)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "Zeichnungsdateien und die Papierdarstellung sind noch nicht implementiert. Geplante Werkzeuge zeigen ihre Optionen an, sofern diese bereits festgelegt sind.",
|
||||
"Drawing workspace": "Zeichnungsarbeitsbereich",
|
||||
"Edit Drawing Table": "Zeichnungstabelle bearbeiten",
|
||||
"Edit Table": "Tabelle bearbeiten",
|
||||
"Export Drawing": "Zeichnung exportieren",
|
||||
"Finish the current operation before entering Drawing": "Beenden Sie den aktuellen Vorgang, bevor Sie zum Zeichnungsarbeitsbereich wechseln",
|
||||
"Hole Callout": "Bohrungsangabe",
|
||||
"Image": "Bild",
|
||||
"Inherited from parent view": "Von der übergeordneten Ansicht übernommen",
|
||||
"Insert Image": "Bild einfügen",
|
||||
"Leader": "Bezugslinie",
|
||||
"Leader Note": "Hinweis mit Bezugslinie",
|
||||
"Manage sheets (not implemented)": "Blätter verwalten (nicht implementiert)",
|
||||
"Manage sheets and docking": "Blätter und Andockpositionen verwalten",
|
||||
"NOTES": "HINWEISE",
|
||||
"No drawing document": "Kein Zeichnungsdokument",
|
||||
"No drawing geometry available": "Keine Zeichnungsgeometrie verfügbar",
|
||||
"No drawing objects": "Keine Zeichnungsobjekte",
|
||||
"No drawing sheet is available yet.": "Es ist noch kein Zeichnungsblatt verfügbar.",
|
||||
"No drawing view available": "Keine Zeichnungsansicht verfügbar",
|
||||
"No sheet available": "Kein Blatt verfügbar",
|
||||
"Not available": "Nicht verfügbar",
|
||||
"Not configured": "Nicht konfiguriert",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "Nicht implementiert. Diese Optionen beschreiben die geplante Oberfläche; es werden keine Zeichnungsdaten geändert.",
|
||||
"Note": "Hinweis",
|
||||
"Paper & layout": "Papier & Layout",
|
||||
"Paper colour": "Papierfarbe",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "Die Papierfarbe wird verfügbar sein, sobald die Blattdarstellung implementiert ist.",
|
||||
"Parent view": "Übergeordnete Ansicht",
|
||||
"Parts & Tables": "Teile & Tabellen",
|
||||
"Parts List": "Teileliste",
|
||||
"Print": "Drucken",
|
||||
"Print Drawing": "Zeichnung drucken",
|
||||
"Profile & units": "Profil & Einheiten",
|
||||
"Projected View": "Projizierte Ansicht",
|
||||
"RESOURCES": "RESSOURCEN",
|
||||
"REVISION": "REVISION",
|
||||
"References": "Referenzen",
|
||||
"Resources": "Ressourcen",
|
||||
"Return to Design to use this command.": "Wechseln Sie zum Design-Arbeitsbereich zurück, um diesen Befehl zu verwenden.",
|
||||
"Review": "Prüfen",
|
||||
"Revision Table": "Revisionstabelle",
|
||||
"Revision Tbl.": "Revisionstbl.",
|
||||
"SOURCE": "QUELLE",
|
||||
"SYMBOLS": "SYMBOLE",
|
||||
"Screen appearance": "Bildschirmdarstellung",
|
||||
"Section View": "Schnittansicht",
|
||||
"Sheet Settings": "Blatteinstellungen",
|
||||
"Sheet unavailable": "Blatt nicht verfügbar",
|
||||
"Size & orientation": "Größe & Ausrichtung",
|
||||
"Source BOM": "Stückliste der Quelle",
|
||||
"Source Flat": "Flachmuster der Quelle",
|
||||
"Source design": "Quelldesign",
|
||||
"Source properties": "Eigenschaften der Quelle",
|
||||
"Space": "Abstände anpassen",
|
||||
"Space Annotations": "Beschriftungsabstände anpassen",
|
||||
"Space Notes": "Hinweisabstände anpassen",
|
||||
"TABLES": "TABELLEN",
|
||||
"Text style": "Textstil",
|
||||
"Title": "Titel",
|
||||
"Title block": "Schriftfeld",
|
||||
"UI preview": "Oberflächenvorschau",
|
||||
"Units & precision": "Einheiten & Genauigkeit",
|
||||
"Update Drawing": "Zeichnung aktualisieren",
|
||||
"View": "Ansicht"
|
||||
},
|
||||
"ko": {
|
||||
"0 objects": "객체 0개",
|
||||
"ASSEMBLY": "조립품",
|
||||
"Add Drawing Revision": "도면 리비전 추가",
|
||||
"Add Revision": "리비전 추가",
|
||||
"Add sheet (not implemented)": "시트 추가 (미구현)",
|
||||
"Align Annotations": "주석 정렬",
|
||||
"Align Notes": "메모 정렬",
|
||||
"Align Views": "뷰 정렬",
|
||||
"Annotate": "주석 달기",
|
||||
"Apply (not implemented)": "적용 (미구현)",
|
||||
"Auto (Theme)": "자동 (테마)",
|
||||
"Balloon": "항목 번호 기호",
|
||||
"Base View": "기본 뷰",
|
||||
"Border & template": "테두리 및 템플릿",
|
||||
"Browse Drawing Resources": "도면 리소스 찾아보기",
|
||||
"CHANGES": "변경 사항",
|
||||
"Centre Mark": "중심 표시",
|
||||
"Check Drawing": "도면 검사",
|
||||
"Close drawing panel": "도면 패널 닫기",
|
||||
"DRAWING ANNOTATE": "도면 주석",
|
||||
"DRAWING LAYOUT": "도면 배치",
|
||||
"DRAWING PARTS": "도면 부품",
|
||||
"DRAWING REVIEW": "도면 검토",
|
||||
"Datum": "기준",
|
||||
"Detail View": "상세 뷰",
|
||||
"Dock at bottom": "아래쪽에 고정",
|
||||
"Dock at top": "위쪽에 고정",
|
||||
"Dock beneath Browser": "브라우저 아래에 고정",
|
||||
"Dock left": "왼쪽에 고정",
|
||||
"Dock right": "오른쪽에 고정",
|
||||
"Dock sheet tabs": "시트 탭 고정",
|
||||
"Drafting defaults": "도면 작성 기본 설정",
|
||||
"Drawing & title block": "도면 및 표제란",
|
||||
"Drawing Dimension": "도면 치수",
|
||||
"Drawing Line": "도면 선",
|
||||
"Drawing Note": "도면 메모",
|
||||
"Drawing defaults (not available)": "도면 기본 설정 (사용 불가)",
|
||||
"Drawing files and the paper renderer are not implemented. Planned tools show their options where defined.": "도면 파일과 용지 렌더러는 아직 구현되지 않았습니다. 예정된 도구는 정의된 옵션이 있는 경우 해당 옵션을 표시합니다.",
|
||||
"Drawing workspace": "도면 작업 공간",
|
||||
"Edit Drawing Table": "도면 표 편집",
|
||||
"Edit Table": "표 편집",
|
||||
"Export Drawing": "도면 내보내기",
|
||||
"Finish the current operation before entering Drawing": "도면 작업 공간으로 전환하기 전에 현재 작업을 완료하세요",
|
||||
"Hole Callout": "구멍 주석",
|
||||
"Image": "이미지",
|
||||
"Inherited from parent view": "상위 뷰에서 상속됨",
|
||||
"Insert Image": "이미지 삽입",
|
||||
"Leader": "지시선",
|
||||
"Leader Note": "지시선 메모",
|
||||
"Manage sheets (not implemented)": "시트 관리 (미구현)",
|
||||
"Manage sheets and docking": "시트 및 고정 위치 관리",
|
||||
"NOTES": "메모",
|
||||
"No drawing document": "도면 문서 없음",
|
||||
"No drawing geometry available": "사용 가능한 도면 형상 없음",
|
||||
"No drawing objects": "도면 객체 없음",
|
||||
"No drawing sheet is available yet.": "아직 사용 가능한 도면 시트가 없습니다.",
|
||||
"No drawing view available": "사용 가능한 도면 뷰 없음",
|
||||
"No sheet available": "사용 가능한 시트 없음",
|
||||
"Not available": "사용 불가",
|
||||
"Not configured": "설정되지 않음",
|
||||
"Not implemented. These options describe the planned interface; no drawing data is changed.": "아직 구현되지 않았습니다. 이 옵션은 예정된 인터페이스를 설명하며 도면 데이터를 변경하지 않습니다.",
|
||||
"Note": "메모",
|
||||
"Paper & layout": "용지 및 배치",
|
||||
"Paper colour": "용지 색상",
|
||||
"Paper colour will be available when the sheet renderer is implemented.": "시트 렌더러가 구현되면 용지 색상을 설정할 수 있습니다.",
|
||||
"Parent view": "상위 뷰",
|
||||
"Parts & Tables": "부품 및 표",
|
||||
"Parts List": "부품 목록",
|
||||
"Print": "인쇄",
|
||||
"Print Drawing": "도면 인쇄",
|
||||
"Profile & units": "프로파일 및 단위",
|
||||
"Projected View": "투영 뷰",
|
||||
"RESOURCES": "리소스",
|
||||
"REVISION": "리비전",
|
||||
"References": "참조",
|
||||
"Resources": "리소스",
|
||||
"Return to Design to use this command.": "이 명령을 사용하려면 디자인 작업 공간으로 돌아가세요.",
|
||||
"Review": "검토",
|
||||
"Revision Table": "리비전 표",
|
||||
"Revision Tbl.": "리비전 표",
|
||||
"SOURCE": "원본",
|
||||
"SYMBOLS": "기호",
|
||||
"Screen appearance": "화면 모양",
|
||||
"Section View": "단면 뷰",
|
||||
"Sheet Settings": "시트 설정",
|
||||
"Sheet unavailable": "시트 사용 불가",
|
||||
"Size & orientation": "크기 및 방향",
|
||||
"Source BOM": "원본 자재 명세서",
|
||||
"Source Flat": "원본 전개도",
|
||||
"Source design": "원본 디자인",
|
||||
"Source properties": "원본 속성",
|
||||
"Space": "간격 조정",
|
||||
"Space Annotations": "주석 간격 조정",
|
||||
"Space Notes": "메모 간격 조정",
|
||||
"TABLES": "표",
|
||||
"Text style": "텍스트 스타일",
|
||||
"Title": "제목",
|
||||
"Title block": "표제란",
|
||||
"UI preview": "인터페이스 미리보기",
|
||||
"Units & precision": "단위 및 정밀도",
|
||||
"Update Drawing": "도면 업데이트",
|
||||
"View": "뷰"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
| assets/icons/ui/design/svg/model/drawing_base_view.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_projected_view.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_section_view.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_detail_view.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_align_views.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_image.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_dimension.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_hole_callout.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_leader.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_datum.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_centre_mark.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_align_annotations.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_space_annotations.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_parts_list.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_balloon.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_edit_table.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_revision_table.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_update.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_check.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_revision.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_print.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/drawing_resources.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
@@ -0,0 +1,86 @@
|
||||
//! Provisional Drawing ribbon metadata, not a drawing-file or persistence API.
|
||||
//!
|
||||
//! These entries reserve discoverable homes while the owning Drawing contracts land.
|
||||
//! JSON execution always refuses; the UI may show reviewed option layouts separately.
|
||||
//! `drawing.export.planned` keeps the disabled UI action separate from `drawing.export`
|
||||
//! (the sheet export provider). View previews likewise use `.planned` ids so the
|
||||
//! real base-view and projected-view providers keep their executable contracts.
|
||||
|
||||
use super::CommandSpec;
|
||||
|
||||
/// Artwork awaiting owner review. Runtime assignments remain the intentional placeholder.
|
||||
pub const PENDING_ICONS: &[(&str, &str)] = &[
|
||||
("drawing.view.base.planned", "drawing_base_view"),
|
||||
("drawing.view.projected.planned", "drawing_projected_view"),
|
||||
("drawing.view.section", "drawing_section_view"),
|
||||
("drawing.view.detail", "drawing_detail_view"),
|
||||
("drawing.views.align", "drawing_align_views"),
|
||||
("drawing.image", "drawing_image"),
|
||||
("drawing.resources", "drawing_resources"),
|
||||
("drawing.dimension", "drawing_dimension"),
|
||||
("drawing.hole_callout", "drawing_hole_callout"),
|
||||
("drawing.leader", "drawing_leader"),
|
||||
("drawing.datum", "drawing_datum"),
|
||||
("drawing.centre_mark", "drawing_centre_mark"),
|
||||
("drawing.annotations.align", "drawing_align_annotations"),
|
||||
("drawing.annotations.space", "drawing_space_annotations"),
|
||||
("drawing.parts_list", "drawing_parts_list"),
|
||||
("drawing.balloon", "drawing_balloon"),
|
||||
("drawing.table.edit", "drawing_edit_table"),
|
||||
("drawing.revision_table", "drawing_revision_table"),
|
||||
("drawing.update", "drawing_update"),
|
||||
("drawing.check", "drawing_check"),
|
||||
("drawing.revision", "drawing_revision"),
|
||||
("drawing.print", "drawing_print"),
|
||||
];
|
||||
|
||||
pub fn pending_icon(id: &str) -> Option<&'static str> {
|
||||
PENDING_ICONS.iter().find(|(command, _)| *command == id).map(|(_, icon)| *icon)
|
||||
}
|
||||
|
||||
pub static COMMANDS: &[CommandSpec] = &[
|
||||
planned!("drawing.view.base.planned", "Base View", "4816").at("DRAWING LAYOUT", "VIEWS").icon("placeholder"),
|
||||
planned!("drawing.view.projected.planned", "Projected View", "4816").at("DRAWING LAYOUT", "VIEWS").icon("placeholder"),
|
||||
planned!("drawing.view.section", "Section View", "4816").at("DRAWING LAYOUT", "VIEWS").icon("placeholder"),
|
||||
planned!("drawing.view.detail", "Detail View", "4816").at("DRAWING LAYOUT", "VIEWS").icon("placeholder"),
|
||||
planned!("drawing.views.align", "Align Views", "4894").at("DRAWING LAYOUT", "ARRANGE").icon("placeholder"),
|
||||
planned!("drawing.image", "Insert Image", "4894").at("DRAWING LAYOUT", "INSERT").icon("placeholder"),
|
||||
planned!("drawing.line", "Drawing Line", "4890").at("DRAWING LAYOUT", "INSERT").icon("line"),
|
||||
planned!("drawing.resources", "Browse Drawing Resources", "4894").at("DRAWING LAYOUT", "RESOURCES").icon("placeholder"),
|
||||
planned!("drawing.dimension", "Drawing Dimension", "4896").at("DRAWING ANNOTATE", "MEASURE").icon("placeholder"),
|
||||
planned!("drawing.hole_callout", "Hole Callout", "4896").at("DRAWING ANNOTATE", "MEASURE").icon("placeholder"),
|
||||
planned!("drawing.note", "Drawing Note", "4896").at("DRAWING ANNOTATE", "NOTES").icon("text"),
|
||||
planned!("drawing.leader", "Leader Note", "4896").at("DRAWING ANNOTATE", "NOTES").icon("placeholder"),
|
||||
planned!("drawing.datum", "Datum", "4896").at("DRAWING ANNOTATE", "SYMBOLS").icon("placeholder"),
|
||||
planned!("drawing.centre_mark", "Centre Mark", "4896").at("DRAWING ANNOTATE", "SYMBOLS").icon("placeholder"),
|
||||
planned!("drawing.annotations.align", "Align Annotations", "4896").at("DRAWING ANNOTATE", "ARRANGE").icon("placeholder"),
|
||||
planned!("drawing.annotations.space", "Space Annotations", "4896").at("DRAWING ANNOTATE", "ARRANGE").icon("placeholder"),
|
||||
planned!("drawing.parts_list", "Parts List", "3874").at("DRAWING PARTS", "ASSEMBLY").icon("placeholder"),
|
||||
planned!("drawing.balloon", "Balloon", "3874").at("DRAWING PARTS", "ASSEMBLY").icon("placeholder"),
|
||||
planned!("drawing.table.edit", "Edit Drawing Table", "4894").at("DRAWING PARTS", "TABLES").icon("placeholder"),
|
||||
planned!("drawing.revision_table", "Revision Table", "4894").at("DRAWING PARTS", "TABLES").icon("placeholder"),
|
||||
planned!("drawing.update", "Update Drawing", "4892").at("DRAWING REVIEW", "CHANGES").icon("placeholder"),
|
||||
planned!("drawing.check", "Check Drawing", "4894").at("DRAWING REVIEW", "CHANGES").icon("placeholder"),
|
||||
planned!("drawing.revision", "Add Drawing Revision", "4894").at("DRAWING REVIEW", "REVISION").icon("placeholder"),
|
||||
planned!("drawing.export.planned", "Export Drawing", "4890").at("DRAWING REVIEW", "OUTPUT").icon("export"),
|
||||
planned!("drawing.print", "Print Drawing", "4890").at("DRAWING REVIEW", "OUTPUT").icon("placeholder"),
|
||||
];
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn drawing_entries_are_metadata_only() {
|
||||
let mut session = crate::Session::new(Default::default());
|
||||
let before = serde_json::to_value(&session.doc).unwrap();
|
||||
for id in ["drawing.export", "drawing.view.base", "drawing.view.projected"] {
|
||||
assert!(crate::find_command(id).unwrap().planned.is_none(), "the executable provider {id} stays registered");
|
||||
}
|
||||
assert!(crate::find_command("drawing.export.planned").unwrap().planned.is_some(), "the scaffold export stays disabled");
|
||||
for command in super::COMMANDS {
|
||||
assert!(command.planned.is_some(), "{}", command.id);
|
||||
assert!(!command.info(&session).enabled, "{}", command.id);
|
||||
assert!((command.run)(&mut session, &serde_json::json!({})).is_err(), "{}", command.id);
|
||||
}
|
||||
assert_eq!(serde_json::to_value(&session.doc).unwrap(), before);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@ mod drawing_export;
|
||||
mod drawing_revision;
|
||||
mod drawing_scene;
|
||||
mod drawing_sheet;
|
||||
mod drawing_ui;
|
||||
mod drawing_view;
|
||||
mod edit;
|
||||
mod face;
|
||||
@@ -47,6 +48,7 @@ mod join_pieces;
|
||||
pub(crate) use bom::{SAME_AS_COMPONENT, occurrence_bom_type, record_item_numbers};
|
||||
pub(crate) use component::{path_ids, refuse_filled_virtual};
|
||||
pub(crate) use construct::{GeoForm, GeoFormError, REF as GEO_REF_FORMS, SKETCH_PART, geo_form, is_model_axis};
|
||||
pub use drawing_ui::pending_icon as drawing_pending_icon;
|
||||
pub(crate) use face::{FaceRefForm, face_ref_form};
|
||||
pub(crate) use features::profiles as feature_profiles;
|
||||
pub(crate) use features::{EdgeRefForm, PLANE_FORMS, axis_dir_form, axis_line_object, edge_ref_form, explicit_plane, plane_name_problem, sketch_key};
|
||||
@@ -394,6 +396,7 @@ pub fn command_specs() -> Vec<&'static CommandSpec> {
|
||||
v.extend(drawing_view::COMMANDS.iter());
|
||||
v.extend(settings::COMMANDS.iter());
|
||||
v.extend(diagnostics::COMMANDS.iter());
|
||||
v.extend(drawing_ui::COMMANDS.iter());
|
||||
v.extend(render::COMMANDS.iter());
|
||||
v.extend(render_ui::COMMANDS.iter());
|
||||
v
|
||||
|
||||
@@ -908,15 +908,16 @@ pub fn browser(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
finish_browser(app, ui, acts);
|
||||
return;
|
||||
}
|
||||
let stacked = crate::timeline_left::stacked(app);
|
||||
let stacked = !crate::drawing_workspace::active(app) && crate::timeline_left::stacked(app);
|
||||
let container =
|
||||
Container::new(ui.ctx(), Surface::Panel).joined(crate::ui_kit::DockEdges { left: true, top: true, bottom: stacked, ..Default::default() });
|
||||
let gap = container.gap();
|
||||
let width = browser_width(app.preferences.browser_width);
|
||||
let vertical = app.ui.show_timeline && app.preferences.timeline_left;
|
||||
let vertical = app.ui.show_timeline && app.preferences.timeline_left && !crate::drawing_workspace::active(app);
|
||||
let mut available = available;
|
||||
if !stacked {
|
||||
available.min.x += crate::agent_chat::left_inset(app, available);
|
||||
available.min.x += crate::drawing_workspace::left_inset(app).min(available.width().max(0.0));
|
||||
}
|
||||
if vertical
|
||||
&& !stacked
|
||||
@@ -1105,14 +1106,16 @@ fn browser_contents(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Actions, c
|
||||
if !render {
|
||||
browser_header(app, ui, collapsible);
|
||||
}
|
||||
let stacked = !render && crate::timeline_left::stacked(app);
|
||||
let stacked = !render && !crate::drawing_workspace::active(app) && crate::timeline_left::stacked(app);
|
||||
if !render
|
||||
&& collapsible
|
||||
&& (if stacked {
|
||||
(app.preferences.timeline_left_browser_collapsed && app.shell.history_folding.is_none_or(|p| p[0] >= 1.0))
|
||||
|| ui.available_height() <= t.space_3
|
||||
} else {
|
||||
app.preferences.browser_collapsed && !reveals(app) && !(app.ui.show_timeline && app.preferences.timeline_left)
|
||||
app.preferences.browser_collapsed
|
||||
&& !reveals(app)
|
||||
&& !(app.ui.show_timeline && app.preferences.timeline_left && !crate::drawing_workspace::active(app))
|
||||
})
|
||||
{
|
||||
return;
|
||||
@@ -1141,8 +1144,8 @@ fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) {
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(ui.available_width(), HEADER_H), Sense::hover());
|
||||
let response = ui.interact(rect, ui.id().with("browser_header"), if collapsible { Sense::click() } else { Sense::hover() });
|
||||
crate::scenario::publish_target("browser:header", rect);
|
||||
let vertical = app.ui.show_timeline && app.preferences.timeline_left;
|
||||
let stacked = crate::timeline_left::stacked(app);
|
||||
let vertical = app.ui.show_timeline && app.preferences.timeline_left && !crate::drawing_workspace::active(app);
|
||||
let stacked = !crate::drawing_workspace::active(app) && crate::timeline_left::stacked(app);
|
||||
let collapsed =
|
||||
collapsible && if stacked { app.preferences.timeline_left_browser_collapsed } else { !vertical && app.preferences.browser_collapsed };
|
||||
if vertical {
|
||||
@@ -1161,9 +1164,13 @@ fn browser_header(app: &mut SolveApp, ui: &mut egui::Ui, collapsible: bool) {
|
||||
crate::ui_kit::disclosure(ui.painter(), pos2(right - t.form_section_icon_size / 2.0, rect.center().y), !collapsed, t.text_dim);
|
||||
right -= 18.0;
|
||||
}
|
||||
let bodies = app.session.model.state().bodies.len();
|
||||
let key = if bodies == 1 { "ui.browser_bodies_one" } else { "ui.browser_bodies_other" };
|
||||
let count = crate::i18n::format(key, &[("count", &bodies.to_string())]);
|
||||
let count = if crate::drawing_workspace::active(app) {
|
||||
crate::i18n::tr_label("0 objects").to_string()
|
||||
} else {
|
||||
let bodies = app.session.model.state().bodies.len();
|
||||
let key = if bodies == 1 { "ui.browser_bodies_one" } else { "ui.browser_bodies_other" };
|
||||
crate::i18n::format(key, &[("count", &bodies.to_string())])
|
||||
};
|
||||
let galley = ui.painter().layout_no_wrap(count, FontId::proportional(t.font_body), t.text_dim);
|
||||
ui.painter().galley(pos2(right - galley.size().x, rect.center().y - galley.size().y / 2.0), galley, t.text_dim);
|
||||
if collapsible {
|
||||
@@ -1250,6 +1257,10 @@ fn browser_rows_hugging(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Action
|
||||
}
|
||||
|
||||
fn rows_scrolling(app: &mut SolveApp, ui: &mut egui::Ui, acts: &mut Actions, shrink_height: bool) {
|
||||
if crate::drawing_workspace::active(app) {
|
||||
crate::drawing_workspace::browser_contents(app, ui);
|
||||
return;
|
||||
}
|
||||
prepare_model(app);
|
||||
acts.lit = Lit::of(app);
|
||||
let mut cache = app.tree.cache.take().unwrap_or_default();
|
||||
|
||||
@@ -407,16 +407,27 @@ pub fn items(app: &SolveApp, target: &Target) -> Vec<Item> {
|
||||
Target::HistoryControls => crate::timeline::transport_items(app),
|
||||
Target::Workspace => {
|
||||
let rendering = crate::render_workspace::active(app);
|
||||
let drawing = crate::drawing_workspace::active(app);
|
||||
let mut v = vec![
|
||||
act("ui.workspace.design", "Design", "ws_design").with(json!({"workspace":"design"})).key(if rendering { "" } else { "current" }),
|
||||
act("ui.workspace.design", "Design", "ws_design").with(json!({"workspace":"design"})).key(if rendering || drawing {
|
||||
""
|
||||
} else {
|
||||
"current"
|
||||
}),
|
||||
act("ui.workspace.render", "Render", "ws_render")
|
||||
.with(json!({"workspace":"render"}))
|
||||
.key(if rendering { "current" } else { "" })
|
||||
.on(app.session.active_sketch.is_none() && app.tool.is_none() && app.dialog.is_none() && !app.file_load.busy()),
|
||||
];
|
||||
for (w, icon) in [("Animation", "ws_animate"), ("Simulation", ""), ("Manufacture", ""), ("Drawing", "ws_drawing")] {
|
||||
for (w, icon) in [("Animation", "ws_animate"), ("Simulation", ""), ("Manufacture", "")] {
|
||||
v.push(act("ui.workspace", w, icon).key("coming later").on(false));
|
||||
}
|
||||
v.push(
|
||||
act("ui.workspace.drawing", "Drawing", "ws_drawing")
|
||||
.with(json!({"workspace":"drawing"}))
|
||||
.key(if drawing { "current" } else { "UI preview" })
|
||||
.on(app.session.active_sketch.is_none() && app.tool.is_none() && app.dialog.is_none() && !app.file_load.busy()),
|
||||
);
|
||||
v
|
||||
}
|
||||
Target::Units => crate::prefs::UNITS
|
||||
@@ -1627,11 +1638,10 @@ pub fn run_item(app: &mut SolveApp, item: &Item, at: Pos2) {
|
||||
app.set_status(e, true);
|
||||
}
|
||||
}
|
||||
"ui.workspace" | "ui.workspace.design" | "ui.workspace.render" => {
|
||||
if let Some(workspace) = p.get("workspace").and_then(Value::as_str)
|
||||
&& let Err(e) = crate::render_workspace::enter(app, workspace == "render")
|
||||
{
|
||||
app.set_status(e, true);
|
||||
"ui.workspace" | "ui.workspace.design" | "ui.workspace.render" | "ui.workspace.drawing" => {
|
||||
let result = p.get("workspace").and_then(Value::as_str).map_or(Ok(()), |workspace| crate::workspace::enter(app, workspace));
|
||||
if let Err(error) = result {
|
||||
app.set_status(error, true);
|
||||
}
|
||||
}
|
||||
"ui.findJoint" => {
|
||||
@@ -2966,7 +2976,7 @@ mod workspace_menu_tests {
|
||||
assert!(rows.windows(2).all(|pair| pair[0].bottom() <= pair[1].top()));
|
||||
assert!(rows[0].top() > opener.bottom());
|
||||
assert_eq!(rows[0].width(), 450.0);
|
||||
assert!(items(&h.app, &Target::Workspace).iter().skip(2).all(|item| !item.enabled));
|
||||
assert!(items(&h.app, &Target::Workspace).iter().skip(2).filter(|item| item.id != "ui.workspace.drawing").all(|item| !item.enabled));
|
||||
h.step(&json!({"click":{"handle":"workspace:menu:animation"}})).unwrap();
|
||||
assert!(!crate::render_workspace::active(&h.app));
|
||||
assert!(h.app.viewport.context_menu.is_some());
|
||||
|
||||
@@ -541,15 +541,14 @@ fn dispatch(app: &mut SolveApp, ctx: &egui::Context, method: &str, p: &Value) ->
|
||||
match method {
|
||||
"ui.status_fixture" => wrap(crate::viewport_status_fixture::control(app, p)),
|
||||
"ui.workspace" => {
|
||||
if let Some(workspace) = s("workspace") {
|
||||
if !matches!(workspace, "design" | "render") {
|
||||
return err("workspace must be design or render");
|
||||
}
|
||||
if let Err(e) = crate::render_workspace::enter(app, workspace == "render") {
|
||||
return err(e);
|
||||
}
|
||||
if let Some(workspace) = s("workspace")
|
||||
&& let Err(e) = crate::workspace::enter(app, workspace)
|
||||
{
|
||||
return err(e);
|
||||
}
|
||||
ok(json!({"workspace":if crate::render_workspace::active(app) {"render"} else {"design"}}))
|
||||
ok(
|
||||
json!({"workspace":if crate::drawing_workspace::active(app) {"drawing"} else if crate::render_workspace::active(app) {"render"} else {"design"}}),
|
||||
)
|
||||
}
|
||||
"engine.execute" | "command" => {
|
||||
let Some(id) = s("command").or(s("id")) else { return err("missing `command`") };
|
||||
@@ -709,10 +708,24 @@ fn dispatch(app: &mut SolveApp, ctx: &egui::Context, method: &str, p: &Value) ->
|
||||
v.hidden_folders.push(SKETCHES_FOLDER.to_string());
|
||||
}
|
||||
}
|
||||
if crate::workspace::render_tab(&u.tab) != crate::render_workspace::active(app)
|
||||
&& let Err(e) = crate::render_workspace::enter(app, crate::workspace::render_tab(&u.tab))
|
||||
let destination = if crate::workspace::drawing_tab(&u.tab) {
|
||||
"drawing"
|
||||
} else if crate::workspace::render_tab(&u.tab) {
|
||||
"render"
|
||||
} else {
|
||||
"design"
|
||||
};
|
||||
let current = if crate::drawing_workspace::active(app) {
|
||||
"drawing"
|
||||
} else if crate::render_workspace::active(app) {
|
||||
"render"
|
||||
} else {
|
||||
"design"
|
||||
};
|
||||
if destination != current
|
||||
&& let Err(error) = crate::workspace::enter(app, destination)
|
||||
{
|
||||
return err(e);
|
||||
return err(error);
|
||||
}
|
||||
u.ribbon_context = app.ui.ribbon_context.clone();
|
||||
app.ui = u;
|
||||
@@ -723,6 +736,13 @@ fn dispatch(app: &mut SolveApp, ctx: &egui::Context, method: &str, p: &Value) ->
|
||||
}
|
||||
}
|
||||
"ui.view" => {
|
||||
if crate::drawing_workspace::active(app) {
|
||||
if crate::dialogs_sheet::flat_active(app) && matches!(s("view").unwrap_or("home"), "fit" | "home") {
|
||||
app.flat_view.fitted = false;
|
||||
return ok(json!({"flat_pattern":true,"fitted":false}));
|
||||
}
|
||||
return err("Drawing has no sheet renderer or camera yet");
|
||||
}
|
||||
let v = s("view").unwrap_or("home");
|
||||
app.viewport.orbit_free = None;
|
||||
if p.get("animate").and_then(Value::as_bool).unwrap_or(false) {
|
||||
|
||||
@@ -1371,3 +1371,42 @@ fn flat_pattern_navigation_slots_hide_orbit_controls_and_keep_fit_home() {
|
||||
assert!(crate::scenario::handle_at(&format!("nav:slot:{slot}")).is_some(), "{slot}");
|
||||
}
|
||||
}
|
||||
|
||||
/// Drawing reuses the source flat-pattern inspector without giving its camera to Design.
|
||||
#[test]
|
||||
fn source_flat_pattern_remains_visible_and_refittable_in_drawing() {
|
||||
for mode in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
let ctx = crate::fonts::test_context();
|
||||
let mut app = plate(mode);
|
||||
app.run("sheet.flange", json!({"sketch":"Base"})).unwrap();
|
||||
let flat = app.session.execute("sheet.flat_pattern", &json!({})).unwrap();
|
||||
let doc = app.session.doc.clone();
|
||||
let design = app.cam;
|
||||
crate::workspace::enter(&mut app, "drawing").unwrap();
|
||||
app.start("sheet.flat_pattern");
|
||||
let Some(Kind::Sheet(Sm::FlatPattern { data, .. })) = app.dialog.as_mut().map(|d| &mut d.kind) else { panic!("source flat inspector") };
|
||||
*data = Some(Ok(flat));
|
||||
let render = |app: &mut SolveApp| {
|
||||
let raw = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() };
|
||||
crate::test_support::frame(&ctx, raw, |ui| app.show_workspace(ui));
|
||||
};
|
||||
render(&mut app);
|
||||
assert!(app.flat_view.fitted, "the Drawing route paints the existing source inspector");
|
||||
let fitted = app.flat_view.cam;
|
||||
for view in ["fit", "home"] {
|
||||
app.flat_view.cam.pan(120.0, -80.0, 600.0);
|
||||
app.animate_view(view);
|
||||
assert!(!app.flat_view.fitted);
|
||||
render(&mut app);
|
||||
assert_eq!(app.flat_view.cam, fitted);
|
||||
assert_eq!(app.cam, design);
|
||||
}
|
||||
app.flat_view.cam.pan(50.0, 50.0, 600.0);
|
||||
app.fit_view();
|
||||
render(&mut app);
|
||||
assert_eq!(app.flat_view.cam, fitted);
|
||||
assert_eq!(app.session.doc, doc);
|
||||
assert_eq!(app.cam, design);
|
||||
assert!(crate::drawing_workspace::active(&app));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,597 @@
|
||||
//! Drawing's native layout anchor. No drawing document or paper scene is fabricated here.
|
||||
//! Missing tools expose only agreed options; source services keep their existing ownership.
|
||||
|
||||
use egui::{Rect, Sense, Stroke, pos2, vec2};
|
||||
|
||||
use crate::ui_kit::{self, Container, Edge, Surface};
|
||||
use crate::{SolveApp, theme::Tokens};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct State {
|
||||
previous_tab: Option<String>,
|
||||
pub settings_open: bool,
|
||||
options: Option<&'static str>,
|
||||
strip: Option<Rect>,
|
||||
strip_sizes: [Option<egui::Vec2>; 2],
|
||||
settings: Option<Rect>,
|
||||
}
|
||||
|
||||
pub fn active(app: &SolveApp) -> bool {
|
||||
matches!(app.ui.tab.as_str(), "DRAWING LAYOUT" | "DRAWING ANNOTATE" | "DRAWING PARTS" | "DRAWING REVIEW")
|
||||
}
|
||||
|
||||
pub fn enter(app: &mut SolveApp, drawing: bool) -> Result<(), String> {
|
||||
if drawing {
|
||||
if app.session.active_sketch.is_some() || app.tool.is_some() || app.dialog.is_some() || app.file_load.busy() {
|
||||
return Err("Finish the current operation before entering Drawing".into());
|
||||
}
|
||||
if !active(app) {
|
||||
app.drawing_workspace.previous_tab = Some(app.ui.tab.clone());
|
||||
app.ui.tab = "DRAWING LAYOUT".into();
|
||||
}
|
||||
} else if active(app) {
|
||||
app.ui.tab = app.drawing_workspace.previous_tab.take().unwrap_or_else(|| "MODEL".into());
|
||||
app.drawing_workspace.options = None;
|
||||
app.drawing_workspace.strip = None;
|
||||
app.drawing_workspace.settings = None;
|
||||
}
|
||||
app.ui.ribbon_context = None;
|
||||
app.cancel_view_animation();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn reset_document(app: &mut SolveApp) {
|
||||
if active(app) {
|
||||
app.ui.tab = "MODEL".into();
|
||||
}
|
||||
app.drawing_workspace = State::default();
|
||||
}
|
||||
|
||||
pub fn can_open_options(id: &str) -> bool {
|
||||
matches!(id, "drawing.view.base.planned" | "drawing.view.projected.planned" | "drawing.dimension" | "drawing.note")
|
||||
}
|
||||
|
||||
/// Dismiss the current Drawing inspector without touching source or drawing data.
|
||||
pub fn close_panel(app: &mut SolveApp) -> bool {
|
||||
let open = app.drawing_workspace.settings_open || app.drawing_workspace.options.is_some();
|
||||
app.drawing_workspace.settings_open = false;
|
||||
app.drawing_workspace.options = None;
|
||||
app.drawing_workspace.settings = None;
|
||||
open
|
||||
}
|
||||
|
||||
/// Toolbar clicks may inspect known options. This never executes a missing CAD command.
|
||||
pub fn start(app: &mut SolveApp, id: &str) -> bool {
|
||||
if !active(app) || !can_open_options(id) {
|
||||
return false;
|
||||
}
|
||||
app.drawing_workspace.options = match id {
|
||||
"drawing.view.base.planned" => Some("Base View"),
|
||||
"drawing.view.projected.planned" => Some("Projected View"),
|
||||
"drawing.dimension" => Some("Dimension"),
|
||||
"drawing.note" => Some("Note"),
|
||||
_ => None,
|
||||
};
|
||||
app.drawing_workspace.settings_open = false;
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn covered_rects(app: &SolveApp) -> Vec<Rect> {
|
||||
if !active(app) {
|
||||
return Vec::new();
|
||||
}
|
||||
[app.drawing_workspace.strip, app.drawing_workspace.settings].into_iter().flatten().collect()
|
||||
}
|
||||
|
||||
pub(crate) fn settings_rect(app: &SolveApp) -> Option<Rect> {
|
||||
active(app).then_some(app.drawing_workspace.settings).flatten()
|
||||
}
|
||||
|
||||
fn panel_width(app: &SolveApp) -> f32 {
|
||||
if app.preferences.drawing_panel_width.is_finite() { app.preferences.drawing_panel_width.clamp(240.0, 560.0) } else { 320.0 }
|
||||
}
|
||||
|
||||
pub(crate) fn left_inset(app: &SolveApp) -> f32 {
|
||||
if active(app)
|
||||
&& Container::for_layout(app.ui.layout, Surface::Panel).floating()
|
||||
&& (app.drawing_workspace.settings_open || app.drawing_workspace.options.is_some())
|
||||
&& app.preferences.drawing_panel_side == "left"
|
||||
{
|
||||
panel_width(app) + crate::shell::SHELL_GAP
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Reuse Browser's existing content-hugging host and resize seam, with Drawing contents.
|
||||
pub(crate) fn browser_contents(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ui_kit::text(ui, crate::i18n::tr_label("No drawing objects"), ui_kit::Text::Note("drawing_objects"));
|
||||
ui.add_space(Tokens::get().space_8);
|
||||
ui_kit::text(ui, crate::i18n::tr_label("Source design"), ui_kit::Text::Heading("drawing_source"));
|
||||
ui.label(&app.session.doc.name);
|
||||
if ui_kit::button(ui, crate::i18n::tr_label("Source properties"), ui_kit::Button::Normal).clicked() {
|
||||
app.start("document.metadata");
|
||||
}
|
||||
}
|
||||
|
||||
/// There is intentionally no paper, sample geometry or camera until a scene provider exists.
|
||||
pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let rect = ui.max_rect();
|
||||
app.viewport.rect = Some(rect);
|
||||
let t = Tokens::get();
|
||||
ui.painter().rect_filled(rect, 0.0, t.viewport_top);
|
||||
let bounds = crate::shell::workspace_content(app, rect);
|
||||
let width = (bounds.width() - t.space_24 * 2.0).clamp(0.0, 420.0);
|
||||
let centre = Rect::from_center_size(bounds.center(), vec2(width, 140.0));
|
||||
ui.scope_builder(egui::UiBuilder::new().max_rect(centre), |ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.heading(crate::i18n::tr_label("Drawing workspace"));
|
||||
ui.label(crate::i18n::tr_label("No drawing sheet is available yet."));
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Drawing files and the paper renderer are not implemented. Planned tools show their options where defined."),
|
||||
ui_kit::Text::Note("drawing_waiting"),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn dock(app: &SolveApp) -> &str {
|
||||
match app.preferences.drawing_sheet_dock.as_str() {
|
||||
"bottom" => "bottom",
|
||||
"under_browser" if app.ui.show_browser => "under_browser",
|
||||
_ => "top",
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_size(app: &SolveApp, ui: &egui::Ui, destination: &str) -> egui::Vec2 {
|
||||
let t = Tokens::get();
|
||||
let label = ui_kit::ContentWidth::button(ui, crate::i18n::tr_label("Sheet unavailable"));
|
||||
let spacing = ui.spacing().item_spacing;
|
||||
let height = ui_kit::row_height().max(t.space_24);
|
||||
let measured = if destination == "under_browser" {
|
||||
vec2(label.max(t.space_10 + 3.0 * t.space_24 + 3.0 * spacing.x) + 10.0, height * 2.0 + spacing.y + 10.0)
|
||||
} else {
|
||||
vec2(label + t.space_10 + 3.0 * t.space_24 + 4.0 * spacing.x + 10.0, height + 10.0)
|
||||
};
|
||||
// Retain measured card dimensions; the font/control metrics cover a first frame or scale change.
|
||||
app.drawing_workspace.strip_sizes.get(usize::from(destination == "under_browser")).copied().flatten().map_or(measured, |size| size.max(measured))
|
||||
}
|
||||
|
||||
fn needs_vertical(app: &SolveApp, available: Rect, width: f32) -> bool {
|
||||
app.ui.show_browser && app.shell.browser.is_some_and(|b| available.right() - b.right() - Tokens::get().space_8 < width)
|
||||
}
|
||||
|
||||
fn strip_position(app: &SolveApp, available: Rect, destination: &str, size: egui::Vec2) -> egui::Pos2 {
|
||||
let gap = Tokens::get().space_8;
|
||||
let beside = app.shell.browser.map_or(available.left(), |b| b.right() + gap);
|
||||
let x = beside.clamp(available.left(), (available.right() - size.x).max(available.left()));
|
||||
match destination {
|
||||
"bottom" => pos2(x, (available.bottom() - size.y).max(available.top())),
|
||||
"under_browser" => app.shell.browser.map_or(available.min, |b| pos2(b.left(), b.bottom() + gap)),
|
||||
_ => pos2(x, available.top()),
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_target(app: &SolveApp, available: Rect, at: egui::Pos2) -> Option<&'static str> {
|
||||
if !available.contains(at) {
|
||||
return None;
|
||||
}
|
||||
let edge = Tokens::get().space_24 * 2.0;
|
||||
if at.y <= available.top() + edge {
|
||||
Some("top")
|
||||
} else if at.y >= available.bottom() - edge {
|
||||
Some("bottom")
|
||||
} else if app.ui.show_browser && app.shell.browser.is_some_and(|b| at.x <= b.right() && at.y >= b.top()) {
|
||||
Some("under_browser")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Same grip and highlighted docking vocabulary as History; no document state changes.
|
||||
fn strip_drag(app: &mut SolveApp, ui: &mut egui::Ui, available: Rect) {
|
||||
let grip = ui_kit::panel_grip(ui);
|
||||
let drag = ui.interact(grip.rect, ui.id().with("drawing_sheet_grip"), Sense::drag()).on_hover_text(crate::i18n::tr_label("Dock sheet tabs"));
|
||||
crate::scenario::publish_target("drawing:sheets:grip", drag.rect);
|
||||
if drag.hovered() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
|
||||
}
|
||||
let target = ui.ctx().pointer_latest_pos().and_then(|p| strip_target(app, available, p));
|
||||
if drag.dragged() {
|
||||
ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
|
||||
if let Some(target) = target {
|
||||
let horizontal = strip_size(app, ui, "top");
|
||||
let target = if target != "under_browser" && needs_vertical(app, available, horizontal.x) { "under_browser" } else { target };
|
||||
let size = strip_size(app, ui, target);
|
||||
let zone = Rect::from_min_size(strip_position(app, available, target, size), size);
|
||||
let t = Tokens::get();
|
||||
let painter = ui.ctx().layer_painter(egui::LayerId::new(egui::Order::Foreground, ui.id().with("drawing_dock_preview")));
|
||||
painter.rect(zone, t.radius_lg, t.accent.gamma_multiply(0.18), Stroke::new(t.space_2, t.accent), egui::StrokeKind::Inside);
|
||||
crate::scenario::publish_rect("drawing:sheets:dock_preview", zone);
|
||||
}
|
||||
}
|
||||
if drag.drag_stopped()
|
||||
&& let Some(target) = target
|
||||
{
|
||||
app.preferences.drawing_sheet_dock = target.into();
|
||||
}
|
||||
// Keyboard-accessible alternative to dragging, like History's layout menu.
|
||||
ui_kit::Menu::context(&drag, |ui| dock_menu(app, ui));
|
||||
}
|
||||
|
||||
fn dock_menu(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
for (value, label) in [("top", "Dock at top"), ("bottom", "Dock at bottom"), ("under_browser", "Dock beneath Browser")] {
|
||||
if ui_kit::button(ui, crate::i18n::tr_label(label), ui_kit::Button::Enabled(value != "under_browser" || app.ui.show_browser)).clicked() {
|
||||
app.preferences.drawing_sheet_dock = value.into();
|
||||
ui.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn icon_action(ui: &mut egui::Ui, id: &str, icon: &str, label: &str, enabled: bool) -> egui::Response {
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(Tokens::get().space_24, Tokens::get().space_24), Sense::hover());
|
||||
let r = ui.add_enabled_ui(enabled, |ui| ui_kit::icon_button_at(ui, rect, icon, label)).inner;
|
||||
crate::scenario::publish_target(id, rect);
|
||||
r
|
||||
}
|
||||
|
||||
/// Draw after Browser, so the strip can hug its measured right or bottom edge.
|
||||
pub fn chrome(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
app.drawing_workspace.strip = None;
|
||||
if !active(app) {
|
||||
return;
|
||||
}
|
||||
let mut available = crate::agent_chat::tool_space(app, ui.available_rect_before_wrap());
|
||||
if Container::for_layout(app.ui.layout, Surface::Panel).floating()
|
||||
&& app.preferences.drawing_panel_side != "left"
|
||||
&& let Some(settings) = settings_rect(app)
|
||||
{
|
||||
available.max.x = available.right().min(settings.left() - Tokens::get().space_8).max(available.left());
|
||||
}
|
||||
if let Some(bottom) = app.shell.cards.iter().map(Rect::bottom).max_by(f32::total_cmp) {
|
||||
available.min.y = available.min.y.max(bottom + Tokens::get().space_8);
|
||||
}
|
||||
if !available.is_positive() {
|
||||
return;
|
||||
}
|
||||
let destination =
|
||||
if dock(app) != "under_browser" && needs_vertical(app, available, strip_size(app, ui, "top").x) { "under_browser" } else { dock(app) }
|
||||
.to_string();
|
||||
let position = strip_position(app, available, &destination, strip_size(app, ui, &destination));
|
||||
let container = Container::new(ui.ctx(), Surface::Panel);
|
||||
let frame = container.frame(ui.ctx(), Tokens::get().panel, crate::frame::Side::Left).inner_margin(4);
|
||||
let card = container.area(egui::Id::new("sc_drawing_sheets")).fixed_pos(position).show(ui.ctx(), |ui| {
|
||||
container.show_frame(ui, frame, available, |ui| {
|
||||
let buttons = |app: &mut SolveApp, ui: &mut egui::Ui| {
|
||||
icon_action(ui, "drawing:sheets:add", "plus", "Add sheet (not implemented)", false);
|
||||
let management = icon_action(ui, "drawing:sheets:manage", "dots", "Manage sheets and docking", true);
|
||||
Container::new(ui.ctx(), Surface::Popup).menu(&management).frame(ui_kit::Menu::frame()).show(|ui| {
|
||||
ui_kit::button(ui, crate::i18n::tr_label("Manage sheets (not implemented)"), ui_kit::Button::Enabled(false));
|
||||
ui.separator();
|
||||
dock_menu(app, ui);
|
||||
});
|
||||
if icon_action(ui, "drawing:sheets:settings", "settings", "Sheet Settings", true).clicked() {
|
||||
app.drawing_workspace.settings_open = !app.drawing_workspace.settings_open;
|
||||
app.drawing_workspace.options = None;
|
||||
}
|
||||
};
|
||||
if destination == "under_browser" {
|
||||
ui.horizontal(|ui| {
|
||||
strip_drag(app, ui, available);
|
||||
buttons(app, ui);
|
||||
});
|
||||
ui_kit::button(ui, crate::i18n::tr_label("Sheet unavailable"), ui_kit::Button::Enabled(false));
|
||||
} else {
|
||||
ui.horizontal(|ui| {
|
||||
strip_drag(app, ui, available);
|
||||
ui_kit::button(ui, crate::i18n::tr_label("Sheet unavailable"), ui_kit::Button::Enabled(false));
|
||||
buttons(app, ui);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
app.drawing_workspace.strip = Some(card.response.rect);
|
||||
if let Some(size) = app.drawing_workspace.strip_sizes.get_mut(usize::from(destination == "under_browser")) {
|
||||
*size = Some(card.response.rect.size());
|
||||
}
|
||||
crate::scenario::publish_rect("drawing:sheets", card.response.rect);
|
||||
}
|
||||
|
||||
pub fn panel(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
app.drawing_workspace.settings = None;
|
||||
if !active(app) || (!app.drawing_workspace.settings_open && app.drawing_workspace.options.is_none()) {
|
||||
return;
|
||||
}
|
||||
let left = app.preferences.drawing_panel_side == "left";
|
||||
let width = panel_width(app);
|
||||
let container = Container::new(ui.ctx(), Surface::Panel);
|
||||
if container.floating() {
|
||||
floating_panel(app, ui, left, width);
|
||||
return;
|
||||
}
|
||||
let r = container
|
||||
.edge(if left { "sc_drawing_settings_left" } else { "sc_drawing_settings_right" }, if left { Edge::Left } else { Edge::Right })
|
||||
.default_size(width)
|
||||
.size_range(240.0..=560.0)
|
||||
.resizable(true)
|
||||
.frame(container.edge_frame(Tokens::get().dialog_body).inner_margin(10))
|
||||
.show(ui, |ui| {
|
||||
ui_kit::scope(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| panel_body(app, ui));
|
||||
});
|
||||
});
|
||||
app.drawing_workspace.settings = Some(r.response.rect);
|
||||
app.preferences.drawing_panel_width = r.response.rect.width().clamp(240.0, 560.0);
|
||||
crate::scenario::publish_rect("drawing:settings", r.response.rect);
|
||||
let rect = r.response.rect;
|
||||
let rail = Rect::from_center_size(if left { rect.right_center() } else { rect.left_center() }, vec2(Tokens::get().space_4, rect.height()));
|
||||
crate::scenario::publish_target("drawing:settings:resize", rail);
|
||||
}
|
||||
|
||||
/// Assistant's floating panel host and bounded resize rail; no independent window chrome.
|
||||
fn floating_panel(app: &mut SolveApp, ui: &mut egui::Ui, left: bool, width: f32) {
|
||||
let ctx = ui.ctx().clone();
|
||||
let t = Tokens::get();
|
||||
let available = crate::agent_chat::tool_space(app, ui.available_rect_before_wrap());
|
||||
let mut bounds = available.shrink(crate::shell::SHELL_GAP);
|
||||
bounds.min.y = available.top();
|
||||
let width = width.min(bounds.width().max(0.0));
|
||||
let position = pos2(if left { bounds.left() } else { bounds.right() - width }, bounds.top());
|
||||
bounds = Rect::from_min_size(position, vec2(width, bounds.height().max(0.0)));
|
||||
if !bounds.is_positive() {
|
||||
return;
|
||||
}
|
||||
let side = if left { crate::frame::Side::Left } else { crate::frame::Side::Right };
|
||||
let frame = ui_kit::Panel::feature_frame(&ctx, app.ui.layout, side);
|
||||
let inner = (bounds.size() - frame.total_margin().sum()).max(egui::Vec2::ZERO);
|
||||
let anchor = if left { bounds.left_top() } else { bounds.right_top() };
|
||||
let panel =
|
||||
ui_kit::Panel::new(crate::i18n::tr_label("Sheet Settings"), egui::Id::new("sc_drawing_settings_floating"), anchor, available, side, frame)
|
||||
.width(width);
|
||||
if let Some(card) = panel.window(&ctx).fixed_size(inner).show(&ctx, |ui| {
|
||||
ui.set_min_size(inner);
|
||||
ui.set_max_size(inner);
|
||||
ui.set_clip_rect(ui.clip_rect().intersect(Rect::from_min_size(ui.max_rect().min, inner)));
|
||||
ui_kit::scope(ui, |ui| {
|
||||
egui::ScrollArea::vertical().show(ui, |ui| panel_body(app, ui));
|
||||
});
|
||||
let rect = ui.max_rect();
|
||||
let rail = if left {
|
||||
Rect::from_x_y_ranges(rect.right() - t.space_4..=rect.right(), rect.y_range())
|
||||
} else {
|
||||
Rect::from_x_y_ranges(rect.left()..=rect.left() + t.space_4, rect.y_range())
|
||||
};
|
||||
let response =
|
||||
ui.interact(rail, egui::Id::new("sc_drawing_settings_resize"), Sense::drag()).on_hover_cursor(egui::CursorIcon::ResizeHorizontal);
|
||||
crate::scenario::publish_target("drawing:settings:resize", rail);
|
||||
if response.dragged() {
|
||||
let delta = ui.input(|i| i.pointer.delta().x) * if left { 1.0 } else { -1.0 };
|
||||
if delta.is_finite() {
|
||||
app.preferences.drawing_panel_width = (width + delta).clamp(240.0, 560.0);
|
||||
}
|
||||
}
|
||||
}) {
|
||||
app.drawing_workspace.settings = Some(card.response.rect);
|
||||
crate::scenario::publish_rect("drawing:settings", card.response.rect);
|
||||
}
|
||||
}
|
||||
|
||||
fn unavailable_row(ui: &mut egui::Ui, label: &str, value: &str) {
|
||||
ui_kit::row(ui, crate::i18n::tr_label(label), |ui| {
|
||||
ui.add_enabled_ui(false, |ui| {
|
||||
if matches!(
|
||||
label,
|
||||
"Source"
|
||||
| "Orientation"
|
||||
| "Direction"
|
||||
| "Parent view"
|
||||
| "Text style"
|
||||
| "Profile & units"
|
||||
| "Size & orientation"
|
||||
| "Border & template"
|
||||
) {
|
||||
ui_kit::dropdown_menu(ui, label, crate::i18n::tr_label(value), |_| {});
|
||||
} else {
|
||||
let mut text = crate::i18n::tr_label(value).to_string();
|
||||
ui_kit::cell_editor(ui, ui.id().with(label), &mut text, "", false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
fn panel_body(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
let options = app.drawing_workspace.options;
|
||||
ui.horizontal(|ui| {
|
||||
ui.heading(crate::i18n::tr_label(options.unwrap_or("Sheet Settings")));
|
||||
if icon_action(ui, "drawing:settings:close", "close", "Close drawing panel", true).clicked() {
|
||||
app.drawing_workspace.settings_open = false;
|
||||
app.drawing_workspace.options = None;
|
||||
}
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
for (side, label) in [("left", "Dock left"), ("right", "Dock right")] {
|
||||
if ui_kit::button(ui, crate::i18n::tr_label(label), ui_kit::Button::Normal).clicked() {
|
||||
app.preferences.drawing_panel_side = side.into();
|
||||
}
|
||||
}
|
||||
});
|
||||
ui.separator();
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Not implemented. These options describe the planned interface; no drawing data is changed."),
|
||||
ui_kit::Text::Note("drawing_options_pending"),
|
||||
);
|
||||
if let Some(kind) = options {
|
||||
match kind {
|
||||
"Base View" => {
|
||||
unavailable_row(ui, "Source", "Source design");
|
||||
unavailable_row(ui, "Orientation", "Not configured");
|
||||
unavailable_row(ui, "Scale", "Not configured");
|
||||
}
|
||||
"Projected View" => {
|
||||
unavailable_row(ui, "Parent view", "No drawing view available");
|
||||
unavailable_row(ui, "Direction", "Not configured");
|
||||
unavailable_row(ui, "Scale", "Inherited from parent view");
|
||||
}
|
||||
"Dimension" => {
|
||||
unavailable_row(ui, "References", "No drawing geometry available");
|
||||
unavailable_row(ui, "Units & precision", "Drawing defaults (not available)");
|
||||
}
|
||||
"Note" => {
|
||||
unavailable_row(ui, "Text", "Not configured");
|
||||
unavailable_row(ui, "Text style", "Drawing defaults (not available)");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
ui.separator();
|
||||
ui_kit::button(ui, crate::i18n::tr_label("Apply (not implemented)"), ui_kit::Button::Enabled(false));
|
||||
} else {
|
||||
ui.heading(crate::i18n::tr_label("Drawing & title block"));
|
||||
unavailable_row(ui, "Title", "No drawing document");
|
||||
unavailable_row(ui, "Title block", "Not available");
|
||||
ui.heading(crate::i18n::tr_label("Paper & layout"));
|
||||
unavailable_row(ui, "Size & orientation", "No sheet available");
|
||||
unavailable_row(ui, "Border & template", "Not available");
|
||||
ui.heading(crate::i18n::tr_label("Drafting defaults"));
|
||||
unavailable_row(ui, "Profile & units", "No drawing document");
|
||||
ui.separator();
|
||||
ui.heading(crate::i18n::tr_label("Screen appearance"));
|
||||
ui.add_enabled_ui(false, |ui| {
|
||||
ui_kit::row(ui, crate::i18n::tr_label("Paper colour"), |ui| {
|
||||
ui_kit::dropdown_menu(ui, "drawing_paper_colour", crate::i18n::tr_label("Auto (Theme)"), |_| {});
|
||||
});
|
||||
});
|
||||
ui_kit::text(
|
||||
ui,
|
||||
crate::i18n::tr_label("Paper colour will be available when the sheet renderer is implemented."),
|
||||
ui_kit::Text::Note("drawing_paper_preference"),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::scenario::Harness;
|
||||
|
||||
#[test]
|
||||
fn workspace_and_options_leave_source_and_camera_unchanged() {
|
||||
let mut h = Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.run("solid.box", serde_json::json!({"length":40,"width":20,"height":3})).unwrap();
|
||||
h.app.ui.tab = "SURFACE".into();
|
||||
let doc = h.app.session.doc.clone();
|
||||
let revision = h.app.session.revision;
|
||||
let undo = h.app.session.undo.len();
|
||||
let cam = h.app.cam;
|
||||
enter(&mut h.app, true).unwrap();
|
||||
assert!(start(&mut h.app, "drawing.view.base.planned"));
|
||||
h.frames(2);
|
||||
assert!(h.widget("Apply (not implemented)").is_some());
|
||||
assert_eq!(h.app.session.undo.len(), undo);
|
||||
assert_eq!(h.app.session.revision, revision);
|
||||
assert!(std::sync::Arc::ptr_eq(&doc, &h.app.session.doc));
|
||||
assert_eq!(h.app.cam, cam);
|
||||
enter(&mut h.app, false).unwrap();
|
||||
assert_eq!(h.app.ui.tab, "SURFACE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sheet_docking_and_settings_only_change_ui_preferences() {
|
||||
for layout in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
let mut h = Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.ui.layout = layout;
|
||||
h.call("ui.resize", serde_json::json!({"width":1400,"height":1000}));
|
||||
enter(&mut h.app, true).unwrap();
|
||||
let revision = h.app.session.revision;
|
||||
let doc = h.app.session.doc.clone();
|
||||
let cam = h.app.cam;
|
||||
h.frames(4);
|
||||
let strip = crate::scenario::rect_of("drawing:sheets").unwrap();
|
||||
let browser = crate::scenario::rect_of("browser:card").unwrap();
|
||||
assert!(strip.width() < 340.0 && strip.height() < 50.0, "single sheet strip should hug its contents: {strip:?}");
|
||||
assert!(strip.left() >= browser.right(), "sheet strip belongs beside Browser");
|
||||
for (destination, at) in [
|
||||
("bottom", pos2(700.0, 980.0)),
|
||||
("under_browser", pos2(browser.center().x, browser.bottom() + 80.0)),
|
||||
("top", pos2(700.0, strip.center().y)),
|
||||
] {
|
||||
let grip = crate::scenario::handle_at("drawing:sheets:grip").unwrap();
|
||||
h.app.synthetic.push(egui::Event::PointerMoved(grip));
|
||||
h.frame();
|
||||
h.app.synthetic.push(egui::Event::PointerButton {
|
||||
pos: grip,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: true,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
});
|
||||
h.frame();
|
||||
h.app.synthetic.push(egui::Event::PointerMoved(at));
|
||||
h.frames(2);
|
||||
assert!(crate::scenario::rect_of("drawing:sheets:dock_preview").is_some(), "{layout:?} {destination}: highlighted docking preview");
|
||||
h.app.synthetic.push(egui::Event::PointerButton {
|
||||
pos: at,
|
||||
button: egui::PointerButton::Primary,
|
||||
pressed: false,
|
||||
modifiers: egui::Modifiers::NONE,
|
||||
});
|
||||
h.frames(4);
|
||||
assert_eq!(h.app.preferences.drawing_sheet_dock, destination);
|
||||
}
|
||||
h.step(&serde_json::json!({"click":{"handle":"drawing:sheets:settings"}})).unwrap();
|
||||
h.frames(3);
|
||||
for (side, label) in [("left", "Dock left"), ("right", "Dock right")] {
|
||||
h.step(&serde_json::json!({"widget":label})).unwrap();
|
||||
h.frames(3);
|
||||
assert_eq!(h.app.preferences.drawing_panel_side, side);
|
||||
let panel = crate::scenario::rect_of("drawing:settings").unwrap();
|
||||
assert_eq!(panel.center().x < 700.0, side == "left", "{layout:?} {side}: {panel:?}");
|
||||
let rail = crate::scenario::handle_at("drawing:settings:resize").unwrap();
|
||||
h.call(
|
||||
"ui.drag",
|
||||
serde_json::json!({"x0":rail.x,"y0":rail.y,"x1":rail.x + if side == "left" {50.0} else {-50.0},"y1":rail.y,"steps":6}),
|
||||
);
|
||||
h.frames(3);
|
||||
assert!(app_width(&h) > panel.width() + 20.0, "{layout:?} {side}: properties resize");
|
||||
}
|
||||
assert_eq!(h.app.session.revision, revision);
|
||||
assert!(std::sync::Arc::ptr_eq(&doc, &h.app.session.doc));
|
||||
assert_eq!(h.app.cam, cam);
|
||||
}
|
||||
assert!(!can_open_options("drawing.export.planned"));
|
||||
assert!(!can_open_options("drawing.update"));
|
||||
}
|
||||
|
||||
fn app_width(h: &Harness) -> f32 {
|
||||
h.app.preferences.drawing_panel_width
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn crowded_sheet_strip_moves_beneath_browser_without_changing_its_dock_preference() {
|
||||
for layout in [crate::shell::LayoutMode::Floating, crate::shell::LayoutMode::Docked] {
|
||||
let mut h = Harness::new();
|
||||
h.app.home.open = false;
|
||||
h.app.ui.layout = layout;
|
||||
h.call("ui.resize", serde_json::json!({"width":960,"height":600}));
|
||||
h.app.preferences.drawing_panel_width = 560.0;
|
||||
enter(&mut h.app, true).unwrap();
|
||||
h.app.drawing_workspace.settings_open = true;
|
||||
h.frames(4);
|
||||
let browser = crate::scenario::rect_of("browser:card").unwrap();
|
||||
let strip = crate::scenario::rect_of("drawing:sheets").unwrap();
|
||||
assert!(strip.top() >= browser.bottom(), "{layout:?}: crowded sheet strip must not cover Browser: {strip:?} {browser:?}");
|
||||
assert_eq!(h.app.preferences.drawing_sheet_dock, "top");
|
||||
h.step(&serde_json::json!({"click":{"handle":"drawing:settings:close"}})).unwrap();
|
||||
h.frames(4);
|
||||
let browser = crate::scenario::rect_of("browser:card").unwrap();
|
||||
let strip = crate::scenario::rect_of("drawing:sheets").unwrap();
|
||||
assert!(
|
||||
strip.left() >= browser.right() && strip.height() < 50.0,
|
||||
"{layout:?}: preferred horizontal strip returns when room is available"
|
||||
);
|
||||
assert_eq!(h.app.preferences.drawing_sheet_dock, "top");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -924,7 +924,13 @@ mod tests {
|
||||
fn command_artwork_resolves_and_construct_methods_are_distinct() {
|
||||
let specs = solidcraft_engine::command_specs();
|
||||
for command in &specs {
|
||||
assert!(!resolve(command.icon).placeholder, "{}: {}", command.id, command.icon);
|
||||
if let Some(candidate) = solidcraft_engine::cmd::drawing_pending_icon(command.id) {
|
||||
assert!(command.planned.is_some(), "{} must remain explicitly planned", command.id);
|
||||
assert_eq!(command.icon, PLACEHOLDER, "{} artwork awaits owner approval", command.id);
|
||||
assert!(!resolve(candidate).placeholder, "{}: missing review draft {candidate}", command.id);
|
||||
} else {
|
||||
assert!(!resolve(command.icon).placeholder, "{}: {}", command.id, command.icon);
|
||||
}
|
||||
}
|
||||
let construction: Vec<_> = specs.iter().filter(|c| c.panel == "CONSTRUCT").collect();
|
||||
let distinct: std::collections::HashSet<_> = construction.iter().map(|c| c.icon).collect();
|
||||
@@ -932,6 +938,25 @@ mod tests {
|
||||
assert_eq!(distinct.len(), construction.len(), "construction methods need distinct visual identities");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pending_drawing_artwork_renders_at_review_sizes_in_both_themes() {
|
||||
let mut candidates = std::collections::HashSet::new();
|
||||
for command in solidcraft_engine::command_specs() {
|
||||
let Some(candidate) = solidcraft_engine::cmd::drawing_pending_icon(command.id) else { continue };
|
||||
assert!(candidates.insert(candidate), "distinct Drawing operations need distinct artwork: {candidate}");
|
||||
let resolved = resolve(candidate);
|
||||
assert_eq!(resolved.master, candidate, "review draft must have an original master");
|
||||
for dark in [false, true] {
|
||||
for size in [16, 24, 64, 128] {
|
||||
let doc = doc_for(resolved.master, size).unwrap();
|
||||
let image = render(doc, Look { dark, accent: None, vision: crate::theme::ColorVision::Off, palette: None }, size).unwrap();
|
||||
assert!(image.pixels().iter().any(|pixel| pixel.alpha() > 0), "{candidate}: {dark} at {size}px");
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(candidates.len(), 22);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn helper_controls_resolve_without_command_registry_entries() {
|
||||
for name in ["agent_chat", "normal", "infinite", "pattern_flip", "pattern_center"] {
|
||||
|
||||
@@ -180,6 +180,7 @@ mod blend_dialog_tests;
|
||||
#[cfg(test)]
|
||||
mod boundary_dialog_tests;
|
||||
pub(crate) mod display_lod;
|
||||
pub mod drawing_workspace;
|
||||
#[cfg(test)]
|
||||
mod file_load_ui_tests;
|
||||
#[cfg(test)]
|
||||
@@ -493,6 +494,7 @@ pub struct SolveApp {
|
||||
saving: Option<save_job::Saving>,
|
||||
pub viewport: viewport::ViewportState,
|
||||
pub render_workspace: render_workspace::State,
|
||||
pub drawing_workspace: drawing_workspace::State,
|
||||
pub tool: Option<tools::Tool>,
|
||||
pub dialog: Option<dialogs::Dialog>,
|
||||
/// The Change Parameters window, when open.
|
||||
@@ -738,6 +740,7 @@ impl SolveApp {
|
||||
saving: None,
|
||||
viewport: viewport::ViewportState::default(),
|
||||
render_workspace,
|
||||
drawing_workspace: Default::default(),
|
||||
tool: None,
|
||||
dialog: None,
|
||||
params: None,
|
||||
@@ -1356,6 +1359,7 @@ impl SolveApp {
|
||||
documents::reset_chat(self);
|
||||
self.edit_in_place.active = None;
|
||||
render_workspace::reset_document(self);
|
||||
drawing_workspace::reset_document(self);
|
||||
dim_view::cancel();
|
||||
design_properties::close_object(self);
|
||||
timeline::stop_playback(self);
|
||||
@@ -1472,6 +1476,17 @@ impl SolveApp {
|
||||
if self.file_load.busy() || self.file_picker.is_some() {
|
||||
return;
|
||||
}
|
||||
if drawing_workspace::start(self, id) {
|
||||
return;
|
||||
}
|
||||
if drawing_workspace::active(self)
|
||||
&& !id.starts_with("drawing.")
|
||||
&& !matches!(id, "assembly.bom" | "sheet.flat_pattern" | "document.metadata" | "document.metadata.set" | "view.layout")
|
||||
&& !(dialogs_sheet::flat_active(self) && matches!(id, "view.fit" | "view.home"))
|
||||
{
|
||||
self.set_status(self.tr_label("Return to Design to use this command.").to_string(), true);
|
||||
return;
|
||||
}
|
||||
if id != "occurrence.move" {
|
||||
joint_handles::close(self);
|
||||
}
|
||||
@@ -2230,6 +2245,9 @@ impl SolveApp {
|
||||
self.flat_view.fitted = false;
|
||||
return;
|
||||
}
|
||||
if drawing_workspace::active(self) {
|
||||
return;
|
||||
}
|
||||
if render_workspace::active(self) {
|
||||
self.cancel_view_animation();
|
||||
match solidcraft_engine::product_scene::extract(&self.session) {
|
||||
@@ -2357,6 +2375,9 @@ impl SolveApp {
|
||||
self.animate_view("fit");
|
||||
return;
|
||||
}
|
||||
if drawing_workspace::active(self) {
|
||||
return;
|
||||
}
|
||||
if render_workspace::active(self) {
|
||||
// The Render preview frames its product scene (see `fit_view`).
|
||||
self.fit_view();
|
||||
@@ -2371,6 +2392,9 @@ impl SolveApp {
|
||||
|
||||
/// Animated view changes for the axis view and navigation bar.
|
||||
pub fn animate_view(&mut self, v: &str) {
|
||||
if drawing_workspace::active(self) && !(dialogs_sheet::flat_active(self) && matches!(v, "fit" | "home")) {
|
||||
return;
|
||||
}
|
||||
if matches!(v, "fit" | "home") {
|
||||
let _ = self.run(if v == "fit" { "view.fit" } else { "view.home" }, json!({}));
|
||||
} else if let Some(view) = StandardView::parse(v) {
|
||||
@@ -2392,6 +2416,9 @@ impl SolveApp {
|
||||
self.flat_view.fitted = false;
|
||||
return;
|
||||
}
|
||||
if drawing_workspace::active(self) {
|
||||
return;
|
||||
}
|
||||
if render_workspace::active(self) {
|
||||
if home {
|
||||
self.cam.set_view(StandardView::Iso);
|
||||
@@ -2695,7 +2722,8 @@ impl SolveApp {
|
||||
let floating = render_workspace::active(self) || ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Panel).floating();
|
||||
timeline::follow_view_pick(self);
|
||||
render_ui::focus_shortcut(self, ui);
|
||||
let show_timeline = self.ui.show_timeline && !render_workspace::active(self) && !self.preferences.history_in_browser;
|
||||
let show_timeline =
|
||||
self.ui.show_timeline && !render_workspace::active(self) && !drawing_workspace::active(self) && !self.preferences.history_in_browser;
|
||||
let left_timeline = show_timeline && self.preferences.timeline_left;
|
||||
timeline_left::animate_folds(self, ui.ctx());
|
||||
if !floating && !self.embedded {
|
||||
@@ -2763,9 +2791,15 @@ impl SolveApp {
|
||||
render_ui::shots_strip(self, ui);
|
||||
render_workspace::panel(self, ui);
|
||||
}
|
||||
if drawing_workspace::active(self) {
|
||||
drawing_workspace::panel(self, ui);
|
||||
}
|
||||
if self.ui.show_browser && !render_ui::focused(self) {
|
||||
browser::browser(self, ui);
|
||||
}
|
||||
if drawing_workspace::active(self) {
|
||||
drawing_workspace::chrome(self, ui);
|
||||
}
|
||||
if floating {
|
||||
agent_chat::show(self, ui);
|
||||
}
|
||||
@@ -2817,7 +2851,14 @@ impl SolveApp {
|
||||
}
|
||||
|
||||
fn show_workspace(&mut self, ui: &mut egui::Ui) {
|
||||
if render_workspace::active(self) {
|
||||
if drawing_workspace::active(self) {
|
||||
if dialogs_sheet::flat_active(self) {
|
||||
// This is the existing source manufacturing inspector, not a drawing scene.
|
||||
viewport::show(self, ui);
|
||||
} else {
|
||||
drawing_workspace::show(self, ui);
|
||||
}
|
||||
} else if render_workspace::active(self) {
|
||||
render_workspace::show(self, ui);
|
||||
} else {
|
||||
viewport::show(self, ui);
|
||||
|
||||
@@ -1108,6 +1108,11 @@ preference_settings! {
|
||||
/// Compact history: the browser lists each component's features in history order with the
|
||||
/// marker, and neither timeline is shown.
|
||||
history_in_browser: bool = false, PAGE_INTERFACE;
|
||||
/// Drawing sheet strip placement; independent of Design's feature history.
|
||||
drawing_sheet_dock: String = "top".into(), PAGE_INTERFACE;
|
||||
/// Drawing properties use the same left/right, resizable panel pattern as Assistant.
|
||||
drawing_panel_side: String = "right".into(), PAGE_INTERFACE;
|
||||
drawing_panel_width: f32 = 320.0, PAGE_INTERFACE, accept(240.0..=560.0);
|
||||
/// Fold the shared-column history to its header.
|
||||
timeline_collapsed: bool = false, PAGE_INTERFACE;
|
||||
/// The browser's share of the column's height when History sits under it, which the user
|
||||
@@ -3445,6 +3450,12 @@ pub fn set(app: &mut SolveApp, p: &serde_json::Value) -> Result<serde_json::Valu
|
||||
if !accepted::perspective_fov(&new) {
|
||||
return Err("`perspective_fov` must be 15°…90° in radians (0.2618…1.5708)".into());
|
||||
}
|
||||
if !accepted::drawing_panel_width(&new)
|
||||
|| !["top", "bottom", "under_browser"].contains(&new.drawing_sheet_dock.as_str())
|
||||
|| !["left", "right"].contains(&new.drawing_panel_side.as_str())
|
||||
{
|
||||
return Err("Drawing preferences require a valid docking position and panel width of 240…560".into());
|
||||
}
|
||||
if solidcraft_engine::doc::display_quality::DisplayQuality::parse(&new.display_quality).is_none() {
|
||||
return Err("`display_quality` must be coarse, normal, fine or ultra".into());
|
||||
}
|
||||
|
||||
@@ -159,6 +159,7 @@ pub(crate) fn fit_rects(app: &SolveApp) -> Vec<Rect> {
|
||||
fn occluding_rects(app: &SolveApp, fitting: bool) -> Vec<Rect> {
|
||||
let mut rects = Vec::new();
|
||||
rects.extend(app.shell.workspace_panels.iter().filter(|(id, _)| !fitting || **id != "render:selection").map(|(_, (rect, _))| *rect));
|
||||
rects.extend(crate::drawing_workspace::covered_rects(app));
|
||||
if crate::render_workspace::active(app) || Container::for_layout(app.ui.layout, Surface::Panel).floating() {
|
||||
rects.extend(app.shell.cards.iter().copied());
|
||||
rects.extend(app.shell.panels.iter().copied());
|
||||
@@ -251,6 +252,15 @@ pub(crate) fn workspace_content(app: &SolveApp, mut content: Rect) -> Rect {
|
||||
if let Some(column) = app.shell.dialog_column {
|
||||
content.min.x = within(column.right() + gap, content.left(), content.right());
|
||||
}
|
||||
if Container::for_layout(app.ui.layout, Surface::Panel).floating()
|
||||
&& let Some(settings) = crate::drawing_workspace::settings_rect(app)
|
||||
{
|
||||
if app.preferences.drawing_panel_side == "left" {
|
||||
content.min.x = (settings.right() + gap).clamp(content.left(), content.right());
|
||||
} else {
|
||||
content.max.x = (settings.left() - gap).clamp(content.left(), content.right());
|
||||
}
|
||||
}
|
||||
content
|
||||
}
|
||||
|
||||
|
||||
@@ -190,6 +190,19 @@ fn keyword_score(q: &str, keywords: &str) -> u32 {
|
||||
/// (tab, panel; `None` off the toolbar), in the current language.
|
||||
pub type Row = (&'static str, &'static str, &'static str, Option<(&'static str, &'static str)>);
|
||||
|
||||
fn available_in_workspace(app: &SolveApp, id: &str) -> bool {
|
||||
if crate::drawing_workspace::active(app) {
|
||||
id.starts_with("drawing.") || matches!(id, "assembly.bom" | "sheet.flat_pattern" | "document.metadata")
|
||||
} else {
|
||||
!id.starts_with("drawing.")
|
||||
}
|
||||
}
|
||||
|
||||
fn can_start(app: &SolveApp, id: &str) -> bool {
|
||||
solidcraft_engine::find_command(id).is_some_and(|c| crate::render_workspace::interactive_command_info(app, c).enabled)
|
||||
|| (crate::drawing_workspace::active(app) && crate::drawing_workspace::can_open_options(id))
|
||||
}
|
||||
|
||||
/// Commands matching `q`, best first. With an empty query, recent commands come first, then
|
||||
/// everything by name.
|
||||
///
|
||||
@@ -200,6 +213,7 @@ pub fn search(app: &SolveApp, q: &str) -> Vec<Row> {
|
||||
let mut v: Vec<(u32, Row)> = command_specs()
|
||||
.into_iter()
|
||||
.filter(|c| !crate::workspace::AGENT_ONLY.contains(&c.id))
|
||||
.filter(|c| available_in_workspace(app, c.id))
|
||||
.filter_map(|c| {
|
||||
let (home_tab, home_panel) = crate::workspace::destination(c).unwrap_or(("", ""));
|
||||
let info = crate::render_workspace::interactive_command_info(app, c);
|
||||
@@ -487,16 +501,9 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
56
|
||||
})
|
||||
.collect();
|
||||
design = if query.trim().is_empty() { Vec::new() } else { design_rows(app, &query) };
|
||||
design = if query.trim().is_empty() || crate::drawing_workspace::active(app) { Vec::new() } else { design_rows(app, &query) };
|
||||
}
|
||||
let enabled: Vec<_> = rows
|
||||
.iter()
|
||||
.map(|r| {
|
||||
solidcraft_engine::find_command(r.0).is_some_and(|c| crate::render_workspace::interactive_command_info(app, c).enabled)
|
||||
})
|
||||
.chain(design.iter().map(|_| true))
|
||||
.take(MAX_RESULTS)
|
||||
.collect();
|
||||
let enabled: Vec<_> = rows.iter().map(|r| can_start(app, r.0)).chain(design.iter().map(|_| true)).take(MAX_RESULTS).collect();
|
||||
if down || up {
|
||||
let step = if down { 1 } else { enabled.len().saturating_sub(1) };
|
||||
for _ in 0..enabled.len() {
|
||||
@@ -561,7 +568,16 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
let shortcut = crate::keymap::display_for(app, id).unwrap_or_default();
|
||||
item.shortcut = &shortcut;
|
||||
let path = place.map_or_else(|| id.to_string(), |(tab, panel)| format!("{tab} › {panel}"));
|
||||
let response = item.search_row(ui, &query, &path, None);
|
||||
let response = ui
|
||||
.scope(|ui| {
|
||||
if solidcraft_engine::find_command(id)
|
||||
.is_some_and(|c| !crate::render_workspace::interactive_command_info(app, c).enabled)
|
||||
{
|
||||
ui.set_opacity(0.35);
|
||||
}
|
||||
item.search_row(ui, &query, &path, None)
|
||||
})
|
||||
.inner;
|
||||
crate::scenario::publish_target(&format!("search:command:{id}"), response.rect);
|
||||
if item.highlighted && (up || down) {
|
||||
ui.scroll_to_rect(response.rect, None);
|
||||
@@ -666,10 +682,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
} else {
|
||||
let index = picked.unwrap_or(app.sbox.index);
|
||||
if let Some((id, ..)) = rows
|
||||
.get(index)
|
||||
.filter(|r| solidcraft_engine::find_command(r.0).is_some_and(|c| crate::render_workspace::interactive_command_info(app, c).enabled))
|
||||
{
|
||||
if let Some((id, ..)) = rows.get(index).filter(|r| can_start(app, r.0)) {
|
||||
app.sbox.used(id);
|
||||
close(app);
|
||||
app.start(id);
|
||||
@@ -878,6 +891,20 @@ mod tests {
|
||||
assert_eq!((other.sbox.pinned.clone(), other.sbox.recent.len()), (vec!["solid.extrude".to_string()], 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drawing_search_exposes_only_drawing_and_explicit_source_actions() {
|
||||
let mut app = SolveApp::new(Default::default(), Default::default());
|
||||
assert!(!search(&app, "").iter().any(|row| row.0.starts_with("drawing.")));
|
||||
crate::workspace::enter(&mut app, "drawing").unwrap();
|
||||
let rows = search(&app, "");
|
||||
assert!(rows.iter().any(|row| row.0 == "drawing.view.base.planned"));
|
||||
assert!(rows.iter().any(|row| row.0 == "assembly.bom"));
|
||||
assert!(!rows.iter().any(|row| row.0 == "solid.extrude"));
|
||||
assert!(can_start(&app, "drawing.view.base.planned"), "known options are inspectable");
|
||||
assert!(!can_start(&app, "drawing.view.section"), "uncertain tools cannot start");
|
||||
assert!(!solidcraft_engine::find_command("drawing.view.base.planned").unwrap().info(&app.session).enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn solve_sketch_is_not_offered_but_the_command_remains() {
|
||||
let app = SolveApp::new(solidcraft_engine::Session::default(), Default::default());
|
||||
|
||||
@@ -17,6 +17,13 @@ fn command_accent(icon: &str) -> Color32 {
|
||||
crate::icon_svg::primary(icon, crate::theme::is_dark(), crate::theme::vision())
|
||||
}
|
||||
|
||||
/// Planned Drawing tools stay visually unavailable, but reviewed option layouts can open.
|
||||
/// Execution availability remains entirely owned by the engine command.
|
||||
fn can_start(app: &SolveApp, command: &solidcraft_engine::CommandSpec) -> bool {
|
||||
crate::render_workspace::interactive_command_info(app, command).enabled
|
||||
|| (crate::drawing_workspace::active(app) && crate::drawing_workspace::can_open_options(command.id))
|
||||
}
|
||||
|
||||
/// Accent for a tab or a panel with a more specific semantic role.
|
||||
fn panel_accent(t: &Tokens, tab: &str, panel: &str) -> Color32 {
|
||||
if panel == "CONSTRAIN" {
|
||||
@@ -1397,7 +1404,8 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
if render {
|
||||
crate::ui_kit::StageBar::workspace(ui, ws, "ws_render", app.tr("ui.render"), app.tr("ui.ribbon_workspace"));
|
||||
} else {
|
||||
let workspace_icon = if render { "ws_render" } else { "ws_design" };
|
||||
let drawing = crate::drawing_workspace::active(app);
|
||||
let workspace_icon = if drawing { "ws_drawing" } else { "ws_design" };
|
||||
icons::paint_icon(
|
||||
&painter,
|
||||
Rect::from_center_size(pos2(ws.center().x, ws.top() + if labels { 39.0 } else { 25.0 }), vec2(30.0, 30.0)),
|
||||
@@ -1407,7 +1415,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
ribbon_text(
|
||||
ui,
|
||||
pos2(ws.center().x, ws.top() + if labels { 69.0 } else { 50.0 }),
|
||||
app.tr(if render { "ui.render" } else { "ui.design" }),
|
||||
if drawing { app.tr_label("Drawing") } else { app.tr("ui.design") },
|
||||
t.font_button,
|
||||
t.text,
|
||||
true,
|
||||
@@ -1501,7 +1509,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
.map(|c| {
|
||||
let family = crate::sketch_families::family(c.id)
|
||||
.filter(|f| tab == "SKETCH" && *panel == "CREATE" && f.commands.first().copied() == Some(c.id));
|
||||
let label = family.map_or_else(|| crate::workspace::ribbon_button_label(c), crate::workspace::ribbon_family_label);
|
||||
let label = family.map_or_else(|| crate::workspace::ribbon_button_label_in(&tab, c), crate::workspace::ribbon_family_label);
|
||||
if labels {
|
||||
ButtonSize { width: ribbon_button_width(&painter, app.tr_label(label)), min_width: BUTTON_MIN_WIDTH }
|
||||
} else {
|
||||
@@ -1661,7 +1669,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
Stroke::new(1.0, accent.color),
|
||||
egui::StrokeKind::Inside,
|
||||
))
|
||||
} else if resp.hovered() && info.enabled {
|
||||
} else if resp.hovered() && can_start(app, selected) {
|
||||
egui::Shape::rect_filled(whole, t.radius_sm, t.hover)
|
||||
} else {
|
||||
egui::Shape::Noop
|
||||
@@ -1686,13 +1694,13 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
icons::paint_icon(&painter, icon_rect, icon, if info.enabled { 1.0 } else { 0.35 });
|
||||
let tr_label = app.tr_label(info.label).to_string();
|
||||
if !persistent {
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && info.enabled, &tr_label));
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && can_start(app, selected), &tr_label));
|
||||
}
|
||||
let clicked = resp.clicked();
|
||||
if !persistent {
|
||||
let _ = ribbon_tooltip(app, resp.clone(), selected, info.disabled_reason.as_deref());
|
||||
}
|
||||
if clicked && info.enabled {
|
||||
if clicked && can_start(app, selected) {
|
||||
if persistent && app.preferences.persistent_ribbon_pressed {
|
||||
resp.request_focus();
|
||||
}
|
||||
@@ -1741,7 +1749,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
sketch_menu_rows(app, ui, &variants, width, &mut menu, &mut toggle);
|
||||
});
|
||||
} else if labels {
|
||||
let button_label = crate::workspace::ribbon_button_label(selected);
|
||||
let button_label = crate::workspace::ribbon_button_label_in(&tab, selected);
|
||||
let label = app.tr_label(if button_label == selected.label { info.label } else { button_label });
|
||||
let color = if info.enabled { t.text } else { t.text_dim };
|
||||
let galley = ribbon_button_galley(&painter, label, whole.width() - BUTTON_LABEL_PADDING, color);
|
||||
@@ -1939,7 +1947,7 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
// focused: the first or the last), instead of egui's own spatial move.
|
||||
let (down, up) = ui.input(|i| (i.key_pressed(egui::Key::ArrowDown), i.key_pressed(egui::Key::ArrowUp)));
|
||||
if down || up {
|
||||
let enabled: Vec<&str> = cmds.iter().filter(|c| c.info(&app.session).enabled).map(|c| c.id).collect();
|
||||
let enabled: Vec<&str> = cmds.iter().filter(|c| can_start(app, c)).map(|c| c.id).collect();
|
||||
let focused = ui.ctx().memory(|m| m.focused());
|
||||
let at = enabled.iter().position(|id| focused == Some(dropdown_row_id(id)));
|
||||
let n = enabled.len();
|
||||
@@ -2012,15 +2020,22 @@ pub fn toolbar(app: &mut SolveApp, ui: &mut egui::Ui) {
|
||||
for c in cmds {
|
||||
let info = crate::render_workspace::interactive_command_info(app, c);
|
||||
let shortcut = crate::keymap::display_for(app, c.id).unwrap_or_default();
|
||||
let response = if persistent_command(c.id) && app.preferences.persistent_ribbon_pressed {
|
||||
let mut item = ui_kit::MenuItem::new(c.id, info.label, info.icon);
|
||||
item.shortcut = &shortcut;
|
||||
item.enabled = info.enabled;
|
||||
item.checked = Some(persistent_pressed(app, c.id));
|
||||
item.show(ui)
|
||||
} else {
|
||||
ui_kit::command_menu_button(ui, c.id, info.label, info.icon, &shortcut, info.enabled)
|
||||
};
|
||||
let response = ui
|
||||
.scope(|ui| {
|
||||
if !info.enabled && can_start(app, c) {
|
||||
ui.set_opacity(0.35);
|
||||
}
|
||||
if persistent_command(c.id) && app.preferences.persistent_ribbon_pressed {
|
||||
let mut item = ui_kit::MenuItem::new(c.id, info.label, info.icon);
|
||||
item.shortcut = &shortcut;
|
||||
item.enabled = can_start(app, c);
|
||||
item.checked = Some(persistent_pressed(app, c.id));
|
||||
item.show(ui)
|
||||
} else {
|
||||
ui_kit::command_menu_button(ui, c.id, info.label, info.icon, &shortcut, can_start(app, c))
|
||||
}
|
||||
})
|
||||
.inner;
|
||||
let response =
|
||||
if let Some(hint) = persistent_hint(app, c, info.enabled) { response.on_hover_text(hint) } else { response };
|
||||
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), response.rect.center());
|
||||
@@ -2285,7 +2300,8 @@ fn dropdown_row(
|
||||
let info = crate::render_workspace::interactive_command_info(app, c);
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(width, ROW_H), Sense::hover());
|
||||
// Every row drags onto the toolbar (to pin it there); enabled rows also start their command.
|
||||
let sense = if info.enabled { Sense::click_and_drag() } else { Sense::drag() };
|
||||
let interactive = can_start(app, c);
|
||||
let sense = if interactive { Sense::click_and_drag() } else { Sense::drag() };
|
||||
let resp = ui.interact(rect, dropdown_row_id(c.id), sense);
|
||||
// Focus can settle on the following frame, after the key event has gone.
|
||||
// Keep the focused row visible even then in a drop-down taller than the window.
|
||||
@@ -2300,10 +2316,10 @@ fn dropdown_row(
|
||||
let pin = ui.interact(pr, ui.id().with(("dropdown_pin", c.id)), Sense::click());
|
||||
let pointer_on = (resp.hovered() || pin.hovered()) && !egui::DragAndDrop::has_any_payload(ui.ctx());
|
||||
let label = app.tr_label(info.label).to_string();
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && info.enabled, &label));
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && interactive, &label));
|
||||
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), rect.center());
|
||||
crate::scenario::publish_handle(&format!("dropdown:{}", c.id), rect.center());
|
||||
let hot = info.enabled && (pointer_on || resp.has_focus());
|
||||
let hot = interactive && (pointer_on || resp.has_focus());
|
||||
let mut row = crate::ui_kit::MenuItem::new(c.id, &label, info.icon);
|
||||
row.enabled = info.enabled;
|
||||
if persistent_command(c.id) && app.preferences.persistent_ribbon_pressed {
|
||||
@@ -2352,7 +2368,7 @@ fn dropdown_row(
|
||||
if resp.secondary_clicked() {
|
||||
return RowAction::Menu(resp.interact_pointer_pos().unwrap_or(rect.left_bottom()));
|
||||
}
|
||||
if resp.clicked() && info.enabled {
|
||||
if resp.clicked() && interactive {
|
||||
return RowAction::Start(rect);
|
||||
}
|
||||
RowAction::None
|
||||
@@ -2556,7 +2572,7 @@ fn grid_keys(app: &SolveApp, ui: &egui::Ui, cmds: &[&'static solidcraft_engine::
|
||||
.map(|(_, key)| key)
|
||||
});
|
||||
let Some(key) = key else { return };
|
||||
let enabled: Vec<usize> = cmds.iter().enumerate().filter(|(_, c)| c.info(&app.session).enabled).map(|(i, _)| i).collect();
|
||||
let enabled: Vec<usize> = cmds.iter().enumerate().filter(|(_, c)| can_start(app, c)).map(|(i, _)| i).collect();
|
||||
let focused = ui.ctx().memory(|m| m.focused());
|
||||
let at = enabled.iter().position(|i| cmds.get(*i).is_some_and(|c| focused == Some(dropdown_row_id(c.id))));
|
||||
if let Some(c) = grid_step(cells, &enabled, at, key).and_then(|k| enabled.get(k)).and_then(|i| cmds.get(*i)) {
|
||||
@@ -2655,7 +2671,8 @@ fn pin_armed(ui: &egui::Ui, id: &'static str, on: bool) -> bool {
|
||||
fn dropdown_cell(app: &SolveApp, ui: &mut egui::Ui, c: &'static solidcraft_engine::CommandSpec, rect: Rect, pinned: bool) -> RowAction {
|
||||
let t = Tokens::get();
|
||||
let info = c.info(&app.session);
|
||||
let sense = if info.enabled { Sense::click_and_drag() } else { Sense::drag() };
|
||||
let interactive = can_start(app, c);
|
||||
let sense = if interactive { Sense::click_and_drag() } else { Sense::drag() };
|
||||
let resp = ui.interact(rect, dropdown_row_id(c.id), sense);
|
||||
if resp.has_focus() && !ui.clip_rect().contains_rect(rect) {
|
||||
resp.scroll_to_me_animation(None, egui::style::ScrollAnimation::none());
|
||||
@@ -2672,11 +2689,11 @@ fn dropdown_cell(app: &SolveApp, ui: &mut egui::Ui, c: &'static solidcraft_engin
|
||||
let pin = armed.then(|| ui.interact(pr, ui.id().with(("dropdown_pin", c.id)), Sense::click()));
|
||||
let pin_hovered = pin.as_ref().is_some_and(egui::Response::hovered);
|
||||
let label = app.tr_label(c.label).to_string();
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && info.enabled, &label));
|
||||
resp.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled() && interactive, &label));
|
||||
crate::scenario::publish_handle(&format!("toolbar:{}", c.id), rect.center());
|
||||
crate::scenario::publish_handle(&format!("dropdown:{}", c.id), rect.center());
|
||||
let pointer_on = (resp.hovered() || pin_hovered) && !dragging;
|
||||
let hot = info.enabled && (pointer_on || resp.has_focus());
|
||||
let hot = interactive && (pointer_on || resp.has_focus());
|
||||
let painter = ui.painter().with_clip_rect(ui.clip_rect().intersect(rect));
|
||||
let cell = rect.shrink(2.0);
|
||||
if hot {
|
||||
@@ -2706,7 +2723,7 @@ fn dropdown_cell(app: &SolveApp, ui: &mut egui::Ui, c: &'static solidcraft_engin
|
||||
if resp.secondary_clicked() {
|
||||
return RowAction::Menu(resp.interact_pointer_pos().unwrap_or(rect.left_bottom()));
|
||||
}
|
||||
if resp.clicked() && info.enabled {
|
||||
if resp.clicked() && interactive {
|
||||
return RowAction::Start(rect);
|
||||
}
|
||||
RowAction::None
|
||||
@@ -2759,7 +2776,7 @@ fn dropdown_keys(app: &SolveApp, ui: &egui::Ui, cmds: &[&'static solidcraft_engi
|
||||
return;
|
||||
}
|
||||
ui.ctx().data_mut(|d| d.insert_temp(handled, pass));
|
||||
let enabled: Vec<_> = cmds.iter().filter(|c| crate::render_workspace::interactive_command_info(app, c).enabled).collect();
|
||||
let enabled: Vec<_> = cmds.iter().filter(|c| can_start(app, c)).collect();
|
||||
if enabled.is_empty() {
|
||||
return;
|
||||
}
|
||||
@@ -2858,7 +2875,7 @@ pub const ESCAPE_TWICE_S: f64 = 0.4;
|
||||
/// Global keyboard shortcuts (when no text field has focus).
|
||||
pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
crate::browser::update_folder_pick(app, ctx);
|
||||
if design_shown(app) {
|
||||
if design_shown(app) && !crate::drawing_workspace::active(app) {
|
||||
crate::navigation::step_key_hold(app, ctx);
|
||||
} else {
|
||||
app.viewport.key_hold = None;
|
||||
@@ -2943,7 +2960,12 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
// A theme pasted on the Interface page is a theme, not copied components (#8226).
|
||||
crate::prefs::take_theme_paste(app, ctx);
|
||||
// Home hides the design: only New, Open and Preferences work there.
|
||||
let shown = design_shown(app);
|
||||
// An empty Drawing workspace has no drawing history, selection or camera. Shortcuts
|
||||
// must not silently edit the source design behind it. Explicit source editors retain
|
||||
// their existing shortcuts while open.
|
||||
let drawing =
|
||||
crate::drawing_workspace::active(app) && app.bom.is_none() && app.design_properties.is_none() && !crate::dialogs_sheet::flat_active(app);
|
||||
let shown = design_shown(app) && !drawing;
|
||||
if shown {
|
||||
crate::edit_clipboard::handle(app, ctx);
|
||||
} else {
|
||||
@@ -2951,6 +2973,16 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
|
||||
}
|
||||
for (k, repeat) in keys {
|
||||
use egui::Key;
|
||||
if drawing && k == Key::S && !mods.command && !mods.shift && !mods.alt {
|
||||
app.start("ui.palette");
|
||||
continue;
|
||||
}
|
||||
if drawing && k == Key::Escape {
|
||||
if crate::drawing_workspace::close_panel(app) {
|
||||
app.esc_handled = true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Only Esc pressed twice in a row finishes a sketch: any other key in between resets it.
|
||||
if k != Key::Escape {
|
||||
app.last_escape = None;
|
||||
@@ -4704,7 +4736,7 @@ mod ribbon_label_tests {
|
||||
for command in commands.iter().take(count) {
|
||||
for catalog in crate::i18n::LOCALES {
|
||||
let locale = crate::i18n::Locale::for_language(catalog.id);
|
||||
let label = locale.label(crate::workspace::ribbon_button_label(command));
|
||||
let label = locale.label(crate::workspace::ribbon_button_label_in(section.tab, command));
|
||||
let width = ribbon_button_width(&painter, label) - BUTTON_LABEL_PADDING;
|
||||
let galley = ribbon_button_galley(&painter, label, width, Color32::WHITE);
|
||||
assert!(galley.rows.len() <= 2, "{}: {label}", command.id);
|
||||
|
||||
@@ -9,9 +9,6 @@ pub const RENDER_TABS: &[&str] = &["RENDER"];
|
||||
pub fn render_tab(tab: &str) -> bool {
|
||||
RENDER_TABS.contains(&tab)
|
||||
}
|
||||
pub fn tab_label(tab: &str) -> &str {
|
||||
tab.strip_prefix("RENDER ").unwrap_or(tab)
|
||||
}
|
||||
|
||||
/// A tab's registry name in normal word casing: "SHEET METAL" → "Sheet Metal".
|
||||
pub fn word_cased(name: &str) -> String {
|
||||
@@ -34,6 +31,41 @@ pub fn tab_name(tab: &str) -> String {
|
||||
|
||||
pub const TABS: &[&str] = &["MODEL", "SURFACE", "ASSEMBLE", "INSERT", "INSPECT", "MANAGE", "VIEW"];
|
||||
|
||||
pub const DRAWING_TABS: &[&str] = &["DRAWING LAYOUT", "DRAWING ANNOTATE", "DRAWING PARTS", "DRAWING REVIEW"];
|
||||
|
||||
pub fn drawing_tab(tab: &str) -> bool {
|
||||
DRAWING_TABS.contains(&tab)
|
||||
}
|
||||
|
||||
/// Unique saved tab keys keep Drawing customisations separate from Design.
|
||||
pub fn tab_label(tab: &str) -> &str {
|
||||
match tab {
|
||||
"DRAWING LAYOUT" => "Layout",
|
||||
"DRAWING ANNOTATE" => "Annotate",
|
||||
"DRAWING PARTS" => "Parts & Tables",
|
||||
"DRAWING REVIEW" => "Review",
|
||||
_ => tab.strip_prefix("RENDER ").unwrap_or(tab),
|
||||
}
|
||||
}
|
||||
|
||||
/// Workspace switching has one lifecycle for menus and automation.
|
||||
pub fn enter(app: &mut crate::SolveApp, workspace: &str) -> Result<(), String> {
|
||||
match workspace {
|
||||
"drawing" => {
|
||||
if crate::render_workspace::active(app) {
|
||||
crate::render_workspace::enter(app, false)?;
|
||||
}
|
||||
crate::drawing_workspace::enter(app, true)
|
||||
}
|
||||
"design" if crate::drawing_workspace::active(app) => crate::drawing_workspace::enter(app, false),
|
||||
"design" | "render" => {
|
||||
crate::drawing_workspace::enter(app, false)?;
|
||||
crate::render_workspace::enter(app, workspace == "render")
|
||||
}
|
||||
_ => Err("workspace must be design, render or drawing".into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Reading order of the compact, three-column constraint gallery. Icons come from each spec.
|
||||
pub const CONSTRAINT_GALLERY: &[&str] = &[
|
||||
"sketch.constraint.horizontal_vertical",
|
||||
@@ -106,6 +138,25 @@ const CONFIGURE: &[&str] = &["parameters.change", "config.table", "config.config
|
||||
pub const AGENT_ONLY: &[&str] = &["sketch.solve", "sketch.move3d"];
|
||||
|
||||
pub const SECTIONS: &[Section] = &[
|
||||
Section {
|
||||
tab: "DRAWING LAYOUT",
|
||||
panel: "VIEWS",
|
||||
promoted: 3,
|
||||
ids: &["drawing.view.base.planned", "drawing.view.projected.planned", "drawing.view.section", "drawing.view.detail"],
|
||||
},
|
||||
Section { tab: "DRAWING LAYOUT", panel: "ARRANGE", promoted: 1, ids: &["drawing.views.align"] },
|
||||
Section { tab: "DRAWING LAYOUT", panel: "INSERT", promoted: 1, ids: &["drawing.image", "drawing.line"] },
|
||||
Section { tab: "DRAWING LAYOUT", panel: "RESOURCES", promoted: 1, ids: &["drawing.resources"] },
|
||||
Section { tab: "DRAWING ANNOTATE", panel: "MEASURE", promoted: 2, ids: &["drawing.dimension", "drawing.hole_callout"] },
|
||||
Section { tab: "DRAWING ANNOTATE", panel: "NOTES", promoted: 2, ids: &["drawing.note", "drawing.leader"] },
|
||||
Section { tab: "DRAWING ANNOTATE", panel: "SYMBOLS", promoted: 2, ids: &["drawing.datum", "drawing.centre_mark"] },
|
||||
Section { tab: "DRAWING ANNOTATE", panel: "ARRANGE", promoted: 2, ids: &["drawing.annotations.align", "drawing.annotations.space"] },
|
||||
Section { tab: "DRAWING PARTS", panel: "ASSEMBLY", promoted: 2, ids: &["drawing.parts_list", "drawing.balloon"] },
|
||||
Section { tab: "DRAWING PARTS", panel: "TABLES", promoted: 2, ids: &["drawing.table.edit", "drawing.revision_table"] },
|
||||
Section { tab: "DRAWING PARTS", panel: "SOURCE", promoted: 2, ids: &["assembly.bom", "sheet.flat_pattern"] },
|
||||
Section { tab: "DRAWING REVIEW", panel: "CHANGES", promoted: 2, ids: &["drawing.update", "drawing.check"] },
|
||||
Section { tab: "DRAWING REVIEW", panel: "REVISION", promoted: 2, ids: &["drawing.revision", "drawing.revision_table"] },
|
||||
Section { tab: "DRAWING REVIEW", panel: "OUTPUT", promoted: 2, ids: &["drawing.export.planned", "drawing.print"] },
|
||||
Section {
|
||||
tab: "MODEL",
|
||||
panel: "SKETCH",
|
||||
@@ -414,6 +465,23 @@ pub const RIBBON_LABELS: &[(&str, &str)] = &[
|
||||
("contact.check", "Contact"),
|
||||
("contact.enable_sets", "Contact Sets"),
|
||||
("document.inspect", "Inspect"),
|
||||
("drawing.annotations.align", "Align Notes"),
|
||||
("drawing.annotations.space", "Space Notes"),
|
||||
("drawing.check", "Check Drawing"),
|
||||
("drawing.dimension", "Dimension"),
|
||||
("drawing.export.planned", "Export"),
|
||||
("drawing.hole_callout", "Hole Callout"),
|
||||
("drawing.image", "Image"),
|
||||
("drawing.leader", "Leader"),
|
||||
("drawing.note", "Note"),
|
||||
("drawing.print", "Print Drawing"),
|
||||
("drawing.resources", "Resources"),
|
||||
("drawing.revision", "Add Revision"),
|
||||
("drawing.revision_table", "Revision Tbl."),
|
||||
("drawing.table.edit", "Edit Table"),
|
||||
("drawing.update", "Update"),
|
||||
("drawing.view.projected.planned", "Projected"),
|
||||
("drawing.view.section", "Section View"),
|
||||
("document.units", "Units"),
|
||||
("inspect.accessibility", "Access"),
|
||||
("inspect.center_of_mass", "Mass Center"),
|
||||
@@ -471,6 +539,18 @@ pub fn ribbon_family_label(family: &crate::sketch_families::Family) -> &'static
|
||||
family.commands.first().and_then(|first| RIBBON_LABELS.iter().find(|(id, _)| id == first)).map_or(family.label, |(_, label)| *label)
|
||||
}
|
||||
|
||||
/// Source utilities retain their Design names; Drawing makes the inspected scope explicit.
|
||||
pub fn ribbon_button_label_in<'a>(tab: &str, command: &'a CommandSpec) -> &'a str {
|
||||
if drawing_tab(tab) {
|
||||
match command.id {
|
||||
"assembly.bom" => return "Source BOM",
|
||||
"sheet.flat_pattern" => return "Source Flat",
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
ribbon_button_label(command)
|
||||
}
|
||||
|
||||
/// Registry placement → default ribbon destination, after explicit id overrides.
|
||||
pub const PLACEMENTS: &[(&str, &str, &str, &str)] = &[
|
||||
("SOLID", "CREATE", "MODEL", "CREATE"),
|
||||
@@ -515,6 +595,8 @@ pub fn destination(c: &CommandSpec) -> Option<(&'static str, &'static str)> {
|
||||
pub(crate) fn legacy_render_destination(c: &CommandSpec) -> Option<(&'static str, &'static str)> {
|
||||
// A few shared commands have a canonical home that differs from their first occurrence.
|
||||
let primary = match c.id {
|
||||
"assembly.bom" => Some(("INSPECT", "CHECK")),
|
||||
"sheet.flat_pattern" => Some(("SHEET METAL", "FLAT")),
|
||||
"sheet.manage_rules" => Some(("MANAGE", "RULES")),
|
||||
"sheet.convert" => Some(("MODEL", "MODIFY")),
|
||||
"parameters.change" => Some(("MANAGE", "PARAMETERS")),
|
||||
@@ -664,6 +746,9 @@ pub fn set_tab_shown(app: &mut crate::SolveApp, tab: &str, shown: bool) {
|
||||
|
||||
/// Every design tab stays hidden until the person chooses to show it again.
|
||||
pub fn visible_tabs(app: &crate::SolveApp) -> Vec<&'static str> {
|
||||
if crate::drawing_workspace::active(app) {
|
||||
return DRAWING_TABS.to_vec();
|
||||
}
|
||||
if crate::render_workspace::active(app) {
|
||||
return Vec::new();
|
||||
}
|
||||
@@ -701,7 +786,7 @@ pub fn starts_a_sketch(app: &crate::SolveApp, id: &str) -> bool {
|
||||
/// other tabs to remain usable for the rest of that context. Sketch takes precedence over sheet
|
||||
/// metal, and sheet metal over weldments.
|
||||
pub fn sync_context(app: &mut crate::SolveApp) {
|
||||
if crate::render_workspace::active(app) {
|
||||
if crate::render_workspace::active(app) || crate::drawing_workspace::active(app) {
|
||||
return;
|
||||
}
|
||||
let context = app
|
||||
@@ -810,6 +895,65 @@ mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
#[test]
|
||||
fn drawing_uses_the_ribbon_without_changing_the_source_design() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.app.run("solid.box", json!({"length":40,"width":20,"height":3})).unwrap();
|
||||
h.app.ui.tab = "SURFACE".into();
|
||||
let before = h.app.session.doc.clone();
|
||||
let revision = h.app.session.revision;
|
||||
h.call("ui.workspace", json!({"workspace":"drawing"}));
|
||||
h.frames(4);
|
||||
assert_eq!(visible_tabs(&h.app), DRAWING_TABS);
|
||||
for tab in DRAWING_TABS {
|
||||
assert!(crate::scenario::handle_at(&format!("tab:{tab}")).is_some(), "{tab}");
|
||||
}
|
||||
h.step(&json!({"click":{"handle":"button:drawing.view.base.planned"}})).unwrap();
|
||||
h.frames(3);
|
||||
assert!(crate::scenario::rect_of("drawing:settings").is_some(), "known options open from the shared ribbon");
|
||||
assert!(h.widget("Apply (not implemented)").is_some());
|
||||
assert!(h.app.run("drawing.view.base.planned", json!({})).is_err(), "the preview never enables engine execution");
|
||||
h.step(&json!({"click":{"handle":"drawing:settings:close"}})).unwrap();
|
||||
h.step(&json!({"click":{"handle":"tab:DRAWING PARTS"}})).unwrap();
|
||||
h.step(&json!({"click":{"handle":"button:assembly.bom"}})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(h.app.bom.is_some(), "Source BOM opens the existing inspector");
|
||||
assert_eq!(h.app.ui.tab, "DRAWING PARTS");
|
||||
assert_eq!(h.app.session.doc, before);
|
||||
assert_eq!(h.app.session.revision, revision);
|
||||
h.app.bom = None;
|
||||
h.call("ui.workspace", json!({"workspace":"design"}));
|
||||
h.frames(2);
|
||||
assert_eq!(h.app.ui.tab, "SURFACE", "return restores the source workspace context");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drawing_search_and_panels_work_from_the_keyboard() {
|
||||
let mut h = crate::scenario::Harness::new();
|
||||
h.call("ui.workspace", json!({"workspace":"drawing"}));
|
||||
h.frames(3);
|
||||
let before = h.app.session.doc.clone();
|
||||
let revision = h.app.session.revision;
|
||||
h.step(&json!({"key":"S"})).unwrap();
|
||||
assert!(h.app.ui.palette_open, "S opens the shared Drawing command search");
|
||||
h.step(&json!({"text":"Base View"})).unwrap();
|
||||
h.step(&json!({"key":"Enter"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(!h.app.ui.palette_open);
|
||||
assert!(crate::scenario::rect_of("drawing:settings").is_some(), "search opens known options");
|
||||
h.step(&json!({"key":"Escape"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(crate::scenario::rect_of("drawing:settings").is_none(), "Escape closes options");
|
||||
h.step(&json!({"click":{"handle":"drawing:sheets:settings"}})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(crate::scenario::rect_of("drawing:settings").is_some());
|
||||
h.step(&json!({"key":"Escape"})).unwrap();
|
||||
h.frames(2);
|
||||
assert!(crate::scenario::rect_of("drawing:settings").is_none(), "Escape closes Sheet Settings");
|
||||
assert_eq!(h.app.session.doc, before);
|
||||
assert_eq!(h.app.session.revision, revision);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn modify_groups_order_commands_and_keep_unlisted_commands_at_the_end() {
|
||||
let specs = solidcraft_engine::command_specs();
|
||||
@@ -855,7 +999,12 @@ mod tests {
|
||||
continue;
|
||||
}
|
||||
if command.label.chars().count() > 12 || command.label.split_whitespace().any(|word| word.chars().count() > 9) {
|
||||
assert!(RIBBON_LABELS.iter().any(|(id, _)| *id == command.id), "missing compact label for {}", command.id);
|
||||
assert!(
|
||||
RIBBON_LABELS.iter().any(|(id, _)| *id == command.id)
|
||||
|| ribbon_button_label_in(section.tab, command) != ribbon_button_label(command),
|
||||
"missing compact label for {}",
|
||||
command.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -869,6 +1018,13 @@ mod tests {
|
||||
let construction = crate::sketch_families::family("sketch.construction_line").unwrap();
|
||||
assert_eq!(ribbon_family_label(construction), "X-Lines");
|
||||
assert_eq!(construction.label, "Construction Line");
|
||||
for (id, design, drawing) in [("assembly.bom", "Bill of Materials", "Source BOM"), ("sheet.flat_pattern", "Flat Pattern", "Source Flat")] {
|
||||
let command = solidcraft_engine::find_command(id).unwrap();
|
||||
assert_eq!(ribbon_button_label(command), design);
|
||||
assert_eq!(ribbon_button_label_in("MODEL", command), design);
|
||||
assert_eq!(ribbon_button_label_in("DRAWING PARTS", command), drawing);
|
||||
assert!(drawing.chars().count() <= 13);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -24,8 +24,32 @@ fn escape(h: &mut Harness) {
|
||||
h.app.design_properties = None;
|
||||
}
|
||||
|
||||
fn planned_drawing_placeholder(id: &str) -> bool {
|
||||
if solidcraft_engine::cmd::drawing_pending_icon(id).is_none() {
|
||||
return false;
|
||||
}
|
||||
solidcraft_engine::find_command(id).is_some_and(|command| command.planned.is_some() && command.icon == icon_svg::PLACEHOLDER)
|
||||
}
|
||||
|
||||
fn expected_planned_drawing_placeholder(h: &Harness) -> bool {
|
||||
// The renderer records icon names, not command ids. Only these two surfaces may
|
||||
// request the literal provisional glyph; unknown ids remain failures everywhere.
|
||||
if workspace::drawing_tab(&h.app.ui.tab) {
|
||||
return solidcraft_engine::command_specs().iter().any(|command| command.tab == h.app.ui.tab && planned_drawing_placeholder(command.id));
|
||||
}
|
||||
!h.app.sbox.text.is_empty()
|
||||
&& solidcraft_ui_egui::shortcut_box::search(&h.app, &h.app.sbox.text).iter().take(56).any(|row| planned_drawing_placeholder(row.0))
|
||||
}
|
||||
|
||||
fn permitted_request(name: &str, placeholder: bool, planned_drawing: bool) -> bool {
|
||||
!placeholder || (name == icon_svg::PLACEHOLDER && planned_drawing)
|
||||
}
|
||||
|
||||
fn collect(h: &mut Harness, surface: &str, requested: &mut BTreeMap<String, bool>) {
|
||||
let reply = h.call("ui.icon_audit", json!({}));
|
||||
let planned_drawing = expected_planned_drawing_placeholder(h);
|
||||
// The control wrapper settles after draining. Read the same audit API directly,
|
||||
// so its extra frames cannot leave requests for the next, unrelated surface.
|
||||
let reply = icon_svg::audit::take();
|
||||
use std::io::Write as _;
|
||||
let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../target/icon-audit");
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
@@ -35,15 +59,16 @@ fn collect(h: &mut Harness, surface: &str, requested: &mut BTreeMap<String, bool
|
||||
"{surface}\tpanel={:?}\ttool={:?}\t{}",
|
||||
h.app.dialog.as_ref().map(|d| &d.kind),
|
||||
h.app.tool.as_ref().map(|t| t.cmd),
|
||||
reply["result"]["requested"]
|
||||
serde_json::to_string(&reply).unwrap()
|
||||
)
|
||||
.unwrap();
|
||||
for item in reply["result"]["requested"].as_array().expect("audit requests") {
|
||||
let name = item["name"].as_str().unwrap();
|
||||
let placeholder = item["placeholder"].as_bool().unwrap();
|
||||
for item in reply {
|
||||
let name = item.name.as_str();
|
||||
let placeholder = item.placeholder;
|
||||
if placeholder && !requested.contains_key(name) {
|
||||
eprintln!("{surface}: {name} fell back to the placeholder");
|
||||
}
|
||||
assert!(permitted_request(name, placeholder, planned_drawing), "{surface}: unexpected placeholder request {name}");
|
||||
requested.insert(name.to_owned(), placeholder);
|
||||
}
|
||||
}
|
||||
@@ -69,7 +94,7 @@ fn menu(h: &mut Harness, target: Value, surface: &str, requested: &mut BTreeMap<
|
||||
// Planned workspace rows deliberately have no artwork until their workspace exists.
|
||||
let intentional = id == "-" || id.starts_with('#') || (id == "ui.workspace" && item["shortcut"] == "coming later");
|
||||
assert!(!blank || intentional, "{surface}: icon-less menu item {item}");
|
||||
assert_eq!(item["placeholder"], false, "{surface}: {item}");
|
||||
assert!(item["placeholder"] == false || (item["icon"] == icon_svg::PLACEHOLDER && planned_drawing_placeholder(id)), "{surface}: {item}");
|
||||
if let Some(children) = item["children"].as_array() {
|
||||
for child in children {
|
||||
let id = child["id"].as_str().unwrap();
|
||||
@@ -79,7 +104,10 @@ fn menu(h: &mut Harness, target: Value, surface: &str, requested: &mut BTreeMap<
|
||||
}
|
||||
let icon = child["icon"].as_str().unwrap();
|
||||
assert!(!icon.is_empty(), "{surface}: submenu row has no icon: {child}");
|
||||
assert!(!solidcraft_ui_egui::icon_svg::resolve(icon).placeholder, "{surface}: submenu placeholder {icon}");
|
||||
assert!(
|
||||
!icon_svg::resolve(icon).placeholder || (icon == icon_svg::PLACEHOLDER && planned_drawing_placeholder(id)),
|
||||
"{surface}: submenu placeholder {icon}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,6 +269,23 @@ fn requested_menu_icons_have_artwork_and_match_manifest() {
|
||||
ribbon(&mut h, &mut requested);
|
||||
solidcraft_ui_egui::render_workspace::enter(&mut h.app, false).unwrap();
|
||||
h.frames(3);
|
||||
{
|
||||
// Independent egui context keeps closing Drawing popup animations out of the
|
||||
// following Design menu scenes, where the provisional glyph is forbidden.
|
||||
let mut drawing = Harness::new();
|
||||
drawing.app.home.open = false;
|
||||
workspace::enter(&mut drawing.app, "drawing").unwrap();
|
||||
drawing.frames(3);
|
||||
collect(&mut drawing, "Drawing sheet strip and browser", &mut requested);
|
||||
ribbon(&mut drawing, &mut requested);
|
||||
click(&mut drawing, "drawing:sheets:manage");
|
||||
collect(&mut drawing, "Drawing sheet management and docking", &mut requested);
|
||||
escape(&mut drawing);
|
||||
click(&mut drawing, "drawing:sheets:settings");
|
||||
collect(&mut drawing, "Drawing Sheet Settings", &mut requested);
|
||||
click(&mut drawing, "drawing:settings:close");
|
||||
collect(&mut drawing, "Drawing panel closed", &mut requested);
|
||||
}
|
||||
|
||||
let body = h.app.session.model.state().bodies.first().unwrap().name.clone();
|
||||
let sketch = h.app.session.world_state().sketches.first().unwrap().feature;
|
||||
@@ -508,10 +553,26 @@ fn requested_menu_icons_have_artwork_and_match_manifest() {
|
||||
"Runtime icon discovery changed. Regenerate with UPDATE_ICON_MANIFEST=1 cargo test -p solidcraft-ui-egui --test icon_audit"
|
||||
);
|
||||
}
|
||||
let missing = requested.iter().filter(|(_, placeholder)| **placeholder).map(|(name, _)| name).collect::<Vec<_>>();
|
||||
// Each request was checked in its Drawing/search context by `collect` above.
|
||||
let missing = requested
|
||||
.iter()
|
||||
.filter(|(name, placeholder)| **placeholder && name.as_str() != icon_svg::PLACEHOLDER)
|
||||
.map(|(name, _)| name)
|
||||
.collect::<Vec<_>>();
|
||||
assert!(missing.is_empty(), "Runtime placeholder requests (see surface diagnostics above): {missing:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intentional_drawing_requests_do_not_allow_unknown_or_unplanned_icons() {
|
||||
assert!(planned_drawing_placeholder("drawing.view.base.planned"));
|
||||
assert!(!planned_drawing_placeholder("drawing.unknown"));
|
||||
assert!(!planned_drawing_placeholder("solid.extrude"));
|
||||
assert!(permitted_request(icon_svg::PLACEHOLDER, true, true));
|
||||
assert!(!permitted_request(icon_svg::PLACEHOLDER, true, false));
|
||||
assert!(!permitted_request("missing_audit_test_icon", true, true));
|
||||
assert!(!permitted_request("drawing_unknown", true, true));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn audit_is_opt_in_drains_and_records_resolved_aliases() {
|
||||
icon_svg::audit::stop();
|
||||
|
||||
@@ -478,9 +478,16 @@ Other `ui.prefs` parameters:
|
||||
- `stand_up: true` stands up a just-opened Y-up file, `stand_up: false` leaves it as it is ("Leave it": the offer, dialog or note, closes and `offer_stand_up` stays on)
|
||||
- the dialog's buttons are the handles `stand_up:stand`, `stand_up:leave` and `stand_up:never`
|
||||
|
||||
`ui.workspace {workspace?: "design" | "render"}` reads or changes the active workspace.
|
||||
`ui.workspace {workspace?: "design" | "render" | "drawing"}` reads or changes the active workspace.
|
||||
Entering Render requires finishing the current modelling operation; returning restores
|
||||
the Design camera. See [the integrated Render workspace](render-workspace.md).
|
||||
Drawing currently exposes the native UI scaffold with a blank canvas. Planned
|
||||
operations refuse engine execution; `ui.start` can inspect the agreed Base View,
|
||||
Projected View, Dimension and Note option layouts. `ui.prefs` accepts
|
||||
`drawing_sheet_dock` (`top`, `bottom`, `under_browser`), `drawing_panel_side`
|
||||
(`left`, `right`) and `drawing_panel_width` (240–560). These preferences do not
|
||||
edit the source design.
|
||||
See [Drawing UI implementation](drawing-workspace-ui.md).
|
||||
|
||||
### Sketch alignment guides
|
||||
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
# Drawing workspace UI implementation roadmap
|
||||
|
||||
The first native Drawing UI provides the agreed workspace layout as an integration
|
||||
target for drawing contributors. It reuses the application shell and existing
|
||||
source-design services. It does not implement drawing documents, sheet rendering,
|
||||
view generation, annotations, tables, reference updates or paper exports.
|
||||
|
||||
## Baseline and ownership
|
||||
|
||||
The implementation branch `codex/drawing-workspace-ui` starts at the freshly fetched
|
||||
`drawings` commit `3b982ca371ceded6a8f4695bc46e4b3618de0ac9`. The Drawing workspace
|
||||
claim remains [PR #4895](https://github.com/tangency-labs/solidcraft/pull/4895), based
|
||||
on [#4894](https://github.com/tangency-labs/solidcraft/issues/4894). This UI-only slice
|
||||
does not replace its end-to-end drawing acceptance milestone or create another claim.
|
||||
|
||||
The baseline has no Drawing file/session or paper renderer. The storage, paper,
|
||||
view-definition, reference and annotation contracts remain separate work. Do not
|
||||
import the older experimental preview's embedded design metadata, private rendering
|
||||
or provisional drawing records to fill these gaps.
|
||||
|
||||
## Planned commits
|
||||
|
||||
| Commit | Change | Acceptance |
|
||||
| --- | --- | --- |
|
||||
| 1 | Record the implementation scope, reuse boundaries and commit sequence | The scaffold's available services and exclusions are explicit. |
|
||||
| 2 | Add original Drawing icon drafts | New artwork remains Open in the existing review tool; runtime icons remain placeholders until approval. Exact-meaning existing icons remain reusable. |
|
||||
| 3 | Integrate planned tool metadata, deliberate placeholders, the native Drawing workspace and shared ribbon, empty canvas, source Browser and docked settings | Four task tabs use existing pins, section lists, command search and customization. Design camera, history and selection remain intact when switching workspaces. No drawing object or design metadata is fabricated. Acceptance coverage checks compact/floating layouts, docking, unavailable controls, source services, state isolation, wasm and native screenshots. |
|
||||
|
||||
Each feature arc must build before its commit. Run the repository quality gates
|
||||
before committing; use focused UI/engine tests during iteration. Changes stay on
|
||||
the implementation branch and are supplied to the existing workspace claim for
|
||||
integration rather than opening a competing claim.
|
||||
|
||||
## Shared implementation boundaries
|
||||
|
||||
Use `workspace::Section`, the existing toolbar/customization pipeline and command
|
||||
registry. Drawing has Layout, Annotate, Parts & Tables, and Review task tabs.
|
||||
Promoted icons and unfolded section headers reach the same command identity;
|
||||
pinning changes toolbar preferences rather than running an operation.
|
||||
|
||||
The canvas stays empty until a real paper renderer is available. It must not show
|
||||
hand-painted paper, sample CAD geometry or pretend hit-testing. Any canvas camera
|
||||
uses the shared navigation handler with orbit disabled. Browser content describes
|
||||
the linked source and available drawing state, rather than presenting the Design
|
||||
feature tree as drawing objects.
|
||||
|
||||
Sheet navigation uses a content-sized strip beside the Browser. One sheet requires
|
||||
one short row, with adjacent add, management and settings controls. A history-style
|
||||
grip and highlighted drop targets support top, bottom and beneath-Browser docking;
|
||||
the latter is a vertical list. Full paper details are shown on demand. With no
|
||||
Drawing document, sheet creation, duplication and other document changes remain
|
||||
unavailable rather than creating local substitute sheet records.
|
||||
|
||||
Sheet Settings follows the Assistant's left/right dock and bounded resize pattern.
|
||||
It shows current state when a provider exists; agreed defaults can be inspected
|
||||
read-only before that. Settings does not use an operation Apply/Cancel workflow.
|
||||
Known operation-option previews may open for unavailable tools, but their execution
|
||||
stays disabled. Do not invent uncertain parameter forms or enabled Apply behavior.
|
||||
|
||||
Use shared semantic containers and theme tokens for all panels, menus and overlays.
|
||||
Register their occupied rectangles with the shared shell where needed so pointer
|
||||
events cannot pass through floating UI. Keep layout, docking and widths in UI
|
||||
preferences, outside source-design undo. No new design-file format is needed.
|
||||
|
||||
## Feature homes and later provider work
|
||||
|
||||
| Roadmap capability | UI home | Provider boundary |
|
||||
| --- | --- | --- |
|
||||
| K1–K12 / W11–W15: drawing records, links, relink, save, tabs, browser, per-file undo | Document/file shell; source status in Browser; sheet strip for sheets only | Add real Drawing documents before enabling file or sheet edits. Source design metadata is separate. |
|
||||
| S1–S3: sheet size, borders, title blocks and templates | Sheet Settings, including resource selection | Read and edit current sheet configuration through document commands; reuse the panel rather than adding separate ribbon tools. |
|
||||
| W17a / W17b–W18: view definitions, base/projected/section/detail views | Layout → Views; shared operation panel | Add validated view records and the paper scene through their own packages. |
|
||||
| W09 / W19–W20: number formats, dimensions, notes and symbols | Defaults in Sheet Settings; creation in Annotate | Replace readonly previews only when the corresponding engine command and reference handling exist. |
|
||||
| P1–P3b / M1–M3 / W02–W08: material, mass, property keys, units and geometry foundations | Existing source properties and source BOM; formatting in Sheet Settings | These services supply evaluated values; UI code must not recalculate physical properties or copy model records. |
|
||||
| P1–P7 / W24–W26 / W31: BOM, parts lists, balloons and revision tables | Parts & Tables; revision table also reachable from Review | Source BOM remains a source service; a paper table needs its own document record and rendering. |
|
||||
| W13–W14: source references and updates | Browser source status; Review → Changes | Provide actual update/repair results before enabling the commands. |
|
||||
| W10 / W21–W22: paper scene and DXF/SVG/PNG/PDF output | Canvas provider; Review → Output | Share scene evaluation with exports. Current Design-view DXF is a different operation. |
|
||||
| W16 / W23: workspace and first complete drawing | Shared shell and ribbon in this slice; acceptance drawing later | This scaffold provides the visual anchor, not the full milestone. |
|
||||
|
||||
Provider increments should be separate commits/PRs for their owning roadmap task.
|
||||
Replace a planned command in place, preserving its dotted identity and shared
|
||||
ribbon entry. A configured operation must execute the engine command; a programmatic
|
||||
call must never depend on opening its UI. Introduce no parallel UI-only CAD state.
|
||||
|
||||
## Existing services
|
||||
|
||||
| Control | Existing implementation | Scope |
|
||||
| --- | --- | --- |
|
||||
| Bill of materials | `assembly.bom` and the existing BOM window | Reads actual source assembly quantities/properties and existing CSV/JSON output; does not place a paper parts list. |
|
||||
| Inspect source flat pattern | `sheet.flat_pattern` and its existing inspector | Uses the source sheet-metal body and existing manufacturing DXF/bend-table actions; does not place a view. |
|
||||
| Source properties | Existing design-properties UI and metadata commands | Edits the source design only; never substitutes for drawing title fields. |
|
||||
|
||||
Drawing Export/Print, view placement, dimensions, balloons, revision records,
|
||||
source repair and drawing Save/Open remain unavailable until their actual providers
|
||||
exist. Current-view DXF is not drawing-sheet export.
|
||||
|
||||
## Unavailable controls and icons
|
||||
|
||||
Unavailable tools are faded and explain their missing capability. Clicking a faded
|
||||
tool is permitted only for an explicit known-options allowlist. Programmatic calls
|
||||
still return an unsupported result and never open a dialog or mutate a document.
|
||||
Controls with uncertain configuration remain noninteractive.
|
||||
|
||||
Reuse an existing icon only when its meaning is identical: adding, settings,
|
||||
management, navigation, actual source BOM, or the exact existing source inspector.
|
||||
Drawing views, drafting annotations and paper tables do not borrow unrelated
|
||||
modeling or Sketch icons. Their runtime assignments use the unimplemented
|
||||
placeholder while original first-version artwork is prepared for human review.
|
||||
Unused drafts must appear Open in the icon review tool; agents never approve their
|
||||
own artwork or change protected acceptance hashes.
|
||||
|
||||
## Verification
|
||||
|
||||
UI scenarios cover entering/leaving Drawing, all four task tabs, shared ribbon
|
||||
unfolding/pinning, faded known-options versus disabled uncertain tools, compact
|
||||
sheet navigation, all three strip docks, settings side/width changes, and both
|
||||
application layouts. Unsupported operations must leave the source document and its
|
||||
history unchanged. Existing Design navigation and source service entry points must
|
||||
continue to work.
|
||||
|
||||
Run `cargo xtask ci`, including assets, icons, layers, shared-container checks,
|
||||
Clippy, tests and wasm. Build and run the native app and capture a small public-safe
|
||||
workspace screenshot. Keep generated screenshots and recordings outside tracked
|
||||
source. Record any baseline failures separately from failures introduced here.
|
||||