mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-05 14:51:06 +00:00
Design tab: translatable offer, one vocabulary, reports in the status line
- Offer table: user-facing strings carry the L() marker so xgettext
extracts them; DesignOffer.hpp, DesignSketchTool.cpp and
SketchInlineEditor.cpp are listed in localization/i18n/list.txt.
- Offer: model-mode Constrain sits in the same row as the sketch one;
Interference is wired; Rib shows its R key; a verb that accepts the
selection but is blocked by the document stays greyed with its reason
instead of vanishing from the submenu; one refusal wording per verb.
- Extrude infers Join when the profile touches a solid (new
CadDocument::body_touching_sketch) and on face push/pull; New body in
free space. Revolve/Sweep/Loft/Boolean use the same result words.
- Interference and volume/area reports go to the status line in mm3/mm2
instead of modal dialogs; Delete Body no longer asks (it is undoable).
- New feature names match the card header ("Extrude 3"), translated;
"Coordinate system", "Angle (°)", center/color spelling, translated
face and length readouts, slot hints say width.
- CAD gizmos in Prepare are selectable only with the CAD feature on; the
sketch auto-close setting is stored per design.
- Docs: confirm/cancel rules, enabling the feature and MCP in
design_tab.md; drift-only right-click in interaction-model.md; the
portability note rewritten to describe the integration as it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QK4VgguuCAk2hZLWgcjJb9
This commit is contained in:
+38
-5
@@ -14,10 +14,15 @@ already ships for STEP import.
|
||||
|
||||
## Getting started
|
||||
|
||||
The Design tab is experimental. It exists only in builds with `SLIC3R_CAD` on, and appears
|
||||
after **Preferences → CAD feature (experimental)** is ticked and the app restarted. Switched off,
|
||||
Prepare and every project behave exactly as without it; a project that carries a design keeps
|
||||
its recipe untouched.
|
||||
|
||||
1. Open the **Design** tab.
|
||||
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.
|
||||
3. Draw a closed profile, then press **✓ Confirm** in the floating action bar (or `Enter`).
|
||||
4. With the sketch selected, press `Shift+E` (Extrude).
|
||||
5. Press **Commit to Plate** to hand the solid to Prepare.
|
||||
|
||||
@@ -59,7 +64,8 @@ sketch tools — the app hands you the tools directly.
|
||||
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.
|
||||
into a submenu under the family name. A verb that is about the selection but blocked by the
|
||||
document (no body yet, no second sketch) stays in that submenu, greyed, with its reason.
|
||||
- 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
|
||||
@@ -72,6 +78,22 @@ Redo, Variables, Section view, Origin planes, World axes. They live in the toolb
|
||||
|
||||
---
|
||||
|
||||
## Confirm and cancel
|
||||
|
||||
One rule everywhere, in the feature cards and in the sketch alike:
|
||||
|
||||
- `Enter` does what **✓** does; `Esc` does what **✗** does. Neither is ever the only way.
|
||||
- `Esc` steps back exactly one level — a value field, then the gesture in progress, then the
|
||||
armed tool, then the selection. It never deletes, discards a sketch or rolls back a feature.
|
||||
- A click on empty space clears the selection. It never applies anything: a pending fillet,
|
||||
offset, move or array waits for `Enter` or **✓**.
|
||||
- Right-click on a tool with something pending drops that gesture; with nothing pending it
|
||||
opens the offer, like anywhere else.
|
||||
- Switching to another tool applies a pending operation that is complete and valid, then arms
|
||||
the new tool.
|
||||
- A value typed into a field that the geometry cannot take (a zero-length side, an arc of more
|
||||
than 360°) is refused in place: the field stays open and says why.
|
||||
|
||||
## Keyboard
|
||||
|
||||
Single letters drive sketch tools **while a sketch is open**; Shift+letter drives feature
|
||||
@@ -100,8 +122,10 @@ selected by the mode, not by whether a sketch session is running.
|
||||
| `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 |
|
||||
| `Del` | Delete the selected sketch entities (nothing selected: nothing happens) |
|
||||
| `Enter` | Apply what is pending; otherwise end the chain; otherwise drop the tool |
|
||||
| `Esc` | Undo one level: close the field, drop the gesture, drop the tool, clear the selection |
|
||||
| `Ctrl+Z` / `Ctrl+Y` | Undo / redo the last sketch edit |
|
||||
|
||||
### Feature (when no sketch is open)
|
||||
|
||||
@@ -151,7 +175,9 @@ kept. With no section on, `F` is Place on Face — lay the picked face flat on t
|
||||
## 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
|
||||
Pick the face or plane and press `Shift+S` — the offer opens with the sketch tools. Pressing
|
||||
`Shift+S` with nothing picked also works: the status line asks for the face or plane, and the
|
||||
first tool you arm sketches on it. The toolbar and
|
||||
the offer both carry the sketch tools.
|
||||
|
||||
**Entities:** line, polyline, rectangle (corner / centre / oblique / rounded), circle
|
||||
@@ -290,6 +316,13 @@ edit; the importer warns before you commit to it.
|
||||
inside the 3MF, so reopening the project restores the editable model rather than a frozen
|
||||
mesh.
|
||||
|
||||
**MCP control** lets an external agent drive the tab through the same kernel the GUI uses.
|
||||
Start the app with `ORCA_CAD_MCP=1` (socket `/tmp/orca-cad-mcp.sock`) or
|
||||
`ORCA_CAD_MCP=/path/to.sock`; the CAD feature must be enabled too. Linux and macOS only. A
|
||||
command that would change the document is refused while the Design tab is busy with it — a
|
||||
rebuild, an open feature card or a sketch session — so the agent and the user never edit the
|
||||
same thing at once.
|
||||
|
||||
---
|
||||
|
||||
## View controls
|
||||
|
||||
@@ -20,13 +20,14 @@ design intent that both steps discard. A part modified after slicing should retu
|
||||
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).
|
||||
For the integration case in full: [design_tab_upstream_portability.md](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`.
|
||||
OCCT's CMake: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`. At run time the tab is off until
|
||||
Preferences → CAD feature (experimental) is enabled.
|
||||
|
||||
Measured cost table: [cad_dependency_weight.md](docs/cad_dependency_weight.md).
|
||||
|
||||
@@ -55,7 +56,7 @@ circle-line tangency solver abort and the internal-thread reference) are fixed.
|
||||
|
||||
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
|
||||
[design_tab_upstream_portability.md](design_tab_upstream_portability.md) for the
|
||||
AGPLv3/GPLv3 compatibility argument; this point should be confirmed with upstream explicitly.
|
||||
|
||||
## Not verified
|
||||
|
||||
@@ -1,162 +1,77 @@
|
||||
# Design (CAD) tab — upstream integration brief
|
||||
# Design (CAD) tab — integration surface
|
||||
|
||||
**Question:** can the Design tab (sketch-first parametric CAD: sketch → constrain →
|
||||
extrude/revolve/fillet/hole/thread/shell, multi-body, undo, 3MF persistence) land in
|
||||
mainline OrcaSlicer?
|
||||
What the Design tab adds to OrcaSlicer, what it touches that upstream already has, and the
|
||||
constraints that come with it. The dependency cost is measured separately in
|
||||
[cad_dependency_weight.md](cad_dependency_weight.md); this page does not repeat its numbers.
|
||||
|
||||
**Answer: yes, and the ask is far smaller than previously believed.** OCCT is *already*
|
||||
an OrcaSlicer dependency. We are not asking upstream to adopt a new library; we are
|
||||
asking it to widen one it already builds, at a measured cost of **3.77 MiB on Windows**.
|
||||
## Two gates
|
||||
|
||||
> ### Corrections to the 2026-06-21 assessment
|
||||
> That revision was written before the persistence work landed and got two load-bearing
|
||||
> facts wrong. Both are corrected here from direct measurement of the branch:
|
||||
>
|
||||
> 1. **"The real blocker: OCCT … a dependency mainline OrcaSlicer has never carried."**
|
||||
> **False.** `deps/OCCT/` exists at the merge-base and upstream links it from
|
||||
> `Format/STEP.cpp`, `Format/svg.cpp`, and `Shape/TextShape.cpp`. Our entire
|
||||
> dependency diff is **one line**: `BUILD_MODULE_ModelingAlgorithms=OFF → ON`.
|
||||
> 2. **"vendored SolveSpace solver … LGPL."** **False.** `src/libslic3r/slvs/LICENSE` is
|
||||
> **GPL-3.0**, not LGPL. This is fine (see Licensing) but must not be misstated.
|
||||
>
|
||||
> It also claimed "no changes to Model" — no longer true; 3MF recipe persistence adds one
|
||||
> `std::string` to `Model`.
|
||||
- **Compile time: `SLIC3R_CAD`** (`CMakeLists.txt`, default ON in this tree). OFF builds no
|
||||
CAD code at all and leaves the OCCT deps prefix identical to upstream. ON also turns on
|
||||
OCCT's `ModelingAlgorithms` module (`deps/OCCT/OCCT.cmake`).
|
||||
- **Run time: Preferences → CAD feature (experimental)** (`enable_cad_feature`, default OFF).
|
||||
Off, the Design page and the MCP control socket are not created, the Prepare-toolbar Sketch
|
||||
and Primitive gizmos cannot be selected, and the Design-only preferences are hidden: Prepare
|
||||
behaves as it does without the feature. A project that carries a CAD recipe still loads,
|
||||
keeps the recipe and writes it back unchanged.
|
||||
|
||||
## Measured shape of the change
|
||||
## Dependencies
|
||||
|
||||
Against merge-base `449a4cf9fc` (34 commits ahead):
|
||||
OCCT is already an upstream dependency (STEP import, SVG, text shapes). The Design tab adds no
|
||||
library; it widens the OCCT build by one module, which builds three extra toolkits — TKFillet,
|
||||
TKOffset and TKFeat — of which the first two are linked. The module flag is all-or-nothing, so
|
||||
TKFeat is built although nothing references it.
|
||||
|
||||
| | files | lines |
|
||||
|---|---:|---:|
|
||||
| **New files** | 138 | +61,720 |
|
||||
| **Modified upstream files** | 23 | +457 / −75 |
|
||||
| **Deleted upstream files** | 0 | — |
|
||||
The constraint solver is vendored in `src/libslic3r/slvs/`: a self-contained subset of
|
||||
SolveSpace (`libslvs`), GPL-3.0, no external dependencies. GPLv3 §13 and AGPLv3 §13 permit the
|
||||
combination with OrcaSlicer's AGPL-3.0.
|
||||
|
||||
The 62 kLOC headline is inflated by localization. The feature itself:
|
||||
## Footprint in upstream files
|
||||
|
||||
| area | LOC | files |
|
||||
|---|---:|---:|
|
||||
| kernel (`src/libslic3r/`) | 15,828 | 37 |
|
||||
| GUI (`src/slic3r/`) | 19,544 | 14 |
|
||||
| tests (Catch2) | 2,567 | 6 |
|
||||
| i18n (unrelated; strip from the CAD PR) | 23,438 | 77 |
|
||||
Almost all of the change is new files under `src/libslic3r/CAD/`, `src/slic3r/GUI/CAD/`,
|
||||
`src/libslic3r/slvs/` and `tests/`. The hooks into existing code are small and null-guarded:
|
||||
|
||||
**99.3 % of the diff is new files.** The negotiable surface is 457 added lines across 23
|
||||
files, and nothing upstream is deleted. The largest single hook is `GLCanvas3D.cpp`
|
||||
(+110/−2): an `m_design_sketch_tool` member plus render/mouse/key hooks, **every one
|
||||
already null-guarded** — which is why the compile-time gate below is cheap.
|
||||
- `GLCanvas3D` — a `DesignSketchTool` pointer plus render, mouse and key hooks, each a no-op
|
||||
when no Design canvas owns it.
|
||||
- `MainFrame` — the Design page and the Edit menu routing Undo/Redo to it while it is shown.
|
||||
- `Plater` — the "no geometry" warning distinguishes a recipe-only project.
|
||||
- `Model` — one `std::string cad_recipe`, empty for non-CAD projects.
|
||||
- `bbs_3mf` — reading and writing that string (below).
|
||||
- `Preferences`, `GUI_App` — the two preferences.
|
||||
|
||||
No changes to the slicing pipeline (Print/PrintObject/Layer/GCode), Tab, or the
|
||||
printer-profile/config system.
|
||||
Nothing in the slicing pipeline (Print, PrintObject, Layer, GCode), Tab, or the profile and
|
||||
config system changes.
|
||||
|
||||
## The dependency ask, precisely
|
||||
|
||||
Not "adopt OCCT" — **widen the existing OCCT build**:
|
||||
|
||||
```diff
|
||||
- -DBUILD_MODULE_ModelingAlgorithms=OFF
|
||||
+ -DBUILD_MODULE_ModelingAlgorithms=ON
|
||||
```
|
||||
|
||||
Cost, measured from the shipped Windows artifact (42 OCCT DLLs, 45.43 MiB total):
|
||||
|
||||
| toolkit | size | note |
|
||||
|---|---:|---|
|
||||
| `TKFillet.dll` | 2.02 MiB | only exists with the flag ON |
|
||||
| `TKOffset.dll` | 1.75 MiB | only exists with the flag ON |
|
||||
| **delta** | **3.77 MiB** | Windows only (OCCT is Shared on Win, Static elsewhere) |
|
||||
|
||||
`TKBool` is *not* part of the delta — upstream's `DataExchange` already pulls it in
|
||||
transitively. On macOS/Linux OCCT links statically, so the cost is only the code actually
|
||||
referenced, not a 3.77 MiB floor.
|
||||
|
||||
**Unmeasured, and we should measure before the call:** clean-deps build-time delta with
|
||||
the flag ON vs OFF, and the resulting CI runner-minute cost. Do not guess these at him.
|
||||
|
||||
## Licensing
|
||||
|
||||
- Vendored solver `src/libslic3r/slvs/` — **GPL-3.0**, 9,339 LOC, © Jonathan Westhues,
|
||||
a self-contained subset of SolveSpace (`libslvs`). No external dependencies.
|
||||
- OrcaSlicer — **AGPL-3.0** (`LICENSE.txt`).
|
||||
|
||||
GPLv3 §13 expressly permits combining a GPLv3 work with an AGPLv3 work; AGPLv3 §13 grants
|
||||
the converse. The combined work is distributable under AGPL-3.0 with the solver's GPLv3
|
||||
terms preserved. This is a favourable direction (GPLv3 → into an AGPLv3 project), but it
|
||||
is a point to **confirm explicitly with upstream**, not to assert unilaterally.
|
||||
|
||||
Open question for SoftFever: keep the solver **vendored** (current: pinned, no submodule,
|
||||
no external build) or move it to `deps/` as a fetched external? Vendoring costs us
|
||||
upstream-sync burden; `deps/` costs build complexity.
|
||||
|
||||
## The one irreversible decision: the 3MF format
|
||||
|
||||
Persistence adds an **optional** archive entry and one field:
|
||||
|
||||
```cpp
|
||||
// Model.hpp
|
||||
std::string cad_recipe; // empty for non-CAD projects
|
||||
```
|
||||
## The 3MF entry
|
||||
|
||||
```
|
||||
Metadata/orca_cad.bin // written only when cad_recipe is non-empty
|
||||
Metadata/orca_cad.bin written only when cad_recipe is non-empty
|
||||
Metadata/SnapOrca_cad.bin legacy name: read, never written; loses to orca_cad.bin
|
||||
```
|
||||
|
||||
Readers that do not know the entry ignore it; writers skip it entirely when empty. So
|
||||
existing projects are bit-identical and old readers are unaffected. Good.
|
||||
Readers that do not know the entry ignore it and writers skip it when the recipe is empty, so
|
||||
projects without a design are byte-identical and older readers are unaffected. The reader caps
|
||||
the entry at 1 GiB.
|
||||
|
||||
**But the moment upstream ships this, it owns forward-compatibility forever.** Three
|
||||
things should be settled *before* the first release, because none can be changed after:
|
||||
The entry is a cereal `PortableBinaryArchive`, portable across endianness and word size. Its
|
||||
layout is versioned:
|
||||
|
||||
1. **Name.** Renamed to `Metadata/orca_cad.bin`.
|
||||
2. **Encoding.** The recipe is an opaque **cereal `PortableBinaryArchive`** blob whose
|
||||
layout is the field order of `CadFeature::serialize`. Portable across endianness and
|
||||
word size — *not* across a field reorder. Append-only is currently a convention held by
|
||||
discipline, not by any check.
|
||||
3. **Embedded BRep.** `Import` features embed OCCT's ASCII BRep for the imported solid,
|
||||
which couples saved project files to an OCCT BRep revision. Alternative: re-import from
|
||||
the source STEP and store only a reference. Worth deciding deliberately.
|
||||
- **v5 and later** frame every feature separately, so fields are appended to
|
||||
`CadFeature::serialize` without a version bump and an older reader skips what it does not
|
||||
know. Optional document-level data (variables, body names, origin, colours, the
|
||||
auto-close setting) follows in trailing blocks that a reader stops at cleanly when absent.
|
||||
- **v4** is the flat layout, read by the frozen `CadFeature::load_flat_v4`, which must never
|
||||
change.
|
||||
|
||||
**Concrete gap we should close before the call.** `test_caddocument.cpp` covers the
|
||||
in-memory round-trip and correctly refuses a version-999 blob — but there is **no
|
||||
checked-in v1 fixture on disk**. A reordered field in `CadFeature::serialize` would pass
|
||||
the entire suite while silently breaking every previously-saved project. Ship a golden
|
||||
`.bin` fixture generated today plus a test that loads it; that is the only thing that will
|
||||
hold the format still once real users have files.
|
||||
Checked-in fixtures `tests/data/cad_recipe_v3.bin`, `cad_recipe_v4.bin` and
|
||||
`cad_recipe_v5.bin` hold the format still: `test_caddocument.cpp` loads them and checks what
|
||||
they contain, so a reordered field fails the suite instead of breaking saved projects.
|
||||
|
||||
## Proposed PR decomposition
|
||||
`Import` features embed OCCT's ASCII BRep of the imported solid, which ties a saved project to
|
||||
a BRep revision OCCT can read.
|
||||
|
||||
35 kLOC in one PR is not reviewable. Behind the flag, slices 1–4 are behaviour-neutral for
|
||||
existing users:
|
||||
## Undo
|
||||
|
||||
1. **Build gate + OCCT flag + Windows packaging guard.** `-DSLIC3R_CAD=ON/OFF`, default
|
||||
**OFF**. Flips `ModelingAlgorithms=ON`. Includes the guard that asserts every linked
|
||||
OCCT toolkit has a shipped DLL (already on both forks: `546cef5f42`). ← *this is what
|
||||
makes SoftFever's "parallel build" a one-line CI matrix entry.*
|
||||
2. **Vendored `slvs` solver** + its Catch2 tests. No GUI, no OCCT.
|
||||
3. **CAD kernel** (`CadDocument`, `SketchEngine`, `GeometryEngine`, `Sketch*`) + kernel
|
||||
tests. Headless, no GUI.
|
||||
4. **3MF recipe persistence** + golden-fixture regression test.
|
||||
5. **GUI Design tab** (`DesignPanel`, `DesignCanvas`, `DesignSketchTool`, `GLGizmoSketch`)
|
||||
+ the 23 upstream hooks.
|
||||
|
||||
## Agenda for the call
|
||||
|
||||
Questions only SoftFever can answer:
|
||||
|
||||
- Does OrcaSlicer *want* to be a CAD-integrated slicer? (Strategic; everything else is mechanical.)
|
||||
- Default of `SLIC3R_CAD` at merge time, and when it flips ON.
|
||||
- Vendored solver vs `deps/` external; and confirmation of the GPLv3/AGPLv3 combination.
|
||||
- Project-file format: neutral name, encoding, embedded-BRep policy, and who owns v1 forward-compat.
|
||||
- Undo/redo: the Design tab has its own stack; integrate with Orca's snapshot system or keep separate?
|
||||
- Does he want the i18n work (Romanian, +23 kLOC) as a wholly separate PR? (Yes, almost certainly.)
|
||||
|
||||
## Verdict
|
||||
|
||||
Portability **high**. The prior "does upstream want OCCT" framing was wrong — OCCT is
|
||||
already there. What remains is a 3.77 MiB dependency widening, a compile-time gate that
|
||||
the existing null-guards make cheap, and one file-format decision that must be made before
|
||||
the first release rather than after.
|
||||
|
||||
---
|
||||
*Revised 2026-07-10 from direct measurement of `cad-mainline` @ `546cef5f42` vs upstream
|
||||
merge-base `449a4cf9fc`. Supersedes the 2026-06-21 read-only assessment.*
|
||||
The Design tab keeps its own history (`CadDocument` checkpoints), separate from the Plater's
|
||||
snapshot stack. The Edit menu's Undo/Redo drive whichever of the two belongs to the page on
|
||||
screen.
|
||||
|
||||
@@ -72,10 +72,11 @@ which is what makes the destructive path unrepresentable rather than merely unli
|
||||
own handlers so it can consume the event before them:
|
||||
|
||||
```cpp
|
||||
RIGHT_DOWN: remember the press position and the clock, then Skip() // the canvas still seeds the orbit
|
||||
RIGHT_DOWN: remember the press position, clear `travelled`, then Skip() // the canvas still seeds the orbit
|
||||
MOTION: travelled |= drift from the press > 3 px // an orbit that came back still orbited
|
||||
|
||||
RIGHT_UP: terminated = sketch_tool.take_right_consumed(); // read-and-clear, always
|
||||
is_click = drift <= 3 px && dt <= 200 ms; // both budgets, or it was navigation
|
||||
is_click = !travelled && drift <= 3 px; // otherwise it was navigation
|
||||
if (callback && !terminated && !inline_busy && is_click) {
|
||||
select_at_screen(press.x, press.y); // raycast at the PRESS, not the release
|
||||
on_context_menu(ClientToScreen(press));
|
||||
@@ -84,8 +85,9 @@ RIGHT_UP: terminated = sketch_tool.take_right_consumed(); // read-and
|
||||
Skip(); // orbit / pan / the handlers underneath
|
||||
```
|
||||
|
||||
Two independent budgets because the two failure modes are independent: drift alone still popped a
|
||||
menu at the end of a slow, careful orbit. `take_right_consumed()` is how a right-click that
|
||||
Only distance decides, never time (charter §6.2: no timing-dependent gestures): a slow, deliberate
|
||||
right-click is still a click. The drift is tracked over the whole press, not just at release, so a
|
||||
careful orbit that ends where it started does not pop a menu. `take_right_consumed()` is how a right-click that
|
||||
already meant something to the armed sketch tool (terminate a chain, drop an edit-op) declines to
|
||||
also mean "open a menu".
|
||||
|
||||
@@ -107,7 +109,7 @@ also mean "open a menu".
|
||||
| **Transient — popup menu** | run the entry | — | close the menu | run the highlighted entry |
|
||||
| **any** | — | — | *never* deletes, discards or rolls back | — |
|
||||
|
||||
Right-hold-and-drag is not in the table on purpose: past 3 px or 200 ms it is navigation, and
|
||||
Right-hold-and-drag is not in the table on purpose: once it has travelled past 3 px it is navigation, and
|
||||
navigation does not transition the state machine.
|
||||
|
||||
## 4. Visual scaffolding
|
||||
|
||||
@@ -42,6 +42,12 @@ def cstr(s):
|
||||
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
def tstr(s):
|
||||
# User-facing text: wrapped in the no-op L() marker so xgettext puts it in the catalogue.
|
||||
# The DesignPanel translates it at use; without the marker it compiles and never translates.
|
||||
return "nullptr" if s is None else "L(" + cstr(s) + ")"
|
||||
|
||||
|
||||
def main():
|
||||
A = json.load(open(ATLAS, encoding="utf-8"))
|
||||
sels = [s["id"] for s in A["selections"]]
|
||||
@@ -61,6 +67,10 @@ def main():
|
||||
"",
|
||||
"#include <cstdint>",
|
||||
"",
|
||||
"#ifndef L",
|
||||
"#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp",
|
||||
"#endif",
|
||||
"",
|
||||
"namespace Slic3r { namespace GUI {",
|
||||
"",
|
||||
"// What the viewport has selected. Ordered as in tool_atlas.json; the bitmask in",
|
||||
@@ -83,7 +93,7 @@ def main():
|
||||
"// nullptr -> kernel support exists, no GUI path yet (row shows disabled)",
|
||||
"struct OfferVerb {",
|
||||
" const char* id;",
|
||||
" const char* name; // drawing-office word (L10); translated at use with wxGetTranslation",
|
||||
" const char* name; // drawing-office word (L10); marked L(); translated at use",
|
||||
" int row; // 0..7, the ratified index — NEVER reorder",
|
||||
" const char* key; // shortcut shown in the row, or nullptr",
|
||||
" const char* action;",
|
||||
@@ -106,7 +116,7 @@ def main():
|
||||
"static const char* const kOfferRowNames[] = {",
|
||||
]
|
||||
for s in A["slots"]:
|
||||
lines.append(f' "{s["label"]}",')
|
||||
lines.append(f' {tstr(s["label"])},')
|
||||
lines += [
|
||||
"};",
|
||||
f"static const int kOfferRowCount = {len(slots)};",
|
||||
@@ -131,12 +141,12 @@ def main():
|
||||
n = v.get("needs") or {}
|
||||
lines.append(
|
||||
" {%s, %s, %d, %s, %s, %s, 0x%08xu, %d, %d, %s, %s, %s, %s, %s}," % (
|
||||
cstr(v["id"]), cstr(v["name"]), slots.index(v["slot"]),
|
||||
cstr(v.get("key")), cstr(v.get("action")), cstr(v.get("refusal")),
|
||||
cstr(v["id"]), tstr(v["name"]), slots.index(v["slot"]),
|
||||
cstr(v.get("key")), cstr(v.get("action")), tstr(v.get("refusal")),
|
||||
mask, n.get("bodies", 0), n.get("sketches", 0),
|
||||
"true" if n.get("sheet") else "false",
|
||||
"true" if v.get("mode") == "sketch" else "false",
|
||||
cstr(v.get("family")), cstr(v.get("icon")), cstr(v.get("hint"))))
|
||||
tstr(v.get("family")), cstr(v.get("icon")), tstr(v.get("hint"))))
|
||||
lines += [
|
||||
"};",
|
||||
f"static const int kOfferVerbCount = {len(A['verbs'])};",
|
||||
|
||||
+38
-36
@@ -326,7 +326,7 @@
|
||||
"id": "rib",
|
||||
"name": "Rib",
|
||||
"slot": "add",
|
||||
"key": null,
|
||||
"key": "R",
|
||||
"feature": "Rib",
|
||||
"mcp": "rib",
|
||||
"accepts": [
|
||||
@@ -343,7 +343,7 @@
|
||||
},
|
||||
{
|
||||
"id": "boolean",
|
||||
"name": "Union",
|
||||
"name": "Join",
|
||||
"slot": "add",
|
||||
"key": "Shift+B",
|
||||
"feature": "Boolean",
|
||||
@@ -354,7 +354,7 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "Boolean needs two bodies — create or import a second solid",
|
||||
"refusal": "Boolean needs two bodies — add or import a second one",
|
||||
"gui": true,
|
||||
"action": "btn:bool#0",
|
||||
"icon": "design_boolean",
|
||||
@@ -373,7 +373,7 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "Boolean needs two bodies — create or import a second solid",
|
||||
"refusal": "Boolean needs two bodies — add or import a second one",
|
||||
"gui": true,
|
||||
"action": "btn:bool#1",
|
||||
"mode": null,
|
||||
@@ -393,7 +393,7 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "Boolean needs two bodies — create or import a second solid",
|
||||
"refusal": "Boolean needs two bodies — add or import a second one",
|
||||
"gui": true,
|
||||
"action": "btn:bool#2",
|
||||
"mode": null,
|
||||
@@ -483,7 +483,7 @@
|
||||
"needs": {
|
||||
"sheet": true
|
||||
},
|
||||
"refusal": "target is not a sheet body",
|
||||
"refusal": "Thicken Surface needs a surface body — make one with a Surface tool first",
|
||||
"gui": true,
|
||||
"action": "fly:surface#5",
|
||||
"icon": "design_thicken",
|
||||
@@ -507,7 +507,7 @@
|
||||
"gui": true,
|
||||
"action": "key:S+H",
|
||||
"icon": "design_hole",
|
||||
"hint": "Drill a hole, centred on a picked face or placed on a plane"
|
||||
"hint": "Drill a hole, centerd on a picked face or placed on a plane"
|
||||
},
|
||||
{
|
||||
"id": "thread",
|
||||
@@ -543,7 +543,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Shell needs a solid body",
|
||||
"refusal": "Shell needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "key:S+K",
|
||||
"icon": "design_shell",
|
||||
@@ -569,7 +569,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Create a solid body to cut first",
|
||||
"refusal": "Cut needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "key:S+X",
|
||||
"icon": "design_cut",
|
||||
@@ -594,7 +594,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Split needs a solid body",
|
||||
"refusal": "Split needs a solid body — add or import one first",
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"icon": null,
|
||||
@@ -677,7 +677,7 @@
|
||||
"needs": {
|
||||
"sheet": true
|
||||
},
|
||||
"refusal": "target is not a sheet body",
|
||||
"refusal": "Surface Offset needs a surface body — make one with a Surface tool first",
|
||||
"gui": true,
|
||||
"action": "fly:surface#4",
|
||||
"icon": "design_offset",
|
||||
@@ -699,7 +699,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Create a solid body to pattern first",
|
||||
"refusal": "Pattern needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "btn:pat#0",
|
||||
"icon": "design_array",
|
||||
@@ -721,7 +721,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Create a solid body to pattern first",
|
||||
"refusal": "Pattern needs a solid body — add or import one first",
|
||||
"gui": true,
|
||||
"action": "btn:pat#1",
|
||||
"mode": null,
|
||||
@@ -768,7 +768,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Pattern on curve needs a body and a curve",
|
||||
"refusal": "Pattern on Curve needs a body and a curve",
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"icon": null,
|
||||
@@ -816,11 +816,11 @@
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": "A mate needs two coordinate systems",
|
||||
"refusal": "Mate needs two coordinate systems — create them first",
|
||||
"gui": true,
|
||||
"action": "fly:placement#2",
|
||||
"icon": "design_c_coincident",
|
||||
"hint": "Assembly: align two CoordSys features (fastened, planar, revolute, slider, cylindrical)"
|
||||
"hint": "Assembly: align two coordinate systems (fastened, planar, revolute, slider, cylindrical)"
|
||||
},
|
||||
{
|
||||
"id": "align",
|
||||
@@ -835,7 +835,7 @@
|
||||
"needs": {
|
||||
"bodies": 1
|
||||
},
|
||||
"refusal": "Align needs a body",
|
||||
"refusal": "Align needs a body — add or import one first",
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"icon": null,
|
||||
@@ -885,7 +885,7 @@
|
||||
},
|
||||
{
|
||||
"id": "coordsys_v",
|
||||
"name": "Coord Sys",
|
||||
"name": "Coordinate system",
|
||||
"slot": "reference",
|
||||
"key": "Shift+C",
|
||||
"feature": "CoordSys",
|
||||
@@ -972,7 +972,7 @@
|
||||
},
|
||||
{
|
||||
"id": "mass_props",
|
||||
"name": "Mass",
|
||||
"name": "Volume and area",
|
||||
"slot": "reference",
|
||||
"key": null,
|
||||
"feature": null,
|
||||
@@ -1003,14 +1003,15 @@
|
||||
"feature": null,
|
||||
"mcp": "check_interference",
|
||||
"accepts": [
|
||||
"body_solid",
|
||||
"bodies_2"
|
||||
],
|
||||
"needs": {
|
||||
"bodies": 2
|
||||
},
|
||||
"refusal": null,
|
||||
"gui": false,
|
||||
"action": null,
|
||||
"refusal": "Interference needs at least two bodies",
|
||||
"gui": true,
|
||||
"action": "btn:interference",
|
||||
"icon": null,
|
||||
"hint": "Check whether two bodies overlap — reports, changes nothing"
|
||||
},
|
||||
@@ -1082,7 +1083,7 @@
|
||||
},
|
||||
{
|
||||
"id": "colour",
|
||||
"name": "Colour",
|
||||
"name": "Color",
|
||||
"slot": "modify",
|
||||
"key": null,
|
||||
"feature": null,
|
||||
@@ -1104,7 +1105,7 @@
|
||||
"gui": true,
|
||||
"action": "btn:colour",
|
||||
"icon": "color_palette",
|
||||
"hint": "Set the selected body's display colour"
|
||||
"hint": "Set the selected body's display color"
|
||||
},
|
||||
{
|
||||
"id": "delete",
|
||||
@@ -1227,7 +1228,7 @@
|
||||
},
|
||||
{
|
||||
"id": "sk_rect_center",
|
||||
"name": "Centre rectangle",
|
||||
"name": "Center rectangle",
|
||||
"slot": "create",
|
||||
"key": null,
|
||||
"feature": "Sketch",
|
||||
@@ -1296,7 +1297,7 @@
|
||||
},
|
||||
{
|
||||
"id": "sk_circle",
|
||||
"name": "Centre circle",
|
||||
"name": "Center circle",
|
||||
"slot": "create",
|
||||
"key": "C",
|
||||
"feature": "Sketch",
|
||||
@@ -1411,7 +1412,7 @@
|
||||
},
|
||||
{
|
||||
"id": "sk_arc_center",
|
||||
"name": "Centre-point arc",
|
||||
"name": "Center-point arc",
|
||||
"slot": "create",
|
||||
"key": null,
|
||||
"feature": "Sketch",
|
||||
@@ -1453,7 +1454,7 @@
|
||||
"action": "key:S",
|
||||
"family": "Slot",
|
||||
"icon": "design_slot",
|
||||
"hint": "Slot — two centerline ends, then end radius"
|
||||
"hint": "Slot — two centerline ends, then the width"
|
||||
},
|
||||
{
|
||||
"id": "sk_slot_arc",
|
||||
@@ -1567,7 +1568,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Triangle — click centre, then a vertex"
|
||||
"hint": "Triangle — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_4",
|
||||
@@ -1590,7 +1591,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Square — click centre, then a vertex"
|
||||
"hint": "Square — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_5",
|
||||
@@ -1613,7 +1614,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Pentagon — click centre, then a vertex"
|
||||
"hint": "Pentagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_polygon",
|
||||
@@ -1636,7 +1637,7 @@
|
||||
"action": "btn:poly#6",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Hexagon — click centre, then a vertex"
|
||||
"hint": "Hexagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_8",
|
||||
@@ -1659,7 +1660,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Octagon — click centre, then a vertex"
|
||||
"hint": "Octagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_12",
|
||||
@@ -1682,7 +1683,7 @@
|
||||
"mode": "sketch",
|
||||
"family": "Polygon",
|
||||
"icon": "design_polygon",
|
||||
"hint": "Dodecagon — click centre, then a vertex"
|
||||
"hint": "Dodecagon — click center, then a vertex"
|
||||
},
|
||||
{
|
||||
"id": "sk_poly_inscribed",
|
||||
@@ -2048,7 +2049,7 @@
|
||||
{
|
||||
"id": "constrain",
|
||||
"name": "Constrain sketch",
|
||||
"slot": "modify",
|
||||
"slot": "reference",
|
||||
"key": null,
|
||||
"feature": "Sketch",
|
||||
"mcp": null,
|
||||
@@ -2067,7 +2068,8 @@
|
||||
"note": [
|
||||
"Same verb, model-mode vocabulary: offered when a SKETCH is selected (bit 14, SkLoop), the",
|
||||
"state a user is in right after finishing one. Without this row the only way in was the",
|
||||
"toolbar icon, and constraints read as absent — see the Onshape-comparison report."
|
||||
"toolbar icon, and constraints read as absent — see the Onshape-comparison report.",
|
||||
"It sits in the Reference row, the address the sketch-mode Constrain has: one verb, one row."
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -298,7 +298,9 @@ src/slic3r/GUI/AMSDryControl.cpp
|
||||
src/slic3r/GUI/AMSDryControl.hpp
|
||||
src/libslic3r/PresetBundle.cpp
|
||||
src/slic3r/GUI/CAD/DesignPanel.cpp
|
||||
src/slic3r/GUI/CAD/DesignSketchTool.cpp
|
||||
src/slic3r/GUI/CAD/SketchInlineEditor.cpp
|
||||
src/slic3r/GUI/CAD/DesignOffer.hpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoPrimitive.cpp
|
||||
src/slic3r/GUI/Gizmos/GLGizmoSketch.cpp
|
||||
src/slic3r/GUI/KeyChord.cpp
|
||||
|
||||
@@ -95,7 +95,8 @@ def load_table():
|
||||
f.append(cur.strip())
|
||||
if len(f) < 12:
|
||||
continue
|
||||
lit = lambda s: None if s == "nullptr" else s.strip('"')
|
||||
# User-facing fields are wrapped in the L("...") gettext marker; strip it.
|
||||
lit = lambda s: None if s == "nullptr" else re.sub(r'^L\((.*)\)$', r'\1', s).strip('"')
|
||||
out.append({"id": lit(f[0]), "name": lit(f[1]), "row": int(f[2]), "key": lit(f[3]),
|
||||
"action": lit(f[4]), "accepts": int(f[6].rstrip("u"), 0),
|
||||
"need_bodies": int(f[7]), "need_sketches": int(f[8]),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <Standard_Failure.hxx>
|
||||
#include <BRepBuilderAPI_MakeWire.hxx>
|
||||
#include <BRepExtrema_DistShapeShape.hxx>
|
||||
#include <BRepBuilderAPI_MakeEdge.hxx>
|
||||
#include <BRepBuilderAPI_MakeFace.hxx>
|
||||
#include <BRepBuilderAPI_MakePolygon.hxx>
|
||||
@@ -1975,6 +1976,7 @@ void CadDocument::clear()
|
||||
display_tri_face.clear();
|
||||
display_tri_body.clear();
|
||||
origin_from_recipe = false;
|
||||
auto_close_loops = true;
|
||||
// Variables are document state like the features: left behind, the previous project's
|
||||
// variables were written into the next project's recipe.
|
||||
variables.clear();
|
||||
@@ -2456,6 +2458,24 @@ TopoDS_Wire CadDocument::build_sketch_wire(const CadFeature& sketch, bool closed
|
||||
return prof.to_occt_wire(sketch.plane);
|
||||
}
|
||||
|
||||
int CadDocument::body_touching_sketch(int sketch_ref) const
|
||||
{
|
||||
if (sketch_ref < 0 || sketch_ref >= int(features.size())
|
||||
|| features[sketch_ref].type != CadFeatureType::Sketch)
|
||||
return -1;
|
||||
TopoDS_Face face;
|
||||
try { face = build_sketch_face(features[sketch_ref]); } catch (const std::exception&) { return -1; }
|
||||
if (face.IsNull()) return -1;
|
||||
for (int i = int(bodies.size()) - 1; i >= 0; --i) { // newest first: the likeliest target
|
||||
const TopoDS_Shape& b = bodies[i].shape;
|
||||
if (b.IsNull() || is_sheet_shape(b)) continue;
|
||||
BRepExtrema_DistShapeShape d(face, b);
|
||||
if (d.IsDone() && d.Value() <= 1e-4)
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
TopoDS_Face CadDocument::build_sketch_face(const CadFeature& sketch) const
|
||||
{
|
||||
if (!sketch.entities.empty()) {
|
||||
@@ -3932,6 +3952,7 @@ void CadDocument::route_feature(std::vector<CadBody>& bodies, const CadFeature&
|
||||
bool CadDocument::recompute()
|
||||
{
|
||||
error.clear();
|
||||
set_sketch_auto_close(auto_close_loops); // this document's weld rule, not the last one's
|
||||
detect_mate_conflicts();
|
||||
std::vector<CadBody> built;
|
||||
// Did any feature in this document even ASK for a solid? A document made only of sketches
|
||||
@@ -4210,7 +4231,7 @@ std::string CadDocument::serialize_recipe() const
|
||||
std::ostringstream xos;
|
||||
{
|
||||
cereal::BinaryOutputArchive xa(xos);
|
||||
xa(modeling_origin.x(), modeling_origin.y(), modeling_origin.z(), colours);
|
||||
xa(modeling_origin.x(), modeling_origin.y(), modeling_origin.z(), colours, auto_close_loops);
|
||||
}
|
||||
std::string xb = xos.str();
|
||||
uint32_t xlen = static_cast<uint32_t>(xb.size());
|
||||
@@ -4309,9 +4330,11 @@ bool CadDocument::deserialize_recipe(const std::string& blob)
|
||||
std::istringstream xs(xbuf);
|
||||
cereal::BinaryInputArchive xa(xs);
|
||||
double ox = 0, oy = 0, oz = 0;
|
||||
xa(ox, oy, oz, colours);
|
||||
bool weld = true;
|
||||
xa(ox, oy, oz, colours, weld);
|
||||
modeling_origin = Vec3d(ox, oy, oz);
|
||||
origin_from_recipe = true;
|
||||
auto_close_loops = weld;
|
||||
} catch (...) {
|
||||
colours.clear(); // older project: the caller's origin stands
|
||||
}
|
||||
|
||||
@@ -539,6 +539,11 @@ public:
|
||||
// printer must keep the origin it was made with or its datums move and its sketches do not.
|
||||
Vec3d modeling_origin{Vec3d::Zero()};
|
||||
bool origin_from_recipe{false}; // modeling_origin came from the loaded project
|
||||
// Weld sketch endpoints within kSketchJoinTol (the "Auto-close sketch loops" preference, taken
|
||||
// when the document is started). A property of the DOCUMENT, saved with it: as a machine-wide
|
||||
// preference it made one project rebuild into a closed solid on one computer and an open
|
||||
// loop on another. recompute() pushes it into the kernel.
|
||||
bool auto_close_loops{true};
|
||||
|
||||
// Tessellation quality, matched to Orca's OWN STEP importer (Format/STEP.hpp defaults:
|
||||
// linear 0.003, angular 0.5 rad) so a body modelled here reaches the screen at the same
|
||||
@@ -799,6 +804,9 @@ public:
|
||||
// tessellate the result into out_mesh, WITHOUT modifying features/body/
|
||||
// display_mesh. Returns false (with err set) if the candidate is invalid.
|
||||
// Used by the Design tab to show a translucent ghost before Confirm.
|
||||
// The solid body the closed profile of sketch `sketch_ref` lies on or touches, or -1. Drives
|
||||
// the Extrude default: a profile drawn on a body joins it, one in free space is a new body.
|
||||
int body_touching_sketch(int sketch_ref) const;
|
||||
bool preview(const CadFeature& candidate, TriangleMesh& out_mesh, std::string& err) const;
|
||||
// Same, but also returns the per-body meshes (in `bodies` order; the candidate may append
|
||||
// one), so the GUI can apply its display-only per-body Move transforms to the ghost and keep
|
||||
|
||||
@@ -47,18 +47,21 @@
|
||||
#include <TopoDS_Wire.hxx>
|
||||
#include <GeomAPI_IntCS.hxx>
|
||||
#include <map>
|
||||
#include <atomic>
|
||||
#include <tuple>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
// Single source of truth for the weld tolerance the viewport and the kernel share.
|
||||
// Defaults ON so headless/kernel-only callers keep welding; the GUI pushes the
|
||||
// "auto_close_sketch_loops" preference in via set_sketch_auto_close().
|
||||
static bool s_auto_close = true;
|
||||
// Single source of truth for the weld tolerance the viewport and the kernel share. Defaults ON
|
||||
// so headless/kernel-only callers keep welding. It is the DOCUMENT's setting
|
||||
// (CadDocument::auto_close_loops, saved with the recipe), pushed in by CadDocument::recompute:
|
||||
// the same project must rebuild into the same solid on every machine. Atomic because the GUI
|
||||
// rebuilds on a worker thread while the viewport reads it.
|
||||
static std::atomic<bool> s_auto_close{true};
|
||||
|
||||
double sketch_join_tol() { return s_auto_close ? kSketchJoinTol : 0.0; }
|
||||
void set_sketch_auto_close(bool on) { s_auto_close = on; }
|
||||
double sketch_join_tol() { return s_auto_close.load() ? kSketchJoinTol : 0.0; }
|
||||
void set_sketch_auto_close(bool on) { s_auto_close.store(on); }
|
||||
|
||||
// ---- SketchPlane ----
|
||||
|
||||
|
||||
@@ -90,9 +90,9 @@ inline constexpr double kSketchJoinTol = 1e-3; // mm
|
||||
// Effective sketch joint tolerance. ONE value for the viewport (region_loops /
|
||||
// loop_report / connected_loop) and the kernel (entities_to_wires): if these ever
|
||||
// disagree again, the viewport shades a region closed that the kernel refuses to
|
||||
// build, which is how a sketch got extruded into the wrong solid. The GUI pushes
|
||||
// the "auto_close_sketch_loops" preference in via set_sketch_auto_close(); the
|
||||
// kernel defaults to ON so headless/kernel-only callers keep welding.
|
||||
// build, which is how a sketch got extruded into the wrong solid. The document's
|
||||
// own setting (CadDocument::auto_close_loops) is pushed in via set_sketch_auto_close();
|
||||
// the kernel defaults to ON so headless/kernel-only callers keep welding.
|
||||
double sketch_join_tol();
|
||||
void set_sketch_auto_close(bool on);
|
||||
|
||||
|
||||
@@ -1961,7 +1961,14 @@ void PlateData::parse_filament_info(GCodeProcessorResult *result)
|
||||
else if (boost::algorithm::iequals(name, ORCA_CAD_RECIPE_FILE)
|
||||
|| boost::algorithm::iequals(name, LEGACY_CAD_RECIPE_FILE)) {
|
||||
// Restore the editable CAD recipe (optional; absent in non-CAD projects).
|
||||
if (stat.m_uncomp_size > 0) {
|
||||
// The current name wins over the legacy one whichever the archive lists
|
||||
// first, and the size the archive claims is capped before it is allocated.
|
||||
constexpr mz_uint64 kMaxCadRecipe = mz_uint64(1) << 30; // 1 GiB
|
||||
const bool legacy = boost::algorithm::iequals(name, LEGACY_CAD_RECIPE_FILE);
|
||||
if (stat.m_uncomp_size > kMaxCadRecipe) {
|
||||
BOOST_LOG_TRIVIAL(error) << "3MF: CAD recipe of " << stat.m_uncomp_size
|
||||
<< " bytes exceeds the limit; not loaded";
|
||||
} else if (stat.m_uncomp_size > 0 && !(legacy && !model.cad_recipe.empty())) {
|
||||
std::string buf((size_t)stat.m_uncomp_size, '\0');
|
||||
if (mz_zip_reader_extract_to_mem(&archive, stat.m_file_index, buf.data(), buf.size(), 0))
|
||||
model.cad_recipe = std::move(buf);
|
||||
|
||||
+106
-101
@@ -10,6 +10,10 @@
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#ifndef L
|
||||
#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp
|
||||
#endif
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// What the viewport has selected. Ordered as in tool_atlas.json; the bitmask in
|
||||
@@ -48,7 +52,7 @@ inline uint32_t offer_bit(OfferSel s) { return 1u << int(s); }
|
||||
// nullptr -> kernel support exists, no GUI path yet (row shows disabled)
|
||||
struct OfferVerb {
|
||||
const char* id;
|
||||
const char* name; // drawing-office word (L10); translated at use with wxGetTranslation
|
||||
const char* name; // drawing-office word (L10); marked L(); translated at use
|
||||
int row; // 0..7, the ratified index — NEVER reorder
|
||||
const char* key; // shortcut shown in the row, or nullptr
|
||||
const char* action;
|
||||
@@ -69,118 +73,119 @@ struct OfferVerb {
|
||||
|
||||
// Row labels, in ratified order.
|
||||
static const char* const kOfferRowNames[] = {
|
||||
"Create",
|
||||
"Add material",
|
||||
"Remove",
|
||||
"Fillet / chamfer / draft",
|
||||
"Repeat",
|
||||
"Transform",
|
||||
"Reference",
|
||||
"Modify",
|
||||
L("Create"),
|
||||
L("Add material"),
|
||||
L("Remove"),
|
||||
L("Fillet / chamfer / draft"),
|
||||
L("Repeat"),
|
||||
L("Transform"),
|
||||
L("Reference"),
|
||||
L("Modify"),
|
||||
};
|
||||
static const int kOfferRowCount = 8;
|
||||
|
||||
static const OfferVerb kOfferVerbs[] = {
|
||||
{"sketch", "Sketch", 0, "Shift+S", "key:S+S", "Click a face or a reference plane in the viewport, then a sketch tool", 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", "Click a face or a reference plane, then pick a drawing tool"},
|
||||
{"extrude", "Extrude", 1, "Shift+E", "key:S+E", "Create a sketch, or pick a solid face, first", 0x00004002u, 0, 0, false, false, nullptr, "design_extrude", "Extrude a sketch profile, or push/pull a picked face"},
|
||||
{"revolve", "Revolve", 1, "Shift+R", "key:S+R", "Create a sketch profile to revolve first", 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", "Revolve a profile about an axis"},
|
||||
{"sweep", "Sweep", 1, "Shift+W", "key:S+W", "Create a profile sketch to sweep first", 0x00004000u, 0, 2, false, false, nullptr, "design_sweep", "Sweep a profile along a path"},
|
||||
{"loft", "Loft", 1, "Shift+L", "key:S+L", "Create at least two profile sketches to loft", 0x00004000u, 0, 2, false, false, nullptr, "design_loft", "Loft (skin) between two or more profiles"},
|
||||
{"thicken", "Thicken", 1, nullptr, "fly:material#4", "Thicken needs a solid body — add or import one first", 0x0000000au, 1, 0, false, false, nullptr, "design_thicken", "Offset a solid face into a thin plate (new body)"},
|
||||
{"rib", "Rib", 1, nullptr, "fly:material#5", "Rib needs a solid body — add or import one first", 0x00010000u, 1, 0, false, false, nullptr, "design_rib", "Grow a thin wall from an open sketch line, fused to a body"},
|
||||
{"boolean", "Union", 1, "Shift+B", "btn:bool#0", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", "Fuse the tool body into the target — one solid, no seam"},
|
||||
{"bool_subtract", "Subtract", 1, nullptr, "btn:bool#1", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", "Cut the tool body out of the target"},
|
||||
{"bool_intersect", "Intersect", 1, nullptr, "btn:bool#2", "Boolean needs two bodies — create or import a second solid", 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", "Keep only where the two bodies overlap"},
|
||||
{"surf_extrude", "Surface Extrude", 1, "Shift+G", "key:S+G", "Create a sketch first", 0x00004000u, 0, 0, false, false, nullptr, "design_extrude", "Extrude a sketch into a sheet body (no end caps)"},
|
||||
{"surf_revolve", "Surface Revolve", 1, nullptr, "fly:surface#1", "Create a sketch profile to revolve first", 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", "Revolve a sketch profile into a sheet body"},
|
||||
{"surf_loft", "Surface Loft", 1, nullptr, "fly:surface#2", "Create at least two profile sketches to loft", 0x00004000u, 0, 2, false, false, nullptr, "design_loft", "Loft (skin) between 2+ profiles, open (no end caps)"},
|
||||
{"surf_fill", "Surface Fill", 1, nullptr, "fly:surface#3", "Create a closed sketch first", 0x00004000u, 0, 0, false, false, nullptr, "design_surface", "Fill a sketch boundary with a smooth face"},
|
||||
{"thicken_surf", "Thicken Surface", 1, nullptr, "fly:surface#5", "target is not a sheet body", 0x00000100u, 0, 0, true, false, nullptr, "design_thicken", "Thicken a sheet body into a solid"},
|
||||
{"hole", "Hole", 2, "Shift+H", "key:S+H", "Pick a face or a plane to drill into", 0x00000402u, 1, 0, false, false, nullptr, "design_hole", "Drill a hole, centred on a picked face or placed on a plane"},
|
||||
{"thread", "Thread", 2, "Shift+T", "key:S+T", "Pick a cylindrical surface (bore / outer) or a circular edge for a thread", 0x00000024u, 1, 0, false, false, nullptr, "design_thread", "Thread a cylindrical surface (inner bore / outer) or a circular edge"},
|
||||
{"shell", "Shell", 2, "Shift+K", "key:S+K", "Shell needs a solid body", 0x00000082u, 1, 0, false, false, nullptr, "design_shell", "Hollow the body to a wall thickness, opening a picked face"},
|
||||
{"cut", "Cut", 2, "Shift+X", "key:S+X", "Create a solid body to cut first", 0x000004feu, 1, 0, false, false, nullptr, "design_cut", "Trim the body with a plane — drag the offset arrow; keep one half or both"},
|
||||
{"split", "Split", 2, nullptr, nullptr, "Split needs a solid body", 0x000000feu, 1, 0, false, false, nullptr, nullptr, "Split the body along a picked face into two solids"},
|
||||
{"fillet", "Fillet", 3, "Shift+F", "btn:dress#0", "Pick an edge to round", 0x000000b2u, 1, 0, false, false, nullptr, "design_filletedge", "Pick an edge, then drag the radius arrow or type it"},
|
||||
{"chamfer", "Chamfer", 3, nullptr, "btn:dress#1", "Pick an edge to bevel", 0x000000b2u, 1, 0, false, false, nullptr, "design_chamfer", "Pick an edge, then drag the distance arrow or type it"},
|
||||
{"draft", "Draft", 3, "Shift+D", "key:S+D", "Pick a face to taper", 0x0000000au, 1, 0, false, false, nullptr, "design_draft", "Tilt a picked face by a draft angle"},
|
||||
{"surf_offset", "Surface Offset", 3, nullptr, "fly:surface#4", "target is not a sheet body", 0x00000100u, 0, 0, true, false, nullptr, "design_offset", "Offset a sheet body's shell by a signed distance"},
|
||||
{"pattern", "Linear pattern", 4, "Shift+N", "btn:pat#0", "Create a solid body to pattern first", 0x00006082u, 1, 0, false, false, nullptr, "design_array", "Repeat the body along a direction — drag the spacing, set the count"},
|
||||
{"pattern_circular", "Circular pattern", 4, nullptr, "btn:pat#1", "Create a solid body to pattern first", 0x00006082u, 1, 0, false, false, nullptr, "design_polararray", "Repeat the body around an axis — set the count and sweep"},
|
||||
{"mirror", "Mirror", 4, "Shift+Z", "key:S+Z", "Mirror needs a body — add or import one first", 0x000004feu, 1, 0, false, false, nullptr, "design_mirror", "Reflect a body about a plane"},
|
||||
{"pat_curve", "Pattern on Curve", 4, nullptr, nullptr, "Pattern on curve needs a body and a curve", 0x00000090u, 1, 0, false, false, nullptr, nullptr, "Repeat the body along a picked curve"},
|
||||
{"transform", "Move", 5, "Shift+Y", "key:S+Y", "Transform needs a body — add or import one first", 0x000021feu, 1, 0, false, false, nullptr, "design_move", "Move and/or rotate an existing body"},
|
||||
{"mate", "Mate", 5, nullptr, "fly:placement#2", "A mate needs two coordinate systems", 0x00001202u, 2, 0, false, false, nullptr, "design_c_coincident", "Assembly: align two CoordSys features (fastened, planar, revolute, slider, cylindrical)"},
|
||||
{"align", "Align to", 5, nullptr, nullptr, "Align needs a body", 0x00000002u, 1, 0, false, false, nullptr, nullptr, "Align the body to a picked face or plane"},
|
||||
{"plane", "Plane", 6, "Shift+P", "key:S+P", nullptr, 0x00000453u, 0, 0, false, false, nullptr, "design_plane", "Reference plane (offset / tilt / midplane / tangent / two edges / coincident)"},
|
||||
{"axis", "Axis", 6, "Shift+A", "key:S+A", nullptr, 0x00000057u, 0, 0, false, false, nullptr, "design_line", "Datum axis (two points, face normal, cylinder centerline, two planes, along edge)"},
|
||||
{"coordsys_v", "Coord Sys", 6, "Shift+C", "key:S+C", nullptr, 0x00000043u, 0, 0, false, false, nullptr, "design_point", "Datum coordinate system (world point, or face + direction edge)"},
|
||||
{"helix", "Helix", 6, nullptr, "fly:plane#3", nullptr, 0x00000405u, 0, 0, false, false, nullptr, "design_thread", "Helical curve (spring path) — use as a sweep path for coils / springs / augers"},
|
||||
{"project", "Project", 6, nullptr, "fly:plane#4", "Project needs a body — add or import one first", 0x00000482u, 1, 0, false, false, nullptr, "design_sketch", "Project body edges onto a plane as sketch entities"},
|
||||
{"measure", "Measure", 6, nullptr, nullptr, nullptr, 0x000b03feu, 0, 0, false, false, nullptr, nullptr, "Measure between the picked points, edges or faces"},
|
||||
{"mass_props", "Mass", 6, nullptr, "btn:mass", nullptr, 0x000000feu, 1, 0, false, false, nullptr, "info", "Report the volume and surface area of the selected body"},
|
||||
{"interference", "Interference", 6, nullptr, nullptr, nullptr, 0x00000200u, 2, 0, false, false, nullptr, nullptr, "Check whether two bodies overlap — reports, changes nothing"},
|
||||
{"edit_feature", "Edit", 7, nullptr, "btn:edit", nullptr, 0x00007d8eu, 0, 0, false, false, nullptr, "design_edit", "Reopen the selected feature to change what it was made from"},
|
||||
{"rename", "Rename…", 7, "F2", "btn:rename", "Select a feature, or a body, to rename it", 0x00004080u, 0, 0, false, false, nullptr, nullptr, "Give this feature a name you will recognise in the tree (a body takes its name from the feature that makes it)"},
|
||||
{"delete_face", "Delete Face", 7, nullptr, "fly:dressup#3", "Delete Face needs a body — add or import one first", 0x0000000eu, 1, 0, false, false, nullptr, "design_delete", "Remove faces from a body and heal the solid"},
|
||||
{"colour", "Colour", 7, nullptr, "btn:colour", nullptr, 0x000001feu, 1, 0, false, false, nullptr, "color_palette", "Set the selected body's display colour"},
|
||||
{"delete", "Delete", 7, "Del", "btn:delete", nullptr, 0x000f7c00u, 0, 0, false, false, nullptr, "design_delete", "Delete what is selected"},
|
||||
{"delete_body", "Delete Body", 7, nullptr, "btn:delete_body", nullptr, 0x000001feu, 1, 0, false, false, nullptr, "design_delete", "Delete this whole body — removes the feature it was made from"},
|
||||
{"sk_line_t", "Line", 0, "L", "key:L", nullptr, 0x000f8000u, 0, 0, false, true, "Line", "design_line", "Line — click start, then end"},
|
||||
{"sk_polyline", "Polyline", 0, nullptr, "fly:design_line#1", nullptr, 0x000f8000u, 0, 0, false, true, "Line", "design_polyline", "Click points; click the first point to close the loop, right-click to end it open"},
|
||||
{"sk_rect", "Corner rectangle", 0, "R", "key:R", nullptr, 0x000f8000u, 0, 0, false, true, "Rectangle", "design_rect", "Rectangle — click two opposite corners"},
|
||||
{"sk_rect_center", "Centre rectangle", 0, nullptr, "fly:design_rect#1", nullptr, 0x000f8000u, 0, 0, false, true, "Rectangle", "design_crect", "Click center, then a corner"},
|
||||
{"sk_rect_oblique", "Oblique rectangle", 0, nullptr, "fly:design_rect#2", nullptr, 0x000f8000u, 0, 0, false, true, "Rectangle", "design_rect_oblique", "Click two corners of one edge, then a point for the width"},
|
||||
{"sk_rect_rounded", "Rounded rectangle", 0, nullptr, "fly:design_rect#3", nullptr, 0x000f8000u, 0, 0, false, true, "Rectangle", "design_rect_rounded", "Click two opposite corners, then a point for the corner radius"},
|
||||
{"sk_circle", "Centre circle", 0, "C", "key:C", nullptr, 0x000f8000u, 0, 0, false, true, "Circle", "design_circle", "Circle — click center, then radius"},
|
||||
{"sk_circle_2pt", "2-point circle", 0, nullptr, "fly:design_circle#1", nullptr, 0x000f8000u, 0, 0, false, true, "Circle", "design_circle2pt", "Click two ends of the diameter"},
|
||||
{"sk_circle_3pt", "3-point circle", 0, nullptr, "fly:design_circle#2", nullptr, 0x000f8000u, 0, 0, false, true, "Circle", "design_circle3pt", "Click three points on the circle"},
|
||||
{"sk_arc_t", "3-point arc", 0, "A", "key:A", nullptr, 0x000f8000u, 0, 0, false, true, "Arc", "design_arc3pt", "Arc — click start, end, then a point"},
|
||||
{"sk_arc_tangent", "Tangent arc", 0, nullptr, "fly:design_arc3pt#1", nullptr, 0x000f8000u, 0, 0, false, true, "Arc", "design_tangentarc", "Click start (on the last entity) then end"},
|
||||
{"sk_arc_center", "Centre-point arc", 0, nullptr, "fly:design_arc3pt#2", nullptr, 0x000f8000u, 0, 0, false, true, "Arc", "design_arc_center", "Click center, then start, then a point for the end angle"},
|
||||
{"sk_slot", "Slot", 0, "S", "key:S", nullptr, 0x000f8000u, 0, 0, false, true, "Slot", "design_slot", "Slot — two centerline ends, then end radius"},
|
||||
{"sk_slot_arc", "Arc slot", 0, nullptr, "fly:design_slot#1", nullptr, 0x000f8000u, 0, 0, false, true, "Slot", "design_slot_arc", "Click center, start, end, then a point for the width"},
|
||||
{"sk_ellipse", "Ellipse", 0, "E", "key:E", nullptr, 0x000f8000u, 0, 0, false, true, "Ellipse", "design_ellipse", "Ellipse — center, major end, minor point"},
|
||||
{"sk_ellipse_arc", "Elliptical arc", 0, nullptr, "fly:design_ellipse#1", nullptr, 0x000f8000u, 0, 0, false, true, "Ellipse", "design_ellipse_arc", "Click center, major-axis end, minor point, then arc start and end"},
|
||||
{"sk_spline", "Spline", 0, "B", "key:B", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_bspline", "Spline — click control points"},
|
||||
{"sk_poly_3", "Triangle", 0, nullptr, "btn:poly#3", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Triangle — click centre, then a vertex"},
|
||||
{"sk_poly_4", "Square", 0, nullptr, "btn:poly#4", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Square — click centre, then a vertex"},
|
||||
{"sk_poly_5", "Pentagon", 0, nullptr, "btn:poly#5", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Pentagon — click centre, then a vertex"},
|
||||
{"sk_polygon", "Hexagon", 0, "G", "btn:poly#6", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Hexagon — click centre, then a vertex"},
|
||||
{"sk_poly_8", "Octagon", 0, nullptr, "btn:poly#8", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Octagon — click centre, then a vertex"},
|
||||
{"sk_poly_12", "Dodecagon", 0, nullptr, "btn:poly#12", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Dodecagon — click centre, then a vertex"},
|
||||
{"sk_poly_inscribed", "Inscribed", 0, nullptr, "btn:polyfit#0", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Measure the polygon to its corners (inscribed)"},
|
||||
{"sk_poly_circumscribed", "Circumscribed", 0, nullptr, "btn:polyfit#1", nullptr, 0x000f8000u, 0, 0, false, true, "Polygon", "design_polygon", "Measure the polygon to its flats (circumscribed)"},
|
||||
{"sk_point_t", "Point", 0, "P", "key:P", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_point", "Point — click to place"},
|
||||
{"sk_text", "Text", 0, nullptr, "btn:text", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_text", "Type text; its outline is added to this sketch as editable lines"},
|
||||
{"sk_svg", "SVG", 0, nullptr, "btn:svg", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_svg", "Import an SVG outline into this sketch as editable lines"},
|
||||
{"sk_offset", "Offset", 1, "O", "key:O", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_offset", "Offset — pick an entity, drag the distance"},
|
||||
{"sk_trim", "Trim", 2, "T", "key:T", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_trim", "Trim — click a segment to trim it"},
|
||||
{"sk_fillet", "Fillet", 3, "F", "key:F", nullptr, 0x00090000u, 0, 0, false, true, nullptr, "design_filletedge", "Fillet — pick two lines, set the radius"},
|
||||
{"sk_chamfer", "Chamfer", 3, "H", "key:H", nullptr, 0x00090000u, 0, 0, false, true, nullptr, "design_chamfer", "Chamfer — pick two lines, set the distance"},
|
||||
{"sk_array", "Linear array", 4, nullptr, "fly:design_array#0", nullptr, 0x000b0000u, 0, 0, false, true, "Array", "design_array", "Pick entities, drag the spacing handle, click the count; click empty to apply"},
|
||||
{"sk_array_polar", "Polar array", 4, nullptr, "fly:design_array#1", nullptr, 0x000b0000u, 0, 0, false, true, "Array", "design_polararray", "Pick entities, drag the sweep handle, click the count; click empty to apply"},
|
||||
{"sk_mirror", "Mirror", 4, "M", "key:M", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_mirror", "Mirror — pick axis, then entities"},
|
||||
{"sk_move", "Move", 5, nullptr, "fly:design_move#0", nullptr, 0x000f0000u, 0, 0, false, true, "Move", "design_move", "Pick entities, then drag the handle or click the distance; click empty to apply"},
|
||||
{"sk_rotate", "Rotate", 5, nullptr, "fly:design_move#1", nullptr, 0x000f0000u, 0, 0, false, true, "Move", "design_rotate", "Pick entities, then drag around the pivot or click the angle; click empty to apply"},
|
||||
{"sk_scale", "Scale", 5, nullptr, "fly:design_move#2", nullptr, 0x000f0000u, 0, 0, false, true, "Move", "design_scale", "Pick entities, then drag the handle or click the factor; click empty to apply"},
|
||||
{"sk_dimension", "Dimension", 6, "D", "key:D", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_dimension", "Dimension — click 2 points or an entity"},
|
||||
{"sk_constrain", "Constrain", 6, "K", "key:K", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_constrain", "Constrain the selected sketch entities to each other"},
|
||||
{"sketch", L("Sketch"), 0, "Shift+S", "key:S+S", L("Click a face or a reference plane in the viewport, then a sketch tool"), 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", L("Click a face or a reference plane, then pick a drawing tool")},
|
||||
{"extrude", L("Extrude"), 1, "Shift+E", "key:S+E", L("Create a sketch, or pick a solid face, first"), 0x00004002u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch profile, or push/pull a picked face")},
|
||||
{"revolve", L("Revolve"), 1, "Shift+R", "key:S+R", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a profile about an axis")},
|
||||
{"sweep", L("Sweep"), 1, "Shift+W", "key:S+W", L("Create a profile sketch to sweep first"), 0x00004000u, 0, 2, false, false, nullptr, "design_sweep", L("Sweep a profile along a path")},
|
||||
{"loft", L("Loft"), 1, "Shift+L", "key:S+L", L("Create at least two profile sketches to loft"), 0x00004000u, 0, 2, false, false, nullptr, "design_loft", L("Loft (skin) between two or more profiles")},
|
||||
{"thicken", L("Thicken"), 1, nullptr, "fly:material#4", L("Thicken needs a solid body — add or import one first"), 0x0000000au, 1, 0, false, false, nullptr, "design_thicken", L("Offset a solid face into a thin plate (new body)")},
|
||||
{"rib", L("Rib"), 1, "R", "fly:material#5", L("Rib needs a solid body — add or import one first"), 0x00010000u, 1, 0, false, false, nullptr, "design_rib", L("Grow a thin wall from an open sketch line, fused to a body")},
|
||||
{"boolean", L("Join"), 1, "Shift+B", "btn:bool#0", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Fuse the tool body into the target — one solid, no seam")},
|
||||
{"bool_subtract", L("Subtract"), 1, nullptr, "btn:bool#1", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Cut the tool body out of the target")},
|
||||
{"bool_intersect", L("Intersect"), 1, nullptr, "btn:bool#2", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Keep only where the two bodies overlap")},
|
||||
{"surf_extrude", L("Surface Extrude"), 1, "Shift+G", "key:S+G", L("Create a sketch first"), 0x00004000u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch into a sheet body (no end caps)")},
|
||||
{"surf_revolve", L("Surface Revolve"), 1, nullptr, "fly:surface#1", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a sketch profile into a sheet body")},
|
||||
{"surf_loft", L("Surface Loft"), 1, nullptr, "fly:surface#2", L("Create at least two profile sketches to loft"), 0x00004000u, 0, 2, false, false, nullptr, "design_loft", L("Loft (skin) between 2+ profiles, open (no end caps)")},
|
||||
{"surf_fill", L("Surface Fill"), 1, nullptr, "fly:surface#3", L("Create a closed sketch first"), 0x00004000u, 0, 0, false, false, nullptr, "design_surface", L("Fill a sketch boundary with a smooth face")},
|
||||
{"thicken_surf", L("Thicken Surface"), 1, nullptr, "fly:surface#5", L("Thicken Surface needs a surface body — make one with a Surface tool first"), 0x00000100u, 0, 0, true, false, nullptr, "design_thicken", L("Thicken a sheet body into a solid")},
|
||||
{"hole", L("Hole"), 2, "Shift+H", "key:S+H", L("Pick a face or a plane to drill into"), 0x00000402u, 1, 0, false, false, nullptr, "design_hole", L("Drill a hole, centerd on a picked face or placed on a plane")},
|
||||
{"thread", L("Thread"), 2, "Shift+T", "key:S+T", L("Pick a cylindrical surface (bore / outer) or a circular edge for a thread"), 0x00000024u, 1, 0, false, false, nullptr, "design_thread", L("Thread a cylindrical surface (inner bore / outer) or a circular edge")},
|
||||
{"shell", L("Shell"), 2, "Shift+K", "key:S+K", L("Shell needs a solid body — add or import one first"), 0x00000082u, 1, 0, false, false, nullptr, "design_shell", L("Hollow the body to a wall thickness, opening a picked face")},
|
||||
{"cut", L("Cut"), 2, "Shift+X", "key:S+X", L("Cut needs a solid body — add or import one first"), 0x000004feu, 1, 0, false, false, nullptr, "design_cut", L("Trim the body with a plane — drag the offset arrow; keep one half or both")},
|
||||
{"split", L("Split"), 2, nullptr, nullptr, L("Split needs a solid body — add or import one first"), 0x000000feu, 1, 0, false, false, nullptr, nullptr, L("Split the body along a picked face into two solids")},
|
||||
{"fillet", L("Fillet"), 3, "Shift+F", "btn:dress#0", L("Pick an edge to round"), 0x000000b2u, 1, 0, false, false, nullptr, "design_filletedge", L("Pick an edge, then drag the radius arrow or type it")},
|
||||
{"chamfer", L("Chamfer"), 3, nullptr, "btn:dress#1", L("Pick an edge to bevel"), 0x000000b2u, 1, 0, false, false, nullptr, "design_chamfer", L("Pick an edge, then drag the distance arrow or type it")},
|
||||
{"draft", L("Draft"), 3, "Shift+D", "key:S+D", L("Pick a face to taper"), 0x0000000au, 1, 0, false, false, nullptr, "design_draft", L("Tilt a picked face by a draft angle")},
|
||||
{"surf_offset", L("Surface Offset"), 3, nullptr, "fly:surface#4", L("Surface Offset needs a surface body — make one with a Surface tool first"), 0x00000100u, 0, 0, true, false, nullptr, "design_offset", L("Offset a sheet body's shell by a signed distance")},
|
||||
{"pattern", L("Linear pattern"), 4, "Shift+N", "btn:pat#0", L("Pattern needs a solid body — add or import one first"), 0x00006082u, 1, 0, false, false, nullptr, "design_array", L("Repeat the body along a direction — drag the spacing, set the count")},
|
||||
{"pattern_circular", L("Circular pattern"), 4, nullptr, "btn:pat#1", L("Pattern needs a solid body — add or import one first"), 0x00006082u, 1, 0, false, false, nullptr, "design_polararray", L("Repeat the body around an axis — set the count and sweep")},
|
||||
{"mirror", L("Mirror"), 4, "Shift+Z", "key:S+Z", L("Mirror needs a body — add or import one first"), 0x000004feu, 1, 0, false, false, nullptr, "design_mirror", L("Reflect a body about a plane")},
|
||||
{"pat_curve", L("Pattern on Curve"), 4, nullptr, nullptr, L("Pattern on Curve needs a body and a curve"), 0x00000090u, 1, 0, false, false, nullptr, nullptr, L("Repeat the body along a picked curve")},
|
||||
{"transform", L("Move"), 5, "Shift+Y", "key:S+Y", L("Transform needs a body — add or import one first"), 0x000021feu, 1, 0, false, false, nullptr, "design_move", L("Move and/or rotate an existing body")},
|
||||
{"mate", L("Mate"), 5, nullptr, "fly:placement#2", L("Mate needs two coordinate systems — create them first"), 0x00001202u, 2, 0, false, false, nullptr, "design_c_coincident", L("Assembly: align two coordinate systems (fastened, planar, revolute, slider, cylindrical)")},
|
||||
{"align", L("Align to"), 5, nullptr, nullptr, L("Align needs a body — add or import one first"), 0x00000002u, 1, 0, false, false, nullptr, nullptr, L("Align the body to a picked face or plane")},
|
||||
{"plane", L("Plane"), 6, "Shift+P", "key:S+P", nullptr, 0x00000453u, 0, 0, false, false, nullptr, "design_plane", L("Reference plane (offset / tilt / midplane / tangent / two edges / coincident)")},
|
||||
{"axis", L("Axis"), 6, "Shift+A", "key:S+A", nullptr, 0x00000057u, 0, 0, false, false, nullptr, "design_line", L("Datum axis (two points, face normal, cylinder centerline, two planes, along edge)")},
|
||||
{"coordsys_v", L("Coordinate system"), 6, "Shift+C", "key:S+C", nullptr, 0x00000043u, 0, 0, false, false, nullptr, "design_point", L("Datum coordinate system (world point, or face + direction edge)")},
|
||||
{"helix", L("Helix"), 6, nullptr, "fly:plane#3", nullptr, 0x00000405u, 0, 0, false, false, nullptr, "design_thread", L("Helical curve (spring path) — use as a sweep path for coils / springs / augers")},
|
||||
{"project", L("Project"), 6, nullptr, "fly:plane#4", L("Project needs a body — add or import one first"), 0x00000482u, 1, 0, false, false, nullptr, "design_sketch", L("Project body edges onto a plane as sketch entities")},
|
||||
{"measure", L("Measure"), 6, nullptr, nullptr, nullptr, 0x000b03feu, 0, 0, false, false, nullptr, nullptr, L("Measure between the picked points, edges or faces")},
|
||||
{"mass_props", L("Volume and area"), 6, nullptr, "btn:mass", nullptr, 0x000000feu, 1, 0, false, false, nullptr, "info", L("Report the volume and surface area of the selected body")},
|
||||
{"interference", L("Interference"), 6, nullptr, "btn:interference", L("Interference needs at least two bodies"), 0x00000280u, 2, 0, false, false, nullptr, nullptr, L("Check whether two bodies overlap — reports, changes nothing")},
|
||||
{"edit_feature", L("Edit"), 7, nullptr, "btn:edit", nullptr, 0x00007d8eu, 0, 0, false, false, nullptr, "design_edit", L("Reopen the selected feature to change what it was made from")},
|
||||
{"rename", L("Rename…"), 7, "F2", "btn:rename", L("Select a feature, or a body, to rename it"), 0x00004080u, 0, 0, false, false, nullptr, nullptr, L("Give this feature a name you will recognise in the tree (a body takes its name from the feature that makes it)")},
|
||||
{"delete_face", L("Delete Face"), 7, nullptr, "fly:dressup#3", L("Delete Face needs a body — add or import one first"), 0x0000000eu, 1, 0, false, false, nullptr, "design_delete", L("Remove faces from a body and heal the solid")},
|
||||
{"colour", L("Color"), 7, nullptr, "btn:colour", nullptr, 0x000001feu, 1, 0, false, false, nullptr, "color_palette", L("Set the selected body's display color")},
|
||||
{"delete", L("Delete"), 7, "Del", "btn:delete", nullptr, 0x000f7c00u, 0, 0, false, false, nullptr, "design_delete", L("Delete what is selected")},
|
||||
{"delete_body", L("Delete Body"), 7, nullptr, "btn:delete_body", nullptr, 0x000001feu, 1, 0, false, false, nullptr, "design_delete", L("Delete this whole body — removes the feature it was made from")},
|
||||
{"sk_line_t", L("Line"), 0, "L", "key:L", nullptr, 0x000f8000u, 0, 0, false, true, L("Line"), "design_line", L("Line — click start, then end")},
|
||||
{"sk_polyline", L("Polyline"), 0, nullptr, "fly:design_line#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Line"), "design_polyline", L("Click points; click the first point to close the loop, right-click to end it open")},
|
||||
{"sk_rect", L("Corner rectangle"), 0, "R", "key:R", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_rect", L("Rectangle — click two opposite corners")},
|
||||
{"sk_rect_center", L("Center rectangle"), 0, nullptr, "fly:design_rect#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_crect", L("Click center, then a corner")},
|
||||
{"sk_rect_oblique", L("Oblique rectangle"), 0, nullptr, "fly:design_rect#2", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_rect_oblique", L("Click two corners of one edge, then a point for the width")},
|
||||
{"sk_rect_rounded", L("Rounded rectangle"), 0, nullptr, "fly:design_rect#3", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_rect_rounded", L("Click two opposite corners, then a point for the corner radius")},
|
||||
{"sk_circle", L("Center circle"), 0, "C", "key:C", nullptr, 0x000f8000u, 0, 0, false, true, L("Circle"), "design_circle", L("Circle — click center, then radius")},
|
||||
{"sk_circle_2pt", L("2-point circle"), 0, nullptr, "fly:design_circle#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Circle"), "design_circle2pt", L("Click two ends of the diameter")},
|
||||
{"sk_circle_3pt", L("3-point circle"), 0, nullptr, "fly:design_circle#2", nullptr, 0x000f8000u, 0, 0, false, true, L("Circle"), "design_circle3pt", L("Click three points on the circle")},
|
||||
{"sk_arc_t", L("3-point arc"), 0, "A", "key:A", nullptr, 0x000f8000u, 0, 0, false, true, L("Arc"), "design_arc3pt", L("Arc — click start, end, then a point")},
|
||||
{"sk_arc_tangent", L("Tangent arc"), 0, nullptr, "fly:design_arc3pt#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Arc"), "design_tangentarc", L("Click start (on the last entity) then end")},
|
||||
{"sk_arc_center", L("Center-point arc"), 0, nullptr, "fly:design_arc3pt#2", nullptr, 0x000f8000u, 0, 0, false, true, L("Arc"), "design_arc_center", L("Click center, then start, then a point for the end angle")},
|
||||
{"sk_slot", L("Slot"), 0, "S", "key:S", nullptr, 0x000f8000u, 0, 0, false, true, L("Slot"), "design_slot", L("Slot — two centerline ends, then the width")},
|
||||
{"sk_slot_arc", L("Arc slot"), 0, nullptr, "fly:design_slot#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Slot"), "design_slot_arc", L("Click center, start, end, then a point for the width")},
|
||||
{"sk_ellipse", L("Ellipse"), 0, "E", "key:E", nullptr, 0x000f8000u, 0, 0, false, true, L("Ellipse"), "design_ellipse", L("Ellipse — center, major end, minor point")},
|
||||
{"sk_ellipse_arc", L("Elliptical arc"), 0, nullptr, "fly:design_ellipse#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Ellipse"), "design_ellipse_arc", L("Click center, major-axis end, minor point, then arc start and end")},
|
||||
{"sk_spline", L("Spline"), 0, "B", "key:B", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_bspline", L("Spline — click control points")},
|
||||
{"sk_poly_3", L("Triangle"), 0, nullptr, "btn:poly#3", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Triangle — click center, then a vertex")},
|
||||
{"sk_poly_4", L("Square"), 0, nullptr, "btn:poly#4", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Square — click center, then a vertex")},
|
||||
{"sk_poly_5", L("Pentagon"), 0, nullptr, "btn:poly#5", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Pentagon — click center, then a vertex")},
|
||||
{"sk_polygon", L("Hexagon"), 0, "G", "btn:poly#6", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Hexagon — click center, then a vertex")},
|
||||
{"sk_poly_8", L("Octagon"), 0, nullptr, "btn:poly#8", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Octagon — click center, then a vertex")},
|
||||
{"sk_poly_12", L("Dodecagon"), 0, nullptr, "btn:poly#12", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Dodecagon — click center, then a vertex")},
|
||||
{"sk_poly_inscribed", L("Inscribed"), 0, nullptr, "btn:polyfit#0", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Measure the polygon to its corners (inscribed)")},
|
||||
{"sk_poly_circumscribed", L("Circumscribed"), 0, nullptr, "btn:polyfit#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Measure the polygon to its flats (circumscribed)")},
|
||||
{"sk_point_t", L("Point"), 0, "P", "key:P", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_point", L("Point — click to place")},
|
||||
{"sk_text", L("Text"), 0, nullptr, "btn:text", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_text", L("Type text; its outline is added to this sketch as editable lines")},
|
||||
{"sk_svg", L("SVG"), 0, nullptr, "btn:svg", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_svg", L("Import an SVG outline into this sketch as editable lines")},
|
||||
{"sk_offset", L("Offset"), 1, "O", "key:O", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_offset", L("Offset — pick an entity, drag the distance")},
|
||||
{"sk_trim", L("Trim"), 2, "T", "key:T", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_trim", L("Trim — click a segment to trim it")},
|
||||
{"sk_fillet", L("Fillet"), 3, "F", "key:F", nullptr, 0x00090000u, 0, 0, false, true, nullptr, "design_filletedge", L("Fillet — pick two lines, set the radius")},
|
||||
{"sk_chamfer", L("Chamfer"), 3, "H", "key:H", nullptr, 0x00090000u, 0, 0, false, true, nullptr, "design_chamfer", L("Chamfer — pick two lines, set the distance")},
|
||||
{"sk_array", L("Linear array"), 4, nullptr, "fly:design_array#0", nullptr, 0x000b0000u, 0, 0, false, true, L("Array"), "design_array", L("Pick entities, drag the spacing handle, click the count; click empty to apply")},
|
||||
{"sk_array_polar", L("Polar array"), 4, nullptr, "fly:design_array#1", nullptr, 0x000b0000u, 0, 0, false, true, L("Array"), "design_polararray", L("Pick entities, drag the sweep handle, click the count; click empty to apply")},
|
||||
{"sk_mirror", L("Mirror"), 4, "M", "key:M", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_mirror", L("Mirror — pick axis, then entities")},
|
||||
{"sk_move", L("Move"), 5, nullptr, "fly:design_move#0", nullptr, 0x000f0000u, 0, 0, false, true, L("Move"), "design_move", L("Pick entities, then drag the handle or click the distance; click empty to apply")},
|
||||
{"sk_rotate", L("Rotate"), 5, nullptr, "fly:design_move#1", nullptr, 0x000f0000u, 0, 0, false, true, L("Move"), "design_rotate", L("Pick entities, then drag around the pivot or click the angle; click empty to apply")},
|
||||
{"sk_scale", L("Scale"), 5, nullptr, "fly:design_move#2", nullptr, 0x000f0000u, 0, 0, false, true, L("Move"), "design_scale", L("Pick entities, then drag the handle or click the factor; click empty to apply")},
|
||||
{"sk_dimension", L("Dimension"), 6, "D", "key:D", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_dimension", L("Dimension — click 2 points or an entity")},
|
||||
{"sk_constrain", L("Constrain"), 6, "K", "key:K", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_constrain", L("Constrain the selected sketch entities to each other")},
|
||||
// Same verb, model-mode vocabulary: offered when a SKETCH is selected (bit 14, SkLoop), the
|
||||
// state a user is in right after finishing one. Without this row the only way in was the
|
||||
// toolbar icon, and constraints read as absent — see the Onshape-comparison report.
|
||||
{"constrain", "Constrain sketch", 7, nullptr, "btn:constrain", "Select a sketch to constrain it", 0x00004000u, 0, 1, false, false, nullptr, "design_constrain", "Add dimensions and relations (coincident, tangent, parallel...) to the selected sketch"},
|
||||
{"sk_construct", "Construction", 6, "Q", "key:Q", nullptr, 0x000b8000u, 0, 0, false, true, nullptr, nullptr, "Toggle construction: geometry that guides but is never built"},
|
||||
{"sk_extend", "Extend", 7, "X", "key:X", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_extend", "Extend — click a line/arc to extend it"},
|
||||
{"sk_delete", "Delete", 7, "Del", "btn:sk_delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_delete", "Delete the selected sketch entities"},
|
||||
// It sits in the Reference row, the address the sketch-mode Constrain has: one verb, one row.
|
||||
{"constrain", L("Constrain sketch"), 6, nullptr, "btn:constrain", L("Select a sketch to constrain it"), 0x00004000u, 0, 1, false, false, nullptr, "design_constrain", L("Add dimensions and relations (coincident, tangent, parallel...) to the selected sketch")},
|
||||
{"sk_construct", L("Construction"), 6, "Q", "key:Q", nullptr, 0x000b8000u, 0, 0, false, true, nullptr, nullptr, L("Toggle construction: geometry that guides but is never built")},
|
||||
{"sk_extend", L("Extend"), 7, "X", "key:X", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_extend", L("Extend — click a line/arc to extend it")},
|
||||
{"sk_delete", L("Delete"), 7, "Del", "btn:sk_delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_delete", L("Delete the selected sketch entities")},
|
||||
// Typing the defining number of the element you pointed at. Three rows rather than one so
|
||||
// each names the quantity in the drawing-office word for THAT element; all three land on
|
||||
// the same handler, because dimension_kind() already resolves the quantity from the
|
||||
// selection. Without these, an element's own numbers were reachable only by arming the
|
||||
// Dimension tool and re-picking geometry that was already selected.
|
||||
{"sk_length", "Length…", 7, "V", "key:V", nullptr, 0x00010000u, 0, 0, false, true, nullptr, "design_dimension", "Type the length of this line"},
|
||||
{"sk_radius", "Radius / diameter…", 7, "V", "key:V", nullptr, 0x00020000u, 0, 0, false, true, nullptr, "design_dimension", "Type the radius of this arc, or the diameter of this circle"},
|
||||
{"sk_angdist", "Angle / distance…", 7, "V", "key:V", nullptr, 0x00080000u, 0, 0, false, true, nullptr, "design_dimension", "Type the angle between two lines, or the distance between the two picks"},
|
||||
{"sk_length", L("Length…"), 7, "V", "key:V", nullptr, 0x00010000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the length of this line")},
|
||||
{"sk_radius", L("Radius / diameter…"), 7, "V", "key:V", nullptr, 0x00020000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the radius of this arc, or the diameter of this circle")},
|
||||
{"sk_angdist", L("Angle / distance…"), 7, "V", "key:V", nullptr, 0x00080000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the angle between two lines, or the distance between the two picks")},
|
||||
};
|
||||
static const int kOfferVerbCount = 92;
|
||||
|
||||
|
||||
+200
-180
File diff suppressed because it is too large
Load Diff
@@ -128,6 +128,7 @@ private:
|
||||
|
||||
void on_shape_changed();
|
||||
void on_add_sketch();
|
||||
std::string feature_name(const wxString& kind) const;
|
||||
void on_add_extrude();
|
||||
void on_add_dressup();
|
||||
void on_add_hole();
|
||||
@@ -528,6 +529,7 @@ private:
|
||||
|
||||
wxStaticText* m_extrude_sketch_label{nullptr};
|
||||
int m_extrude_sketch_ref{-1};
|
||||
int m_extrude_auto_body{-1}; // body the profile touches: the inferred Join target
|
||||
|
||||
// Revolve controls (sweep a sketch profile about an in-plane axis).
|
||||
wxStaticText* m_revolve_sketch_label{nullptr};
|
||||
|
||||
@@ -65,19 +65,9 @@ static double ray_segment_dist3(const Vec3d& ro, const Vec3d& rd, const Vec3d& a
|
||||
return wxPoint(int(sx + 0.5), int(sy + 0.5));
|
||||
}
|
||||
|
||||
// The kernel's weld tolerance follows the app preference, and it must be pushed at EVERY
|
||||
// point that starts a sketch session: a Constrain session never passes through begin(), and
|
||||
// it uses region_loops()/connected_loop(), which read the same tolerance. Pushing in one
|
||||
// place only would leave those sessions on whatever the previous session set.
|
||||
static void push_auto_close_pref()
|
||||
{
|
||||
Slic3r::set_sketch_auto_close(wxGetApp().is_auto_close_sketch_loops());
|
||||
}
|
||||
|
||||
void DesignSketchTool::begin(const SketchPlane& plane, Mode mode)
|
||||
{
|
||||
m_sketch_redo.clear();
|
||||
push_auto_close_pref();
|
||||
|
||||
m_plane = plane;
|
||||
m_mode = mode;
|
||||
@@ -2451,7 +2441,6 @@ void DesignSketchTool::finish()
|
||||
|
||||
void DesignSketchTool::begin_constrain(const SketchProfile& prof, const SketchPlane& plane)
|
||||
{
|
||||
push_auto_close_pref();
|
||||
m_plane = plane;
|
||||
m_mode = Mode::Constrain;
|
||||
m_points = prof.points;
|
||||
@@ -2466,7 +2455,6 @@ void DesignSketchTool::begin_constrain(const SketchProfile& prof, const SketchPl
|
||||
void DesignSketchTool::begin_constrain_entities(const std::vector<SketchEntity>& ents,
|
||||
const SketchPlane& plane)
|
||||
{
|
||||
push_auto_close_pref();
|
||||
m_plane = plane;
|
||||
m_mode = Mode::Constrain;
|
||||
m_constrain_entities = true;
|
||||
@@ -10422,7 +10410,7 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
|
||||
// In-canvas edit-op tools (Fillet/Chamfer/Offset/Mirror): pick entities, then a
|
||||
// draggable arrow + editable value label (Mirror: a two-phase pick) drives a live
|
||||
// ghost. A click on empty space confirms; right-click/Esc cancels the gesture.
|
||||
// ghost. Enter or ✓ applies, Esc or right-click discards (charter 4.2).
|
||||
// Standalone Trim / Extend scissors: click a segment to cut it back to (Trim) or out to
|
||||
// (Extend) its nearest intersection with the other live entities. One cut per click; the
|
||||
// tool stays active for more cuts; right-click exits. Drag falls through so the camera can
|
||||
@@ -10502,7 +10490,7 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
|
||||
// In-canvas transform tools (Move/Rotate/Scale/Array/PolarArray): pick subject
|
||||
// entities, then a single draggable handle + editable value label(s) drive a live
|
||||
// ghost. A click on empty space confirms; right-click drops the gesture / exits.
|
||||
// ghost. Enter or ✓ applies, Esc or right-click discards the pending transform.
|
||||
if (is_transform_mode()) {
|
||||
if (evt.Moving()) {
|
||||
screen_to_plane(canvas, evt, m_cursor);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
@@ -20,7 +21,11 @@ GLGizmoPrimitive::GLGizmoPrimitive(GLCanvas3D& parent, const std::string& icon_f
|
||||
|
||||
bool GLGizmoPrimitive::on_init() { return true; }
|
||||
std::string GLGizmoPrimitive::on_get_name() const { return _u8L("Primitive"); }
|
||||
bool GLGizmoPrimitive::on_is_activable() const { return true; }
|
||||
// Part of the experimental CAD feature: built into every CAD build, but offered in the Prepare
|
||||
// toolbar only when that feature is switched on in Preferences — switched off, Prepare must be
|
||||
// exactly what it is without it.
|
||||
bool GLGizmoPrimitive::on_is_activable() const { return wxGetApp().is_enable_cad_feature(); }
|
||||
bool GLGizmoPrimitive::on_is_selectable() const { return wxGetApp().is_enable_cad_feature(); }
|
||||
void GLGizmoPrimitive::on_render() {}
|
||||
void GLGizmoPrimitive::on_set_state()
|
||||
{ if (m_state == EState::On) { m_params = PrimitiveParams{}; m_preview_dirty = true; } }
|
||||
@@ -80,14 +85,15 @@ void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_lim
|
||||
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_NoTitleBar);
|
||||
|
||||
if (ImGui::CollapsingHeader("Shape", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
static const char* names[] = {"Box", "Cylinder", "Sphere", "Cone", "Torus"};
|
||||
if (ImGui::CollapsingHeader(_u8L("Shape").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
const std::string names_s[] = {_u8L("Box"), _u8L("Cylinder"), _u8L("Sphere"), _u8L("Cone"), _u8L("Torus")};
|
||||
const char* names[] = {names_s[0].c_str(), names_s[1].c_str(), names_s[2].c_str(), names_s[3].c_str(), names_s[4].c_str()};
|
||||
int cur = (int)m_params.type;
|
||||
if (ImGui::Combo("##type", &cur, names, (int)PrimitiveType::COUNT)) {
|
||||
m_params.type = (PrimitiveType)cur;
|
||||
m_preview_dirty = true;
|
||||
}
|
||||
ImGui::Text("Quick:");
|
||||
ImGui::TextUnformatted(_u8L("Quick:").c_str());
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("10mm")) apply_preset("10mm cube", 10, 10, 10);
|
||||
ImGui::SameLine();
|
||||
@@ -98,7 +104,7 @@ void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_lim
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::CollapsingHeader("Dimensions", ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (ImGui::CollapsingHeader(_u8L("Dimensions").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
auto dim = [&](const char* label, double& val, double step=0.5, double fast=5.0) {
|
||||
ImGui::SetNextItemWidth(130);
|
||||
if (ImGui::InputDouble(label, &val, step, fast, "%.1f mm")) m_preview_dirty = true;
|
||||
@@ -106,25 +112,25 @@ void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_lim
|
||||
};
|
||||
switch (m_params.type) {
|
||||
case PrimitiveType::Box:
|
||||
dim("Width (X)", m_params.box_w);
|
||||
dim("Depth (Y)", m_params.box_d);
|
||||
dim("Height (Z)", m_params.box_h);
|
||||
dim(_u8L("Width (X)").c_str(), m_params.box_w);
|
||||
dim(_u8L("Depth (Y)").c_str(), m_params.box_d);
|
||||
dim(_u8L("Height (Z)").c_str(), m_params.box_h);
|
||||
break;
|
||||
case PrimitiveType::Cylinder:
|
||||
dim("Radius", m_params.cyl_radius);
|
||||
dim("Height", m_params.cyl_height);
|
||||
dim(_u8L("Radius").c_str(), m_params.cyl_radius);
|
||||
dim(_u8L("Height").c_str(), m_params.cyl_height);
|
||||
break;
|
||||
case PrimitiveType::Sphere:
|
||||
dim("Radius", m_params.sph_radius);
|
||||
dim(_u8L("Radius").c_str(), m_params.sph_radius);
|
||||
break;
|
||||
case PrimitiveType::Cone:
|
||||
dim("Bottom R", m_params.cone_r1);
|
||||
dim("Top R", m_params.cone_r2);
|
||||
dim("Height", m_params.cone_height);
|
||||
dim(_u8L("Bottom R").c_str(), m_params.cone_r1);
|
||||
dim(_u8L("Top R").c_str(), m_params.cone_r2);
|
||||
dim(_u8L("Height").c_str(), m_params.cone_height);
|
||||
break;
|
||||
case PrimitiveType::Torus:
|
||||
dim("Major R", m_params.torus_r1);
|
||||
dim("Minor R", m_params.torus_r2, 0.1, 1.0);
|
||||
dim(_u8L("Major R").c_str(), m_params.torus_r1);
|
||||
dim(_u8L("Minor R").c_str(), m_params.torus_r2, 0.1, 1.0);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
@@ -132,26 +138,28 @@ void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_lim
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::CollapsingHeader("Fillet / Chamfer")) {
|
||||
ImGui::Checkbox("Enable", &m_params.dressup_enabled);
|
||||
if (ImGui::CollapsingHeader(_u8L("Fillet / Chamfer").c_str())) {
|
||||
ImGui::Checkbox(_u8L("Enable").c_str(), &m_params.dressup_enabled);
|
||||
if (m_params.dressup_enabled) {
|
||||
static const char* dn[] = {"Fillet", "Chamfer"};
|
||||
const std::string dn_s[] = {_u8L("Fillet"), _u8L("Chamfer")};
|
||||
const char* dn[] = {dn_s[0].c_str(), dn_s[1].c_str()};
|
||||
int du = (int)m_params.dressup_type;
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::Combo("##dtype", &du, dn, 2)) { m_params.dressup_type = (DressUpType)du; m_preview_dirty = true; }
|
||||
static const char* fn[] = {"All edges", "Top edges", "Bottom edges", "Lateral edges"};
|
||||
const std::string fn_s[] = {_u8L("All edges"), _u8L("Top edges"), _u8L("Bottom edges"), _u8L("Lateral edges")};
|
||||
const char* fn[] = {fn_s[0].c_str(), fn_s[1].c_str(), fn_s[2].c_str(), fn_s[3].c_str()};
|
||||
int fg = (int)m_params.dressup_faces;
|
||||
ImGui::SetNextItemWidth(140);
|
||||
if (ImGui::Combo("Edges", &fg, fn, 4)) { m_params.dressup_faces = (FaceGroup)fg; m_preview_dirty = true; }
|
||||
if (ImGui::Combo(_u8L("Edges").c_str(), &fg, fn, 4)) { m_params.dressup_faces = (FaceGroup)fg; m_preview_dirty = true; }
|
||||
if (m_params.dressup_type == DressUpType::Fillet) {
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::InputDouble("Radius", &m_params.dressup_radius, 0.1, 1.0, "%.1f mm")) {
|
||||
if (ImGui::InputDouble(_u8L("Radius").c_str(), &m_params.dressup_radius, 0.1, 1.0, "%.1f mm")) {
|
||||
if (m_params.dressup_radius < 0.1) m_params.dressup_radius = 0.1;
|
||||
m_preview_dirty = true;
|
||||
}
|
||||
} else {
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::InputDouble("Distance", &m_params.dressup_chamfer_dist, 0.1, 1.0, "%.1f mm")) {
|
||||
if (ImGui::InputDouble(_u8L("Distance").c_str(), &m_params.dressup_chamfer_dist, 0.1, 1.0, "%.1f mm")) {
|
||||
if (m_params.dressup_chamfer_dist < 0.1) m_params.dressup_chamfer_dist = 0.1;
|
||||
m_preview_dirty = true;
|
||||
}
|
||||
@@ -161,9 +169,9 @@ void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_lim
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::CollapsingHeader("Quality")) {
|
||||
if (ImGui::CollapsingHeader(_u8L("Quality").c_str())) {
|
||||
ImGui::SetNextItemWidth(130);
|
||||
if (ImGui::InputDouble("Mesh resolution", &m_params.linear_deflection, 0.001, 0.1, "%.3f mm")) {
|
||||
if (ImGui::InputDouble(_u8L("Mesh resolution").c_str(), &m_params.linear_deflection, 0.001, 0.1, "%.3f mm")) {
|
||||
if (m_params.linear_deflection < 0.001) m_params.linear_deflection = 0.001;
|
||||
if (m_params.linear_deflection > 1.0) m_params.linear_deflection = 1.0;
|
||||
m_preview_dirty = true;
|
||||
@@ -172,10 +180,10 @@ void GLGizmoPrimitive::on_render_input_window(float x, float y, float bottom_lim
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (ImGui::Button("Add Shape", {-1, 28}))
|
||||
if (ImGui::Button(_u8L("Add Shape").c_str(), {-1, 28}))
|
||||
apply_primitive();
|
||||
|
||||
if (ImGui::Button("Close", {-1, 0}))
|
||||
if (ImGui::Button(_u8L("Close").c_str(), {-1, 0}))
|
||||
m_parent.reset_all_gizmos();
|
||||
|
||||
GizmoImguiEnd();
|
||||
|
||||
@@ -20,6 +20,7 @@ protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_render() override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "slic3r/GUI/GLCanvas3D.hpp"
|
||||
#include "slic3r/GUI/ImGuiWrapper.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/NotificationManager.hpp"
|
||||
@@ -15,8 +16,6 @@
|
||||
#endif
|
||||
#include <imgui/imgui_internal.h>
|
||||
|
||||
#define UL(s) Slic3r::GUI::I18N::translate_utf8((s)).c_str()
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
@@ -25,7 +24,11 @@ GLGizmoSketch::GLGizmoSketch(GLCanvas3D& parent, const std::string& icon_filenam
|
||||
|
||||
bool GLGizmoSketch::on_init() { return true; }
|
||||
std::string GLGizmoSketch::on_get_name() const { return _u8L("Sketch"); }
|
||||
bool GLGizmoSketch::on_is_activable() const { return true; }
|
||||
// Part of the experimental CAD feature: built into every CAD build, but offered in the Prepare
|
||||
// toolbar only when that feature is switched on in Preferences — switched off, Prepare must be
|
||||
// exactly what it is without it.
|
||||
bool GLGizmoSketch::on_is_activable() const { return wxGetApp().is_enable_cad_feature(); }
|
||||
bool GLGizmoSketch::on_is_selectable() const { return wxGetApp().is_enable_cad_feature(); }
|
||||
void GLGizmoSketch::on_render() {}
|
||||
void GLGizmoSketch::on_set_state() { if (m_state == EState::On) clear_all(); }
|
||||
bool GLGizmoSketch::on_mouse(const wxMouseEvent&) { return false; }
|
||||
@@ -257,8 +260,9 @@ void GLGizmoSketch::on_render_input_window(float x, float y, float bottom_limit)
|
||||
| ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse
|
||||
| ImGuiWindowFlags_NoTitleBar);
|
||||
|
||||
if (ImGui::CollapsingHeader(UL("Profile"), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
static const char* names[] = {"Line", "Rectangle", "Circle", "Polygon"};
|
||||
if (ImGui::CollapsingHeader(_u8L("Profile").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
const std::string names_s[] = {_u8L("Line"), _u8L("Rectangle"), _u8L("Circle"), _u8L("Polygon")};
|
||||
const char* names[] = {names_s[0].c_str(), names_s[1].c_str(), names_s[2].c_str(), names_s[3].c_str()};
|
||||
int cur = (int)m_tool;
|
||||
if (ImGui::Combo("##shape", &cur, names, (int)SketchTool::COUNT)) {
|
||||
m_tool = (SketchTool)cur;
|
||||
@@ -266,39 +270,40 @@ void GLGizmoSketch::on_render_input_window(float x, float y, float bottom_limit)
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (m_imgui->button("+##newprofile")) m_active_profile = -1;
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", UL("Start new profile (for holes)"));
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Start new profile (for holes)").c_str());
|
||||
|
||||
if (m_tool == SketchTool::Rectangle) {
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("W", &m_rect_w,1,10,"%.0f")) build_preset_profile();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble(_u8L("Width").c_str(), &m_rect_w,1,10,"%.0f")) build_preset_profile();
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("H", &m_rect_h,1,10,"%.0f")) build_preset_profile();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble(_u8L("Height").c_str(), &m_rect_h,1,10,"%.0f")) build_preset_profile();
|
||||
} else if (m_tool == SketchTool::Circle) {
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("R", &m_circle_r,1,5,"%.0f")) build_preset_profile();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble(_u8L("Radius").c_str(), &m_circle_r,1,5,"%.0f")) build_preset_profile();
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::SliderInt("Seg", &m_circle_seg,8,64)) build_preset_profile();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::SliderInt(_u8L("Segments").c_str(), &m_circle_seg,8,64)) build_preset_profile();
|
||||
} else if (m_tool == SketchTool::Polygon) {
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::SliderInt("Sides", &m_poly_sides,3,12)) build_preset_profile();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::SliderInt(_u8L("Sides").c_str(), &m_poly_sides,3,12)) build_preset_profile();
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble("R", &m_poly_r,1,5,"%.0f")) build_preset_profile();
|
||||
ImGui::SetNextItemWidth(80); if (ImGui::InputDouble((_u8L("Radius") + "##poly").c_str(), &m_poly_r,1,5,"%.0f")) build_preset_profile();
|
||||
} else {
|
||||
ImGui::Text("%s", UL("Click on canvas to draw"));
|
||||
ImGui::Text("%s", _u8L("Click on canvas to draw").c_str());
|
||||
}
|
||||
|
||||
ImGui::Checkbox(UL("Snap to grid"), &m_snap_grid);
|
||||
ImGui::Checkbox(_u8L("Snap to grid").c_str(), &m_snap_grid);
|
||||
ImGui::SameLine();
|
||||
ImGui::SetNextItemWidth(80); ImGui::InputFloat("Step", &m_grid_step, 1, 5, "%.0f mm");
|
||||
ImGui::SetNextItemWidth(80); ImGui::InputFloat(_u8L("Grid step").c_str(), &m_grid_step, 1, 5, "%.0f mm");
|
||||
|
||||
draw_canvas();
|
||||
|
||||
if (!m_profiles.empty()) {
|
||||
ImGui::Text("%s: %zu", UL("Profiles"), m_profiles.size());
|
||||
ImGui::Text("%s: %zu", _u8L("Profiles").c_str(), m_profiles.size());
|
||||
for (int i = 0; i < (int)m_profiles.size(); ++i) {
|
||||
auto& p = m_profiles[i];
|
||||
ImGui::PushID(i);
|
||||
bool outer = (i == 0);
|
||||
ImVec4 col = outer ? ImVec4(0,1,0,1) : ImVec4(1,0.3f,0.3f,1);
|
||||
const char* label = outer ? "Outer" : "Hole";
|
||||
ImGui::TextColored(col, "%s %d: %zu pts %s", label, i+1, p.points.size(), p.closed ? "CLOSED" : "");
|
||||
const std::string label = outer ? _u8L("Outer") : _u8L("Hole");
|
||||
ImGui::TextColored(col, "%s %d: %zu %s %s", label.c_str(), i+1, p.points.size(),
|
||||
_u8L("points").c_str(), p.closed ? _u8L("closed").c_str() : "");
|
||||
ImGui::SameLine();
|
||||
if (ImGui::SmallButton("X")) delete_profile(i);
|
||||
ImGui::PopID();
|
||||
@@ -311,59 +316,63 @@ void GLGizmoSketch::on_render_input_window(float x, float y, float bottom_limit)
|
||||
bool is_revolve = false;
|
||||
bool has_sel = false;
|
||||
|
||||
if (ImGui::CollapsingHeader(UL("Operation"), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
if (ImGui::CollapsingHeader(_u8L("Operation").c_str(), ImGuiTreeNodeFlags_DefaultOpen)) {
|
||||
static int pi = 0;
|
||||
if (ImGui::Combo(UL("Plane"), &pi, "XY (Top)\0XZ (Front)\0YZ (Side)\0"))
|
||||
const std::string planes = "XY (" + _u8L("Top") + ")" + std::string(1, '\0') + "XZ (" + _u8L("Front") + ")"
|
||||
+ std::string(1, '\0') + "YZ (" + _u8L("Side") + ")" + std::string(2, '\0');
|
||||
if (ImGui::Combo(_u8L("Plane").c_str(), &pi, planes.c_str()))
|
||||
m_plane = (pi==0) ? SketchPlane::XY() : (pi==1) ? SketchPlane::XZ() : SketchPlane::YZ();
|
||||
|
||||
is_revolve = (m_sp.revolve_deg > 0 && m_sp.revolve_deg < 360);
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::InputDouble(UL("Revolve deg"), &m_sp.revolve_deg, 15, 90, "%.0f")) {
|
||||
if (ImGui::InputDouble(_u8L("Revolve deg").c_str(), &m_sp.revolve_deg, 15, 90, "%.0f")) {
|
||||
if (m_sp.revolve_deg > 360) m_sp.revolve_deg = 360;
|
||||
if (m_sp.revolve_deg < 0) m_sp.revolve_deg = 0;
|
||||
}
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", UL("Set to 0 for extrude, >0 for revolve"));
|
||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", _u8L("Set to 0 for extrude, >0 for revolve").c_str());
|
||||
|
||||
if (!is_revolve) {
|
||||
ImGui::SetNextItemWidth(100);
|
||||
ImGui::InputDouble(UL("Length"), &m_sp.extrude_len, 0.5, 5, "%.1f mm");
|
||||
ImGui::InputDouble(_u8L("Length").c_str(), &m_sp.extrude_len, 0.5, 5, "%.1f mm");
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox(UL("Symmetric"), &m_sp.extrude_sym);
|
||||
ImGui::Checkbox(_u8L("Symmetric").c_str(), &m_sp.extrude_sym);
|
||||
}
|
||||
|
||||
has_sel = !m_parent.get_selection().is_empty();
|
||||
if (has_sel) {
|
||||
if (ImGui::Checkbox(UL("Pocket (cut)"), &m_sp.is_pocket))
|
||||
if (ImGui::Checkbox(_u8L("Pocket (cut)").c_str(), &m_sp.is_pocket))
|
||||
if (m_sp.is_pocket) m_sp.dressup_enabled = false;
|
||||
} else m_sp.is_pocket = false;
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if (!m_sp.is_pocket && ImGui::CollapsingHeader(UL("Fillet / Chamfer"))) {
|
||||
ImGui::Checkbox(UL("Enable"), &m_sp.dressup_enabled);
|
||||
if (!m_sp.is_pocket && ImGui::CollapsingHeader(_u8L("Fillet / Chamfer").c_str())) {
|
||||
ImGui::Checkbox(_u8L("Enable").c_str(), &m_sp.dressup_enabled);
|
||||
if (m_sp.dressup_enabled) {
|
||||
static const char* dn[] = {"Fillet", "Chamfer"};
|
||||
const std::string dn_s[] = {_u8L("Fillet"), _u8L("Chamfer")};
|
||||
const char* dn[] = {dn_s[0].c_str(), dn_s[1].c_str()};
|
||||
int du = (int)m_sp.dressup_type;
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (ImGui::Combo("##dtype", &du, dn, 2)) m_sp.dressup_type = (DressUpType)du;
|
||||
static const char* fn[] = {"All edges", "Top edges", "Bottom edges", "Lateral edges"};
|
||||
const std::string fn_s[] = {_u8L("All edges"), _u8L("Top edges"), _u8L("Bottom edges"), _u8L("Lateral edges")};
|
||||
const char* fn[] = {fn_s[0].c_str(), fn_s[1].c_str(), fn_s[2].c_str(), fn_s[3].c_str()};
|
||||
int fg = (int)m_sp.dressup_faces;
|
||||
ImGui::SetNextItemWidth(140);
|
||||
ImGui::Combo(UL("Edges"), &fg, fn, 4); m_sp.dressup_faces = (FaceGroup)fg;
|
||||
ImGui::Combo(_u8L("Edges").c_str(), &fg, fn, 4); m_sp.dressup_faces = (FaceGroup)fg;
|
||||
ImGui::SetNextItemWidth(100);
|
||||
if (m_sp.dressup_type == DressUpType::Fillet)
|
||||
ImGui::InputDouble(UL("Radius"), &m_sp.dressup_radius, 0.1, 1, "%.1f mm");
|
||||
ImGui::InputDouble(_u8L("Radius").c_str(), &m_sp.dressup_radius, 0.1, 1, "%.1f mm");
|
||||
else
|
||||
ImGui::InputDouble(UL("Distance"), &m_sp.dressup_chamfer_dist, 0.1, 1, "%.1f mm");
|
||||
ImGui::InputDouble(_u8L("Distance").c_str(), &m_sp.dressup_chamfer_dist, 0.1, 1, "%.1f mm");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
bool ok = has_closed_profile();
|
||||
if (ok) ImGui::TextColored({0,1,0,1}, "%zu %s", m_profiles.size(), UL("closed profile(s)"));
|
||||
else ImGui::TextColored({0.6f,0.6f,0.6f,1}, "%s", UL("Draw a closed profile to enable"));
|
||||
if (ok) ImGui::TextColored({0,1,0,1}, "%zu %s", m_profiles.size(), _u8L("closed profile(s)").c_str());
|
||||
else ImGui::TextColored({0.6f,0.6f,0.6f,1}, "%s", _u8L("Draw a closed profile to enable").c_str());
|
||||
|
||||
auto btn = [&](const char* label, bool enabled) {
|
||||
if (!enabled) { ImGui::PushItemFlag(ImGuiItemFlags_Disabled,true); ImGui::PushStyleColor(ImGuiCol_Button,{0.25f,0.25f,0.25f,1}); }
|
||||
@@ -373,14 +382,14 @@ void GLGizmoSketch::on_render_input_window(float x, float y, float bottom_limit)
|
||||
};
|
||||
|
||||
if (m_sp.is_pocket && has_sel) {
|
||||
if (btn(_u8L("Pocket (Cut)").c_str(), ok)) apply_pocket();
|
||||
if (btn(_u8L("Pocket (cut)").c_str(), ok)) apply_pocket();
|
||||
} else if (is_revolve) {
|
||||
if (btn(_u8L("Revolve").c_str(), ok)) apply_revolve();
|
||||
} else {
|
||||
if (btn(_u8L("Extrude").c_str(), ok)) apply_extrude();
|
||||
}
|
||||
|
||||
if (ImGui::Button(_u8L("Clear All").c_str(), {-1,0})) clear_all();
|
||||
if (ImGui::Button(_u8L("Clear all").c_str(), {-1,0})) clear_all();
|
||||
if (ImGui::Button(_u8L("Close").c_str(), {-1,0})) m_parent.reset_all_gizmos();
|
||||
|
||||
GizmoImguiEnd();
|
||||
@@ -448,7 +457,7 @@ void GLGizmoSketch::apply_pocket()
|
||||
mo->ensure_on_bed();
|
||||
wxGetApp().plater()->update();
|
||||
clear_all();
|
||||
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, UL("Pocket added (negative volume)"));
|
||||
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::RegularNotificationLevel, _u8L("Pocket added (negative volume)").c_str());
|
||||
} catch (const std::exception& e) {
|
||||
wxGetApp().notification_manager()->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel, std::string("Pocket: ")+e.what());
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ protected:
|
||||
bool on_init() override;
|
||||
std::string on_get_name() const override;
|
||||
bool on_is_activable() const override;
|
||||
bool on_is_selectable() const override;
|
||||
void on_render() override;
|
||||
void on_set_state() override;
|
||||
CommonGizmosDataID on_get_requirements() const override;
|
||||
|
||||
@@ -10074,10 +10074,23 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
||||
// a user sees after opening a design they spent an hour on — it reads as "your work is gone"
|
||||
// when the recipe has in fact just been loaded and the Design tab will rehydrate it. Count a
|
||||
// recipe that came from THESE files as geometry.
|
||||
if (tolal_model_count <= 0 && !loaded_cad_recipe && !q->m_exported_file) {
|
||||
//
|
||||
// Only where the Design tab can actually show it, though. With the CAD feature switched off
|
||||
// (or not built) the recipe is still carried through to the next save, but nothing will
|
||||
// display it, so an empty plate needs saying — and saying why.
|
||||
bool cad_can_show = false;
|
||||
#ifdef SLIC3R_CAD
|
||||
cad_can_show = wxGetApp().is_enable_cad_feature();
|
||||
#endif
|
||||
if (tolal_model_count <= 0 && !q->m_exported_file && (!loaded_cad_recipe || !cad_can_show)) {
|
||||
dlg.Hide();
|
||||
if (!is_user_cancel) {
|
||||
MessageDialog msg(wxGetApp().mainframe, _L("The file does not contain any geometry data."), _L("Warning"), wxYES | wxICON_WARNING);
|
||||
const wxString text = loaded_cad_recipe
|
||||
? _L("This project contains a model made in the Design tab and no other geometry. "
|
||||
"Enable \"CAD feature (experimental)\" in Preferences and restart to see and edit it; "
|
||||
"it is kept when the project is saved.")
|
||||
: _L("The file does not contain any geometry data.");
|
||||
MessageDialog msg(wxGetApp().mainframe, text, _L("Warning"), wxYES | wxICON_WARNING);
|
||||
if (msg.ShowModal() == wxID_YES) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "I18N.hpp"
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Format/DRC.hpp"
|
||||
#include "libslic3r/CAD/SketchEngine.hpp"
|
||||
#include <wx/language.h>
|
||||
#include "OG_CustomCtrl.hpp"
|
||||
#include "wx/graphics.h"
|
||||
@@ -1765,13 +1764,6 @@ void PreferencesDialog::create_items()
|
||||
"parametrically. This feature is experimental and still under development."),
|
||||
"enable_cad_feature", _L("(Requires restart)"));
|
||||
g_sizer->Add(item_cad_feature);
|
||||
|
||||
auto item_auto_close_sketch_loops = create_item_checkbox(_L("Auto-close sketch loops"),
|
||||
_L("Treat sketch endpoints within 0.001 mm as one joint and weld the loop shut. "
|
||||
"Off: only exactly coincident endpoints join, so a loop with a tiny gap is "
|
||||
"shown as open instead of being closed for you."),
|
||||
"auto_close_sketch_loops");
|
||||
g_sizer->Add(item_auto_close_sketch_loops);
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
@@ -1861,11 +1853,18 @@ void PreferencesDialog::create_items()
|
||||
"disc with a roll quadrant. A face's orientation is read without being learned. "
|
||||
"Turn this off for the conventional CAD representation."), "design_connector_face_glyph");
|
||||
g_sizer->Add(item_connector_face_glyph);
|
||||
}
|
||||
|
||||
// Push the weld preference into the kernel now so toggling it takes effect without
|
||||
// a restart (the sketch tool also re-pushes on activation, see DesignSketchTool::begin).
|
||||
Slic3r::set_sketch_auto_close(wxGetApp().is_auto_close_sketch_loops());
|
||||
// Saved WITH each design (it decides which loops are closed, i.e. what solid a project
|
||||
// rebuilds into), so it applies to designs started from now on; an open design keeps
|
||||
// the rule it was made with.
|
||||
auto item_auto_close_sketch_loops = create_item_checkbox(_L("Auto-close sketch loops"),
|
||||
_L("Treat sketch endpoints within 0.001 mm as one joint and weld the loop shut. "
|
||||
"Off: only exactly coincident endpoints join, so a loop with a tiny gap is "
|
||||
"shown as open instead of being closed for you. Saved with each design; "
|
||||
"applies to designs started after the change."),
|
||||
"auto_close_sketch_loops");
|
||||
g_sizer->Add(item_auto_close_sketch_loops);
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<wxString> ButtonDragActions = {_L("None"), _L("Pan"), _L("Rotate")};
|
||||
|
||||
@@ -8657,12 +8657,13 @@ TEST_CASE("Hole standards: inch sizes by either name, with their 82° countersin
|
||||
CHECK(doc.features[m].hole_cbore_diameter == 11.0);
|
||||
}
|
||||
|
||||
TEST_CASE("Body colours and names survive a save and a load", "[CadDocument][recipe]")
|
||||
TEST_CASE("Body colours, the modeling origin and the weld rule survive a save and a load", "[CadDocument][recipe]")
|
||||
{
|
||||
CadDocument doc;
|
||||
const int sk = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 10, 10, 0, "S");
|
||||
doc.add_extrude(sk, 5, false, BooleanMode::New, "E");
|
||||
doc.modeling_origin = Vec3d(110, 120, 0);
|
||||
doc.auto_close_loops = false; // the design's weld rule travels with it
|
||||
REQUIRE(doc.recompute());
|
||||
doc.bodies[0].has_color = true;
|
||||
doc.bodies[0].color = ColorRGBA(0.1f, 0.2f, 0.3f, 1.0f);
|
||||
@@ -8675,6 +8676,9 @@ TEST_CASE("Body colours and names survive a save and a load", "[CadDocument][rec
|
||||
CHECK(std::abs(back.bodies[0].color.g() - 0.2f) < 1e-6);
|
||||
CHECK((back.modeling_origin - Vec3d(110, 120, 0)).norm() < 1e-9); // the project's own
|
||||
CHECK(back.origin_from_recipe);
|
||||
CHECK_FALSE(back.auto_close_loops);
|
||||
back.auto_close_loops = true; // leave the kernel's global as the other tests expect it
|
||||
back.recompute();
|
||||
}
|
||||
|
||||
TEST_CASE("A new thread reads its radius as the nominal (major) radius", "[CadDocument][thread]")
|
||||
@@ -8692,3 +8696,26 @@ TEST_CASE("A new thread reads its radius as the nominal (major) radius", "[CadDo
|
||||
CHECK_FALSE(doc.recompute());
|
||||
CHECK(doc.error.find("thread") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_CASE("CadDocument: a profile on a body face touches it, one in free space does not", "[CadDocument]")
|
||||
{
|
||||
// The Extrude default reads this: a profile drawn on a body joins it (charter L6).
|
||||
CadDocument doc;
|
||||
const int base = doc.add_sketch(SketchShape::Rectangle, SketchPlane::XY(), 20, 20, 10, "Base");
|
||||
doc.add_extrude(base, 5.0, false, BooleanMode::New, "Block");
|
||||
REQUIRE(doc.recompute());
|
||||
REQUIRE(doc.bodies.size() == 1);
|
||||
|
||||
SketchPlane top = SketchPlane::XY();
|
||||
top.origin = Vec3d(0, 0, 5); // the block's top face
|
||||
const int on_face = doc.add_sketch(SketchShape::Rectangle, top, 6, 6, 3, "Boss");
|
||||
SketchPlane above = SketchPlane::XY();
|
||||
above.origin = Vec3d(0, 0, 30); // well clear of the block
|
||||
const int in_air = doc.add_sketch(SketchShape::Rectangle, above, 6, 6, 3, "Floating");
|
||||
REQUIRE(doc.recompute());
|
||||
|
||||
CHECK(doc.body_touching_sketch(on_face) == 0);
|
||||
CHECK(doc.body_touching_sketch(in_air) == -1);
|
||||
CHECK(doc.body_touching_sketch(-1) == -1);
|
||||
CHECK(doc.body_touching_sketch(base + 1) == -1); // the extrude: not a sketch
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user