mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-18 14:32:36 +00:00
Design: slot Radius caption, keyboard offer, plane combo removal, mass props, docs
Mirror of snaporca ac85277bac..0e7cb3ec78 (six changes, applied as a patch to DesignPanel.cpp rather than copied, so this fork's 30 permitted divergent lines survive — parity re-checked afterwards: the five shared files are byte-identical, DesignCanvas.cpp and DesignPanel.cpp differ by exactly 16 and 30 lines). - The straight slot's inline field says Radius, which is what it sets. It stores the half-width and passed the typed number through unchanged, so 30 produced a 60 mm slot. - The offer opens from the keyboard (Menu, Shift+F10), anchored on the viewport rather than wherever the pointer happens to be. The card hint names the new route. - The sketch card's Plane combo is gone; the plane comes from the viewport. Also stops build_candidate collapsing a face plane to a base plane while editing. - Mass properties and the dead Edit row are wired into the offer; DesignOffer.hpp is regenerated from tool_atlas.json, verified by re-running the generator and diffing. - docs/rig_build_traps.md + scripts/rig-build.sh, which derives its fork identity from project() so it cannot be pointed at the other fork's image or volume. - docs/design_tab.md refreshed (44 commits stale) + a PR description, with this fork's own merge-base and diff shape rather than snaporca's. Built green in the deps container with the new script and verified on the rig: Menu and Shift+F10 both open the offer at the viewport centre with the pointer parked off-canvas, and the sketch card shows no Plane row. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LyRwbuq6fjn3VV9U9UvhBM
This commit is contained in:
co-authored by
Claude Opus 5
parent
6e9910303b
commit
fd1bc092d8
+138
-14
@@ -1,8 +1,9 @@
|
||||
# The Design tab
|
||||
|
||||
A parametric CAD modeller inside the slicer. Draw a sketch, turn it into a solid, refine it,
|
||||
and send it straight to Prepare — without leaving for another application and coming back
|
||||
through an STL.
|
||||
Object-driven parametric CAD inside the slicer. Point at geometry; the geometry offers the
|
||||
verbs that apply to it. Selection comes first and the tool consumes it. Draw a sketch,
|
||||
constrain it, turn it into a solid, refine it, and send it straight to Prepare — without
|
||||
leaving for another application and coming back through an STL.
|
||||
|
||||
The model is a **recipe**, not a mesh. Every action becomes a feature in a tree that is
|
||||
replayed from the start whenever anything changes, so editing a dimension you set twenty
|
||||
@@ -14,19 +15,142 @@ already ships for STEP import.
|
||||
## Getting started
|
||||
|
||||
1. Open the **Design** tab.
|
||||
2. Pick a plane — XY, XZ or YZ — and press **Sketch**.
|
||||
3. Draw a closed profile, then press **✓** to finish the sketch.
|
||||
4. Select the sketch and press **Extrude** (`Shift+E`).
|
||||
2. Click a face or a reference plane in the viewport, then press `Shift+S` (Sketch). The offer
|
||||
opens with the sketch tools on it.
|
||||
3. Draw a closed profile, then press **✓ Confirm** in the floating action bar.
|
||||
4. With the sketch selected, press `Shift+E` (Extrude).
|
||||
5. Press **Commit to Plate** to hand the solid to Prepare.
|
||||
|
||||
The status line under the toolbar is the thing to watch: it says what the current tool is
|
||||
waiting for, and it is where a refusal explains itself.
|
||||
waiting for. When no plane is picked it reads *"Click a face or a reference plane in the
|
||||
viewport, then a sketch tool"*; once one is picked it reads *"Sketching on <face> — pick a
|
||||
tool"*. It is also where a refusal explains itself.
|
||||
|
||||
---
|
||||
|
||||
## Selecting
|
||||
|
||||
- One left-click selects what is under the cursor. There is no click-cycling through
|
||||
face → edge → body.
|
||||
- A click near a corner takes the corner, not the face behind it.
|
||||
- Left-drag sweeps a rubber band, and a rubber band takes the whole body.
|
||||
- An open sketch line can be clicked, even where it bounds a region.
|
||||
- Double-click a sketch stroke to edit it — the gesture belongs on the geometry.
|
||||
- Editing a dimension's value **updates** that dimension instead of adding a second one next
|
||||
to it.
|
||||
- The floating chrome that belongs to a sketch leaves with the sketch when it ends.
|
||||
- Sketching happens on the face you clicked, first click.
|
||||
- A sketch whose entities form no wire **fails** instead of extruding a default box. A
|
||||
subtraction that removes nothing is reported as an error instead of a silent success.
|
||||
|
||||
---
|
||||
|
||||
## The offer
|
||||
|
||||
Right-click on the geometry, released without moving the mouse (an 8 px budget — a
|
||||
right-drag that orbits the camera does not open it). Left-click still only selects, so
|
||||
pointing at things stays quiet.
|
||||
|
||||
The offer also opens by itself the moment you press Sketch on a face or plane, showing the
|
||||
sketch tools — the app hands you the tools directly.
|
||||
|
||||
**Eight families, always in this fixed order:** Create, Add material, Remove, Dress-up,
|
||||
Repeat, Transform, Reference, Modify.
|
||||
|
||||
- A family with at least one applicable verb shows it. Several applicable verbs collapse
|
||||
into a submenu under the family name.
|
||||
- A family with nothing applicable is **shown greyed in place, with the reason** — e.g.
|
||||
*"Create — Click a face or a reference plane in the viewport, then a sketch tool"*. It is
|
||||
not hidden. A control that cannot be used still says what it is and what you would have to
|
||||
do first.
|
||||
- Inside a sketch the offer shows the sketch verbs; outside it shows the feature verbs.
|
||||
|
||||
**Document-level actions never enter the offer**, because they act on the document and not
|
||||
on a selection: Import STEP, Import mesh, Text, SVG, Export STEP, Commit to Plate, Undo,
|
||||
Redo, Variables, Section view, Origin planes, World axes. They live in the toolbar.
|
||||
|
||||
---
|
||||
|
||||
## Keyboard
|
||||
|
||||
Single letters drive sketch tools **while a sketch is open**; Shift+letter drives feature
|
||||
tools and single letters drive view toggles **when no sketch is open**. The two maps are
|
||||
selected by the mode, not by whether a sketch session is running.
|
||||
|
||||
### Sketch (while a sketch is open)
|
||||
|
||||
| Key | Tool |
|
||||
|---|---|
|
||||
| `L` | Line — click start, then end |
|
||||
| `R` | Rectangle — click two opposite corners |
|
||||
| `C` | Circle — click centre, then radius |
|
||||
| `A` | Arc — click start, end, then a point |
|
||||
| `S` | Slot — two centreline ends, then width |
|
||||
| `E` | Ellipse — centre, major end, minor point |
|
||||
| `B` | Spline — click control points |
|
||||
| `P` | Point — click to place |
|
||||
| `G` | Polygon — click centre, then a vertex |
|
||||
| `D` | Dimension — click 2 points or an entity |
|
||||
| `T` | Trim — click a segment to trim it |
|
||||
| `X` | Extend — click a line/arc to extend it |
|
||||
| `O` | Offset — pick an entity, drag the distance |
|
||||
| `M` | Mirror — pick axis, then entities |
|
||||
| `F` | Fillet — pick two lines, set the radius |
|
||||
| `H` | Chamfer — pick two lines, set the distance |
|
||||
| `K` | Constrain — finish the live sketch and enter constrain |
|
||||
| `Q` | Construction toggle — draw the next entity as construction geometry |
|
||||
| `Del` | Delete the selected sketch entity |
|
||||
| `Esc` | Cancel the live tool |
|
||||
|
||||
### Feature (when no sketch is open)
|
||||
|
||||
| Key | Tool |
|
||||
|---|---|
|
||||
| `Shift+S` | Sketch |
|
||||
| `Shift+E` | Extrude — extrude a profile, or push/pull a picked face |
|
||||
| `Shift+R` | Revolve |
|
||||
| `Shift+W` | Sweep |
|
||||
| `Shift+L` | Loft |
|
||||
| `Shift+N` | Pattern |
|
||||
| `Shift+G` | Surface Extrude |
|
||||
| `Shift+J` | Surface Revolve |
|
||||
| `Shift+O` | Surface Loft |
|
||||
| `Shift+Q` | Surface Fill |
|
||||
| `Shift+U` | Surface Offset |
|
||||
| `Shift+V` | Thicken Surface |
|
||||
| `Shift+P` | Plane |
|
||||
| `Shift+A` | Axis |
|
||||
| `Shift+C` | Coord Sys |
|
||||
| `Shift+Y` | Transform |
|
||||
| `Shift+Z` | Mirror |
|
||||
| `Shift+B` | Boolean |
|
||||
| `Shift+X` | Cut |
|
||||
| `Shift+F` | Fillet / Chamfer |
|
||||
| `Shift+D` | Draft |
|
||||
| `Shift+K` | Shell |
|
||||
| `Shift+H` | Hole |
|
||||
| `Shift+T` | Thread |
|
||||
| `Shift+I` | Import STEP |
|
||||
| `Shift+M` | Import mesh |
|
||||
|
||||
### View toggles (single letters, when no sketch is open)
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `P` | Origin planes on/off |
|
||||
| `A` | World axes on/off |
|
||||
| `X` | Section view on/off |
|
||||
|
||||
While the section is on: `PageUp` / `PageDown` move the cut plane, `F` flips which half is
|
||||
kept. With no section on, `F` is Place on Face — lay the picked face flat on the bed.
|
||||
|
||||
---
|
||||
|
||||
## Sketching
|
||||
|
||||
A sketch is a closed (or open) 2D profile on a plane or on a flat face of an existing body.
|
||||
Press `Shift+S`, click the face or plane you want to sketch on, and draw. The toolbar and
|
||||
the offer both carry the sketch tools.
|
||||
|
||||
**Entities:** line, polyline, rectangle (corner / centre / oblique / rounded), circle
|
||||
(centre-radius / 2-point / 3-point), arc (centre-point / 3-point / tangent), ellipse and
|
||||
@@ -168,10 +292,10 @@ mesh.
|
||||
|
||||
## View controls
|
||||
|
||||
**Section view** hides half the model so you can see inside — `PageUp`/`PageDown` move the
|
||||
plane, Flip shows the other half, `Delete` removes it. **Place on Face** (`F`) lays a picked
|
||||
face flat on the bed. The bed and its grid can be toggled off when they get in the way, and
|
||||
origin planes and world axes can be shown while you orient yourself.
|
||||
**Section view** (`X`) hides half the model so you can see inside — `PageUp`/`PageDown` move
|
||||
the plane, `F` flips which half is kept. **Place on Face** (`F`, when section is off) lays a
|
||||
picked face flat on the bed. Origin planes (`P`) and world axes (`A`) can be toggled on while
|
||||
you orient yourself.
|
||||
|
||||
---
|
||||
|
||||
@@ -182,12 +306,12 @@ Being straight about the edges, so nobody discovers them the hard way:
|
||||
- **Rib** needs a sketch containing an explicit open line. A parametric rectangle sketch
|
||||
carries no individual entities, so Rib cannot use one.
|
||||
- **Surface Loft** and **Surface Fill** have kernel tests but have not been exercised by hand.
|
||||
- Two kernel tests are known-broken and excluded from the suite: a tangent-constraint case
|
||||
that aborts inside the vendored solver, and an internal-thread groove volume below its
|
||||
asserted threshold. Both are tracked, neither is fixed.
|
||||
- Card wiring for 9 of the 16 late-wired tools has never been click-tested.
|
||||
- Mate resolves by composing transforms directly. There is no 3D assembly solver, so mates
|
||||
are applied in order rather than solved simultaneously, and mate limits are not implemented.
|
||||
- Move-face and replace-face are not implemented — OCCT offers no clean primitive for them.
|
||||
- There is no automated GUI test in CI. Every behaviour above is traced to code and to a
|
||||
hand pass, not to a synthetic click.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# Design (CAD) tab — upstream pull request
|
||||
|
||||
## What this adds
|
||||
|
||||
A sketch-first parametric CAD tab inside the slicer. The workflow is direct:
|
||||
sketch → constrain → solid features → commit to plate. The whole feature recipe is
|
||||
persisted inside the 3MF, so reopening restores an editable model rather than a frozen mesh.
|
||||
|
||||
- Kernel: OCCT, which upstream already links for STEP import — see
|
||||
[cad_dependency_weight.md](docs/cad_dependency_weight.md)
|
||||
- Constraint solver: vendored SolveSpace `libslvs` subset
|
||||
- Interaction model: object-driven — point at geometry, the geometry offers the verbs that
|
||||
apply to it; see [cad_ux_guidelines.md](docs/cad_ux_guidelines.md)
|
||||
- Full user-facing documentation: [design_tab.md](docs/design_tab.md)
|
||||
|
||||
## Why it belongs in the slicer
|
||||
|
||||
Every round trip through an external CAD tool costs a file export, a re-import, and the
|
||||
design intent that both steps discard. A part modified after slicing should return to its
|
||||
feature history, not to a mesh. Keeping the CAD model inside the slicer preserves that
|
||||
loop — the nozzle diameter, the build volume and the material are known at design time.
|
||||
|
||||
For the integration case in full: [design_tab_upstream_portability.md](docs/design_tab_upstream_portability.md).
|
||||
|
||||
## How it is built
|
||||
|
||||
The `SLIC3R_CAD` CMake flag (default ON) gates the entire tab. With it OFF the tab is not
|
||||
compiled and the deps prefix matches upstream exactly — the dependency diff is one line in
|
||||
OCCT's CMake: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`.
|
||||
|
||||
Measured cost table: [cad_dependency_weight.md](docs/cad_dependency_weight.md).
|
||||
|
||||
## Diff shape
|
||||
|
||||
<!-- fork-specific: measured against this fork's upstream base; re-run the commands above after mirroring -->
|
||||
|
||||
Against merge-base `d6cb667b894f`:
|
||||
|
||||
306 files changed, 83032 insertions(+), 777 deletions(-)
|
||||
|
||||
350 commits, of which 284 are new files and 37 modify upstream files. 99.3 % of the diff
|
||||
is new code. The negotiable surface is the 37 modified files.
|
||||
|
||||
## Tests
|
||||
|
||||
205 `TEST_CASE` blocks across 6 new test source files. This counts assertions written, not
|
||||
assertions passed — a run needs a build.
|
||||
|
||||
`scripts/kernel-test.sh` is the headless verification contract: it builds only
|
||||
`libslic3r_tests` (not the GUI app), needs no display, and exit 0 means the CAD suite
|
||||
passed. It now runs with **no exclusions** — both cases that used to be quarantined (the
|
||||
circle-line tangency solver abort and the internal-thread reference) are fixed.
|
||||
|
||||
## Licensing
|
||||
|
||||
The vendored solver in `src/libslic3r/slvs/` is **GPL-3.0** (see `src/libslic3r/slvs/LICENSE`),
|
||||
not LGPL. The combined work is distributable under AGPL-3.0. See the Licensing section of
|
||||
[design_tab_upstream_portability.md](docs/design_tab_upstream_portability.md) for the
|
||||
AGPLv3/GPLv3 compatibility argument; this point should be confirmed with upstream explicitly.
|
||||
|
||||
## Not verified
|
||||
|
||||
- Card wiring for 9 of the 16 late-wired tools was never click-tested.
|
||||
- There is no automated GUI test in CI. A green kernel run says nothing about the GUI —
|
||||
synthetic clicks never drift, so the test suite and the viewport are two separate realities.
|
||||
- The click-test defect rate has **not converged**: a second pass found no new defects, but
|
||||
four further days of work found five more. The earlier pass is not evidence of stability.
|
||||
|
||||
## Reviewer's map
|
||||
|
||||
See the [Where the code lives](docs/design_tab.md#where-the-code-lives) table in the user
|
||||
doc for the file-to-role mapping, and [docs/ux/tool_atlas.json](docs/ux/tool_atlas.json) as
|
||||
the generated-from source of `src/slic3r/GUI/DesignOffer.hpp`.
|
||||
@@ -0,0 +1,123 @@
|
||||
# Rig build traps
|
||||
|
||||
The build rig is two long-lived containers, `snaporca-gui` and `orcacad-gui`, one per fork. Each
|
||||
mounts only its fork's build volume (`snaporca_buildcache` / `orcacad_buildcache`) at
|
||||
`/OrcaSlicer/build`, its fork's `resources/`, and a shots directory — nothing else. They run the
|
||||
binary; they do not build it. Rebuild with `scripts/rig-build.sh`.
|
||||
|
||||
| fork repo | project() | deps image | build volume | GUI container | binary |
|
||||
|---|---|---|---|---|---|
|
||||
| `snaporca` | `Snapmaker_Orca` | `snaporca-deps` | `snaporca_buildcache` | `snaporca-gui` | `snapmaker-orca` |
|
||||
| `orca_cad` | `OrcaSlicer` | `orcacad-deps` | `orcacad_buildcache` | `orcacad-gui` | `orca-slicer` |
|
||||
|
||||
`scripts/rig-build.sh` exists alongside `scripts/docker-iter-build.sh` for one reason: it does a
|
||||
target-only `ninja` into the volume the GUI rig launches from, so a session can test a single
|
||||
change without a full repackage, whereas `docker-iter-build.sh` runs the full packaged build.
|
||||
Both start a throwaway container from the deps image with the live repo mounted over the baked
|
||||
tree — never build inside the GUI container (Trap 1).
|
||||
|
||||
Every trap below has already cost about a session to re-derive, once each. They are recorded now
|
||||
so no fresh session pays them again. Symptoms, causes, and exact recovery commands follow.
|
||||
|
||||
---
|
||||
|
||||
## Trap 1 — never configure inside the GUI container
|
||||
|
||||
**Symptom.** After building inside the GUI container, the fork's targets no longer exist; ninja
|
||||
reports an unknown target, and `orca-slicer` / `OrcaSlicer` have been replaced by
|
||||
`snapmaker-orca` / `Snapmaker_Orca`.
|
||||
|
||||
**Cause.** The GUI image's baked `/OrcaSlicer` tree is the Jun-13 Snapmaker-derived source
|
||||
(`project(Snapmaker_Orca)`, executable `snapmaker-orca`). `orcacad-deps` is layered on
|
||||
`snaporca-deps`, so even on the mainline fork the baked tree is the other fork's. A `cmake .`
|
||||
there reconfigures the shared build dir under the wrong project name.
|
||||
|
||||
**Fix.** Build only via `scripts/rig-build.sh`, which starts a throwaway container from the deps
|
||||
image with the live repo mounted over the baked tree — `src`, `resources`, `cmake`, `deps_src`,
|
||||
`localization`, `CMakeLists.txt`, `version.inc` — and writes into the same volume the rig
|
||||
launches from.
|
||||
|
||||
---
|
||||
|
||||
## Trap 2 — stale `NLopt_DIR` in CMakeCache
|
||||
|
||||
**Symptom.** Configure fails with `Cannot find NLopt library 'nlopt_cxx' in '<prefix>/lib'`.
|
||||
|
||||
**Cause.** `cmake/modules/FindNLopt.cmake:26` is `set(NLopt_DIR $ENV{NLOPT})`. With `NLOPT`
|
||||
unset that expands to `set(NLopt_DIR)` — zero arguments — which *unsets the normal variable* and
|
||||
lets a leftover CACHE entry of the same name (e.g. `<prefix>/lib/cmake/nlopt`) show through the
|
||||
following `if(NOT NLopt_DIR)`. The `else()` branch then searches for `nlopt_cxx` under
|
||||
`${NLopt_DIR}/lib` with `NO_DEFAULT_PATH`, while the deps prefix ships plain `nlopt`.
|
||||
|
||||
**Fix.** From inside the build dir:
|
||||
|
||||
cmake -U NLopt_DIR -U NLopt_LIBS .
|
||||
|
||||
Do **not** `sed` the entry out of `CMakeCache.txt` — deleting a line breaks the cache parser.
|
||||
|
||||
---
|
||||
|
||||
## Trap 3 — the image lacks `deps_src/pybind11`
|
||||
|
||||
**Symptom.** Configure aborts with `pybind11 headers not found in /OrcaSlicer/deps_src/pybind11.
|
||||
Did you initialize submodules?` (the `FATAL_ERROR` guarding `PYBIND11_SOURCE_DIR` in the mainline
|
||||
fork's root `CMakeLists.txt`, near line 948).
|
||||
|
||||
**Cause.** The deps image predates that requirement. Only the mainline (`orca_cad`) fork has
|
||||
`deps_src/pybind11` and the requirement; snaporca has neither.
|
||||
|
||||
**Fix.** Mount `deps_src` over the baked tree — `scripts/rig-build.sh` does. Corollary: mounting a
|
||||
snaporca tree into an `orcacad-deps` build reproduces this error exactly.
|
||||
|
||||
---
|
||||
|
||||
## Trap 4 — `OCCT_LIBS` lags one configure
|
||||
|
||||
**Symptom.** A wall of undefined references to `TopOpeBRepBuild` symbols. It reads as a broken
|
||||
OCCT installation. It is not.
|
||||
|
||||
**Cause.** `src/libslic3r/CMakeLists.txt:603` does
|
||||
`set(OCCT_LIBS "${OCCT_LIBS}" CACHE INTERNAL "OCCT toolkits linked by libslic3r")` at the END of
|
||||
its own configure, while the consumer in the root `CMakeLists.txt` (`if (NOT OCCT_LIBS)` …
|
||||
`foreach (_tk IN LISTS OCCT_LIBS)`) reads whatever is already in the cache. The first reconfigure
|
||||
after the `TKFillet TKOffset` prepend (`src/libslic3r/CMakeLists.txt:599`) therefore links the
|
||||
previous list and drops `TKBool`/`TKOffset`.
|
||||
|
||||
**Fix.** Configure twice. `scripts/rig-build.sh` runs `cmake .` twice for exactly this reason; if
|
||||
you ever configure by hand, run it twice.
|
||||
|
||||
---
|
||||
|
||||
## Trap 5 — `SLIC3R_CAD=ON` in the cache, macro never defined
|
||||
|
||||
**Symptom.** The build succeeds and links, but the Design tab is simply absent — or it fails with
|
||||
`class GLCanvas3D has no member named set_design_sketch_tool`.
|
||||
|
||||
**Cause.** The cache carries `SLIC3R_CAD=ON`, but the root `CMakeLists.txt` actually configured is
|
||||
a stale baked copy that predates the gate and never runs `add_definitions(-DSLIC3R_CAD)` (the
|
||||
gate is `if (SLIC3R_CAD)` / `add_definitions(-DSLIC3R_CAD)` in the root list — line 179/180 in
|
||||
snaporca, 319/320 in orca_cad). Every `#ifdef SLIC3R_CAD` block therefore compiles out while the
|
||||
option still reads ON.
|
||||
|
||||
**Fix.** Always mount the live `CMakeLists.txt` and `cmake/` — never inherit them from the image.
|
||||
This is why `scripts/docker-iter-build.sh`, `scripts/kernel-test.sh` and `scripts/rig-build.sh`
|
||||
all mount both.
|
||||
|
||||
---
|
||||
|
||||
## The binary the rig actually launches
|
||||
|
||||
`ninja <target>` writes `/OrcaSlicer/build/src/Release/<binary>`; only `build_linux.sh`
|
||||
additionally packages to `/OrcaSlicer/build/package/bin/<binary>`. `orca_cad`'s
|
||||
`scripts/gui-session.sh` defaults `BIN` to `src/Release/orca-slicer`, but snaporca's defaults to
|
||||
`package/bin/snapmaker-orca`. So after a target-only rebuild on snaporca, launching
|
||||
`gui-session.sh` with its default runs the **stale packaged** binary — the change under test is
|
||||
invisible and the session hunts a phantom. Pass `BIN` explicitly:
|
||||
|
||||
docker exec -e BIN=/OrcaSlicer/build/src/Release/snapmaker-orca snaporca-gui /OrcaSlicer/scripts/gui-session.sh
|
||||
|
||||
`scripts/rig-build.sh` prints the correct line for the current fork when it finishes.
|
||||
|
||||
Note also that the GUI containers do **not** mount `scripts/`: `/OrcaSlicer/scripts` inside them
|
||||
is the baked copy, so a local edit to `gui-session.sh` has no effect until you
|
||||
`docker cp scripts/gui-session.sh <container>:/OrcaSlicer/scripts/`.
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -829,8 +829,8 @@
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": null,
|
||||
"gui": false,
|
||||
"action": null
|
||||
"gui": true,
|
||||
"action": "btn:mass"
|
||||
},
|
||||
{
|
||||
"id": "interference",
|
||||
@@ -871,7 +871,7 @@
|
||||
"needs": {},
|
||||
"refusal": null,
|
||||
"gui": true,
|
||||
"action": null
|
||||
"action": "btn:edit"
|
||||
},
|
||||
{
|
||||
"id": "delete_face",
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# On success the binary is inside the persistent volume at
|
||||
# /OrcaSlicer/build/package/bin/orca-slicer (copy it out with a follow-up
|
||||
# `docker run --rm -v orcacad_buildcache:/b alpine cp ...` or via this script's tail).
|
||||
# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
# working tree to a per-volume staging dir on that host and re-runs this same script
|
||||
# there, so the verification contract is identical either way. Drop --host once the image
|
||||
# is present locally.
|
||||
# Rig build traps already paid for once each (stale project, NLopt cache, pybind11, OCCT_LIBS, SLIC3R_CAD gate): docs/rig_build_traps.md
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
# Rebuild the GUI binary the design rig launches — in a THROWAWAY container, writing into the
|
||||
# same build-cache volume the rig's long-lived GUI container reads from.
|
||||
#
|
||||
# NEVER build inside the GUI container (snaporca-gui / orcacad-gui). Its baked /OrcaSlicer tree
|
||||
# is the Jun-13 Snapmaker-derived source, so a `cmake .` in there silently reconfigures the
|
||||
# shared build dir as project(Snapmaker_Orca) and this fork's targets vanish. That is Trap 1 of
|
||||
# five; all of them, with symptoms and exact recovery commands, are in docs/rig_build_traps.md.
|
||||
# Read that file before debugging a configure or link failure this script reports.
|
||||
#
|
||||
# Usage:
|
||||
# scripts/rig-build.sh # configure + build the fork's GUI target
|
||||
# DRY_RUN=1 scripts/rig-build.sh # print the resolved fork identity and exit, no container
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
|
||||
# Fork identity is DERIVED from the repo, never hardcoded, so this file is byte-identical in
|
||||
# both forks and cannot be mirrored into the wrong one. Pointing a fork at the other fork's
|
||||
# image or volume is not a slow failure: with the wrong image CMake dies at configure, and with
|
||||
# the wrong volume the two forks silently trade build artefacts.
|
||||
PROJECT="$(sed -n 's/^project(\([A-Za-z_0-9]*\)).*/\1/p' "$REPO/CMakeLists.txt" | head -1)"
|
||||
case "$PROJECT" in
|
||||
Snapmaker_Orca) PREFIX=snaporca; BIN=snapmaker-orca ;;
|
||||
OrcaSlicer) PREFIX=orcacad; BIN=orca-slicer ;;
|
||||
*) echo "FATAL: unrecognised project($PROJECT) in $REPO/CMakeLists.txt" >&2; exit 2 ;;
|
||||
esac
|
||||
TARGET="$PROJECT"
|
||||
IMAGE="${PREFIX}-deps"
|
||||
BUILD_VOL="${PREFIX}_buildcache"
|
||||
|
||||
echo "REPO=$REPO PROJECT=$PROJECT IMAGE=$IMAGE BUILD_VOL=$BUILD_VOL TARGET=$TARGET BIN=$BIN"
|
||||
|
||||
if [ -n "${DRY_RUN:-}" ]; then
|
||||
echo "DRY_RUN: resolution only, no container started"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Every one of these mounts covers a trap, none is decorative:
|
||||
# CMakeLists.txt + cmake/ carry the SLIC3R_CAD gate — inherit the baked copies and the cache
|
||||
# says SLIC3R_CAD=ON while -DSLIC3R_CAD is never defined, so every #ifdef block compiles out.
|
||||
# deps_src/ carries pybind11, which the image predates.
|
||||
# src/, resources/, localization/, version.inc are the code under test.
|
||||
rc=0
|
||||
docker run --rm \
|
||||
-v "$REPO/src":/OrcaSlicer/src \
|
||||
-v "$REPO/resources":/OrcaSlicer/resources \
|
||||
-v "$REPO/cmake":/OrcaSlicer/cmake \
|
||||
-v "$REPO/deps_src":/OrcaSlicer/deps_src \
|
||||
-v "$REPO/localization":/OrcaSlicer/localization \
|
||||
-v "$REPO/CMakeLists.txt":/OrcaSlicer/CMakeLists.txt \
|
||||
-v "$REPO/version.inc":/OrcaSlicer/version.inc \
|
||||
-v "$BUILD_VOL":/OrcaSlicer/build \
|
||||
"$IMAGE" bash -lc "
|
||||
cd /OrcaSlicer/build || exit 1
|
||||
cmake . > /tmp/cfg.log 2>&1 || { echo 'CONFIGURE FAILED'; tail -25 /tmp/cfg.log; exit 1; }
|
||||
# Twice, deliberately. src/libslic3r/CMakeLists.txt publishes OCCT_LIBS as CACHE INTERNAL
|
||||
# at the END of its own configure, so a first pass after that list changes links the
|
||||
# PREVIOUS one and drops TKBool/TKOffset — a wall of TopOpeBRepBuild undefined references
|
||||
# that reads as a broken OCCT install and is not. Trap 4.
|
||||
cmake . > /tmp/cfg2.log 2>&1 || { echo 'RECONFIGURE FAILED'; tail -25 /tmp/cfg2.log; exit 1; }
|
||||
ninja -f build-Release.ninja -j\"$(nproc)\" $TARGET > /tmp/bld.log 2>&1
|
||||
rc=\$?
|
||||
echo \"EXIT=\$rc\"
|
||||
grep -n 'error:' /tmp/bld.log | head -20
|
||||
tail -4 /tmp/bld.log
|
||||
ls -la /OrcaSlicer/build/src/Release/$BIN 2>/dev/null
|
||||
exit \$rc
|
||||
" || rc=$?
|
||||
|
||||
# A target-only build writes src/Release/, but this fork's gui-session.sh may default BIN to the
|
||||
# PACKAGED path that only build_linux.sh refreshes — launching with the default would then run a
|
||||
# stale binary. Pass BIN explicitly. See docs/rig_build_traps.md.
|
||||
echo "=== launch the rig on the binary just built ==="
|
||||
echo " docker exec -e BIN=/OrcaSlicer/build/src/Release/$BIN ${PREFIX}-gui /OrcaSlicer/scripts/gui-session.sh"
|
||||
exit "$rc"
|
||||
@@ -51,7 +51,7 @@ public:
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane);
|
||||
void set_sketch_tool(DesignSketchTool::Mode mode);
|
||||
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when the Plane dropdown changes
|
||||
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when a reference plane is clicked in 3D
|
||||
void set_sketch_construction(bool c);
|
||||
void set_sketch_polygon_sides(int n);
|
||||
void set_sketch_polygon_circumscribed(bool c);
|
||||
|
||||
@@ -108,9 +108,9 @@ static const OfferVerb kOfferVerbs[] = {
|
||||
{"helix", "Helix", 6, nullptr, "fly:plane#3", nullptr, 0x00000405u, 0, 0, false, false},
|
||||
{"project", "Project", 6, nullptr, "fly:plane#4", "Project needs a body — add or import one first", 0x00000482u, 1, 0, false, false},
|
||||
{"measure", "Measure", 6, nullptr, nullptr, nullptr, 0x000b03feu, 0, 0, false, false},
|
||||
{"mass_props", "Mass", 6, nullptr, nullptr, nullptr, 0x00000080u, 1, 0, false, false},
|
||||
{"mass_props", "Mass", 6, nullptr, "btn:mass", nullptr, 0x00000080u, 1, 0, false, false},
|
||||
{"interference", "Interference", 6, nullptr, nullptr, nullptr, 0x00000200u, 2, 0, false, false},
|
||||
{"edit_feature", "Edit", 7, nullptr, nullptr, nullptr, 0x00007d8eu, 0, 0, false, false},
|
||||
{"edit_feature", "Edit", 7, nullptr, "btn:edit", nullptr, 0x00007d8eu, 0, 0, false, false},
|
||||
{"delete_face", "Delete Face", 7, nullptr, "fly:dressup#3", "Delete Face needs a body — add or import one first", 0x0000000eu, 1, 0, false, false},
|
||||
{"colour", "Colour", 7, nullptr, "btn:colour", nullptr, 0x00000180u, 1, 0, false, false},
|
||||
{"delete", "Delete", 7, "Del", "btn:delete", nullptr, 0x000f7f80u, 0, 0, false, false},
|
||||
|
||||
@@ -375,7 +375,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
sk_key('R', DesignSketchTool::Mode::CornerRect, _L("Rectangle — click two opposite corners"));
|
||||
sk_key('C', DesignSketchTool::Mode::CenterCircle, _L("Circle — click center, then radius"));
|
||||
sk_key('A', DesignSketchTool::Mode::ThreePointArc,_L("Arc — click start, end, then a point"));
|
||||
sk_key('S', DesignSketchTool::Mode::Slot, _L("Slot — two centerline ends, then width"));
|
||||
sk_key('S', DesignSketchTool::Mode::Slot, _L("Slot — two centerline ends, then end radius"));
|
||||
sk_key('E', DesignSketchTool::Mode::Ellipse, _L("Ellipse — center, major end, minor point"));
|
||||
sk_key('B', DesignSketchTool::Mode::BSpline, _L("Spline — click control points"));
|
||||
sk_key('P', DesignSketchTool::Mode::Point, _L("Point — click to place"));
|
||||
@@ -533,7 +533,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_status->Refresh();
|
||||
if (m_sketch_hint) { // the card must agree with the status line, not argue with it
|
||||
m_sketch_hint->SetLabel(have_plane
|
||||
? wxString::Format(_L("Drawing on %s.\nPick a tool, or right-click for the list."), where)
|
||||
? wxString::Format(_L("Drawing on %s.\nPick a tool, or press Menu for the list."), where)
|
||||
: _L("Click a face or a reference plane, then a sketch tool."));
|
||||
m_sketch_hint->Refresh();
|
||||
m_cards->Layout();
|
||||
@@ -542,7 +542,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// mode change has settled before a modal menu takes the loop; the menu carries each
|
||||
// tool's shortcut, so pressing the key instead of picking a row costs nothing.
|
||||
if (have_plane)
|
||||
CallAfter([this] { show_offer_menu(wxGetMousePosition()); });
|
||||
CallAfter([this] { show_offer_menu(offer_anchor()); });
|
||||
};
|
||||
b_sketch->Bind(wxEVT_BUTTON, [act_sketch](wxCommandEvent&) { act_sketch(); });
|
||||
m_keys_feature[SHIFT('S')] = act_sketch;
|
||||
@@ -898,6 +898,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
fadd("color", b_color);
|
||||
m_verb_actions["btn:colour"] = [this] { on_set_body_color(); };
|
||||
m_verb_actions["btn:delete"] = [this] { on_delete_feature(); };
|
||||
m_verb_actions["btn:edit"] = [this] { on_edit_feature(); };
|
||||
m_verb_actions["btn:mass"] = [this] { on_mass_properties(); };
|
||||
|
||||
// Dress-up: finishing operations on the faces and edges of an existing solid — nothing
|
||||
// that moves a body (see the Placement drawer) and nothing that creates geometry.
|
||||
@@ -1132,7 +1134,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
{"design_tangentarc", DesignSketchTool::Mode::TangentArc, _L("Tangent arc"), _L("Click start (on the last entity) then end")},
|
||||
{"design_arc_center", DesignSketchTool::Mode::CenterArc, _L("Center-point arc"), _L("Click center, then start, then a point for the end angle")} });
|
||||
dropdown("design_slot", _L("Slot"), {
|
||||
{"design_slot", DesignSketchTool::Mode::Slot, _L("Slot"), _L("Click two centerline ends, then a point for width")},
|
||||
{"design_slot", DesignSketchTool::Mode::Slot, _L("Slot"), _L("Click two centerline ends, then a point for the end radius")},
|
||||
{"design_slot_arc", DesignSketchTool::Mode::ArcSlot, _L("Arc slot"), _L("Click center, start, end, then a point for the width")} });
|
||||
dropdown("design_ellipse", _L("Ellipse"), {
|
||||
{"design_ellipse", DesignSketchTool::Mode::Ellipse, _L("Ellipse"), _L("Click center, a major-axis end, then a point for the minor axis")},
|
||||
@@ -1419,13 +1421,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
form->Add(new wxStaticText(m_cards, wxID_ANY, _L("Shape")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
form->Add(m_shape, 0, wxEXPAND);
|
||||
|
||||
m_plane = make_combo(m_cards);
|
||||
m_plane->Append(_L("XY"));
|
||||
m_plane->Append(_L("XZ"));
|
||||
m_plane->Append(_L("YZ"));
|
||||
m_plane->SetSelection(0);
|
||||
form->Add(new wxStaticText(m_cards, wxID_ANY, _L("Plane")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
form->Add(m_plane, 0, wxEXPAND);
|
||||
// NO plane row. A sketch takes its plane from what is picked in the VIEWPORT — a planar face
|
||||
// on a solid, or one of the reference-plane ghosts clicked in 3D — resolved by
|
||||
// sketch_plane_from_selection(). A three-row XY/XZ/YZ combo could not express either of those
|
||||
// targets, so it displayed a value that was at best redundant and at worst false. snaporca-e1p.
|
||||
|
||||
m_width = make_spin(m_cards, 20);
|
||||
form->Add(new wxStaticText(m_cards, wxID_ANY, _L("Width / X")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -3473,6 +3472,20 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
const bool dismissable = m_active != Tool::None || (m_viewport && m_viewport->moving_body());
|
||||
if (key == WXK_ESCAPE && dismissable) { tool_cancel(); return; }
|
||||
|
||||
// The offer from the keyboard (charter 4.1): the Menu key, or Shift+F10 for keyboards that
|
||||
// do not have one. Same menu the right-click opens — show_offer_menu already decides which
|
||||
// half of the map applies via sketch_map_applies(), so nothing about the content is decided
|
||||
// here. With no press to anchor it, offer_anchor() puts it on the viewport.
|
||||
// WXK_MENU is the GTK code for the physical Menu key (GDK_KEY_Menu); wxMSW instead sends
|
||||
// WXK_WINDOWS_MENU for VK_APPS and maps Alt to WXK_ALT, so accepting both is safe
|
||||
// everywhere. CallAfter because the menu is modal: let the key event finish dispatching
|
||||
// before a nested loop takes the queue, the same reason the Sketch entry does it.
|
||||
if (!in_text && !ctrl
|
||||
&& (key == WXK_MENU || key == WXK_WINDOWS_MENU || (key == WXK_F10 && e.ShiftDown()))) {
|
||||
CallAfter([this] { show_offer_menu(offer_anchor()); });
|
||||
return;
|
||||
}
|
||||
|
||||
// Ctrl+Z / Ctrl+Shift+Z / Ctrl+Y — undo/redo handled here (not only in the GL canvas) so
|
||||
// it works even when the canvas lost keyboard focus. In a sketch, undo drops the last entity.
|
||||
if (!in_text && ctrl && (key == 'Z' || key == 'z' || key == WXK_CONTROL_Z ||
|
||||
@@ -4089,13 +4102,14 @@ void DesignPanel::on_add_sketch()
|
||||
{
|
||||
SketchShape shape = (m_shape->GetSelection() == 1) ? SketchShape::Circle
|
||||
: SketchShape::Rectangle;
|
||||
SketchPlane plane = plane_from_choice(m_plane->GetSelection());
|
||||
wxString where; // named for the status line
|
||||
SketchPlane plane = sketch_plane_from_selection(where); // picked face, else the 3D plane click
|
||||
m_feature_counter++;
|
||||
m_doc.add_sketch(shape, plane, m_width->GetValue(), m_height->GetValue(),
|
||||
m_radius->GetValue(), "Sketch" + std::to_string(m_feature_counter));
|
||||
m_doc.recompute(); // a lone sketch yields an empty body; that is expected
|
||||
m_status->SetForegroundColour(wxNullColour);
|
||||
m_status->SetLabel(_L("Sketch added — select it and Extrude"));
|
||||
m_status->SetLabel(wxString::Format(_L("Sketch added on %s — select it and Extrude"), where));
|
||||
refresh_tree();
|
||||
}
|
||||
|
||||
@@ -4712,6 +4726,31 @@ void DesignPanel::on_check_interference()
|
||||
wxMessageBox(msg, _L("Interference"), wxOK, this);
|
||||
}
|
||||
|
||||
// Mass properties of the selected solid. A report, not a feature: it never checkpoints, never
|
||||
// recomputes and never opens a card, which is why it sits beside the interference check rather
|
||||
// than in the on_add_* family. The caller only reaches us with m_sel_solid_body in range.
|
||||
void DesignPanel::on_mass_properties()
|
||||
{
|
||||
const auto mp = GeometryEngine::mass_properties(m_doc.bodies[m_sel_solid_body].shape);
|
||||
if (!mp.valid) {
|
||||
m_status->SetForegroundColour(wxColour(235, 110, 110));
|
||||
m_status->SetLabel(_L("Mass properties could not be computed for this body"));
|
||||
m_status->Refresh();
|
||||
return;
|
||||
}
|
||||
// 1-based, and the body's own name when it has one — the same wording the parts list uses.
|
||||
wxString name = wxString::Format(_L("Body %d"), m_sel_solid_body + 1);
|
||||
if (!m_doc.bodies[m_sel_solid_body].name.empty())
|
||||
name = wxString::FromUTF8(m_doc.bodies[m_sel_solid_body].name);
|
||||
m_status->SetForegroundColour(wxNullColour);
|
||||
m_status->SetLabel(wxString::Format(_L("%s: %.3f cm³, %.2f cm²"),
|
||||
name, mp.volume / 1000.0, mp.surface_area / 100.0));
|
||||
m_status->Refresh();
|
||||
wxMessageBox(wxString::Format(_L("%s\n\nVolume: %.3f cm³\nSurface area: %.2f cm²"),
|
||||
name, mp.volume / 1000.0, mp.surface_area / 100.0),
|
||||
_L("Mass properties"), wxOK, this);
|
||||
}
|
||||
|
||||
// The rows are only the SHEET bodies, so a row index is NOT a body index — with a solid at 0
|
||||
// and a sheet at 1 the single row is row 0 but body 1. Every caller must therefore read the
|
||||
// real body index out of the client data (3-arg Append; the 2-arg form takes a bitmap), never
|
||||
@@ -5016,6 +5055,17 @@ void DesignPanel::run_offer_action(const char* action)
|
||||
it->second();
|
||||
}
|
||||
|
||||
wxPoint DesignPanel::offer_anchor() const
|
||||
{
|
||||
const wxPoint mouse = wxGetMousePosition();
|
||||
if (!m_viewport)
|
||||
return mouse;
|
||||
const wxRect r = m_viewport->GetScreenRect();
|
||||
if (r.Contains(mouse))
|
||||
return mouse;
|
||||
return wxPoint(r.x + r.width / 2, r.y + r.height / 2);
|
||||
}
|
||||
|
||||
void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
{
|
||||
const int kind = offer_selection_kind();
|
||||
@@ -7167,7 +7217,6 @@ void DesignPanel::load_feature_into_dialog(const CadFeature& f)
|
||||
switch (f.type) {
|
||||
case CadFeatureType::Sketch:
|
||||
m_shape->SetSelection(f.shape == SketchShape::Circle ? 1 : 0);
|
||||
m_plane->SetSelection(index_from_plane(f.plane));
|
||||
m_width->SetValue(f.width);
|
||||
m_height->SetValue(f.height);
|
||||
m_radius->SetValue(f.radius);
|
||||
@@ -7770,7 +7819,11 @@ CadFeature DesignPanel::build_candidate(Tool t) const
|
||||
case Tool::Sketch:
|
||||
f.type = CadFeatureType::Sketch;
|
||||
f.shape = (m_shape->GetSelection() == 0) ? SketchShape::Rectangle : SketchShape::Circle;
|
||||
f.plane = plane_from_choice(m_plane->GetSelection());
|
||||
// The plane is STRUCTURAL, like Extrude's profile source. While EDITING it is preserved
|
||||
// from the seeded original — the card carries no plane control and the old combo silently
|
||||
// collapsed a face plane to a base plane through the modeling origin. While ADDING it
|
||||
// comes from what is picked in the viewport. snaporca-e1p.
|
||||
if (!editing) { wxString where; f.plane = sketch_plane_from_selection(where); }
|
||||
f.width = m_width->GetValue();
|
||||
f.height = m_height->GetValue();
|
||||
f.radius = m_radius->GetValue();
|
||||
|
||||
@@ -127,6 +127,7 @@ private:
|
||||
void on_add_helix();
|
||||
void on_add_mate();
|
||||
void on_check_interference();
|
||||
void on_mass_properties(); // read-only report on the selected solid; edits nothing
|
||||
// Fill m_bool_target / m_bool_tool / m_cut_target. as_of_feature < 0 = current bodies (add);
|
||||
// >= 0 = the bodies as they existed just before that feature index (Boolean re-edit, so a
|
||||
// consumed tool body still appears and its saved selection round-trips).
|
||||
@@ -405,7 +406,6 @@ private:
|
||||
// from "nobody has chosen anything yet". This does.
|
||||
bool m_plane_picked{false};
|
||||
ComboBox* m_shape{nullptr};
|
||||
ComboBox* m_plane{nullptr};
|
||||
ComboBox* m_mode{nullptr};
|
||||
wxSpinCtrlDouble* m_width{nullptr};
|
||||
wxSpinCtrlDouble* m_height{nullptr};
|
||||
@@ -617,6 +617,11 @@ private:
|
||||
// the verbs that have no keyboard shortcut to route through.
|
||||
std::map<std::string, std::function<void()>> m_verb_actions;
|
||||
void show_offer_menu(const wxPoint& screen_pos);
|
||||
// Where the offer opens when no mouse press anchors it: the keyboard route, and the automatic
|
||||
// open on entering Sketch. The pointer if it is over the viewport, else the viewport's centre.
|
||||
// A raw wxGetMousePosition() can be sitting on the toolbar, on the card column or on another
|
||||
// monitor, and the menu would map there — detached from the geometry it is about.
|
||||
wxPoint offer_anchor() const;
|
||||
int offer_selection_kind() const; // an OfferSel, as int to keep the header light
|
||||
// Does the SKETCH half of the map apply? A mode question, not a session one: begin_sketch
|
||||
// does not run until the first tool is armed, so between "press Sketch" and "pick a tool"
|
||||
|
||||
@@ -1256,7 +1256,7 @@ void DesignSketchTool::open_primary_autoedit()
|
||||
const double Lc = d.norm();
|
||||
double deg = std::atan2(d.y(), d.x()) * 180.0 / M_PI; if (deg < 0.0) deg += 360.0;
|
||||
m_autoedit_dims.push_back({ m_live_slot_len_label, Lc, [this, fi](double v){ const Feature& g = m_features[fi]; set_slot(fi, v, g.param); }, span(fi), "Length" });
|
||||
m_autoedit_dims.push_back({ m_live_slot_w_label, f.param, [this, fi](double v){ const Feature& g = m_features[fi]; set_slot(fi, (g.c1-g.c0).norm(), std::max(1e-3, v)); }, span(fi), "Width" });
|
||||
m_autoedit_dims.push_back({ m_live_slot_w_label, f.param, [this, fi](double v){ const Feature& g = m_features[fi]; set_slot(fi, (g.c1-g.c0).norm(), std::max(1e-3, v)); }, span(fi), "Radius" });
|
||||
m_autoedit_dims.push_back({ m_live_slot_angle_label, deg, [this, fi](double v){ set_slot_angle(fi, v); }, span(fi), "Angle" });
|
||||
}
|
||||
if (m_live_arc_ei >= 0) { // arc Radius is already a scalar step above; add its sweep angle
|
||||
@@ -1838,7 +1838,7 @@ void DesignSketchTool::open_slot_editor(int fi, int which)
|
||||
double deg = std::atan2(d.y(), d.x()) * 180.0 / M_PI; if (deg < 0.0) deg += 360.0;
|
||||
const double v = (which == 0) ? d.norm() : (which == 1) ? f.param : deg;
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
on_inline_edit(px, v, which == 0 ? "Length" : which == 1 ? "Width" : "Angle",
|
||||
on_inline_edit(px, v, which == 0 ? "Length" : which == 1 ? "Radius" : "Angle",
|
||||
[this, fi, which](double nv) {
|
||||
const Feature& g = m_features[fi];
|
||||
if (which == 0) set_slot(fi, nv, g.param);
|
||||
|
||||
@@ -80,7 +80,7 @@ public:
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane);
|
||||
void set_tool(Mode mode); // switch tool, keep accumulated entities
|
||||
void set_plane(const SketchPlane& plane) { m_plane = plane; } // re-plane a live sketch (Plane dropdown changed mid-session); entities are 2D, re-lifted through the new plane
|
||||
void set_plane(const SketchPlane& plane) { m_plane = plane; } // re-plane a live sketch (a reference plane was clicked mid-session); entities are 2D, re-lifted through the new plane
|
||||
void set_construction(bool c) { m_construction = c; }
|
||||
void set_polygon_sides(int n) { m_polygon_sides = (n < 3 ? 3 : n); }
|
||||
void set_polygon_circumscribed(bool c) { m_polygon_circumscribed = c; }
|
||||
|
||||
Reference in New Issue
Block a user