Compare commits

..
Author SHA1 Message Date
Ian Chua 17b0eda7f8 fix: clang-tidy 2026-10-06 21:06:31 +08:00
Ian Chua ec22321a4b fix: move connect_mutex to protected 2026-10-06 21:02:27 +08:00
Ian Chua 5e861b31f9 fix: use-after-free in MoonrakerPrinterAgent teardown 2026-10-06 20:54:47 +08:00
Ian Chua 334ba8f84d chore: reconcile with PR 15710 squash base 2026-10-06 20:05:39 +08:00
Ian Chua 418623e2c5 chore: remove orcasonar test changes
(cherry picked from commit c6d3e755ec)
2026-10-06 20:05:38 +08:00
Ian Chua cf476637d4 refactor: move orcasonar changes to dedicated branch
(cherry picked from commit 797c2c1b33)
2026-10-06 20:05:38 +08:00
Ian Chua ceadec4e2d chore: port add_platform_root_certificates from 16027
(cherry picked from commit 6194c4f8bb)
2026-10-06 20:05:37 +08:00
peachismomo 92b3068cf6 refactor: separate out networking infra
(cherry picked from commit 1cac04eae8)
2026-10-06 20:05:37 +08:00
peachismomo e45f5846b2 fix: printer agent and camera shutdown races
(cherry picked from commit a6cad8a6e5)
2026-10-06 20:05:36 +08:00
Ian Chua f0be15df9e fix: remove redundant earlier expires_never call
(cherry picked from commit 50c5ec2862)
2026-10-06 20:05:36 +08:00
Ian Chua 0ddb86bec7 fix: orca mqtt session timeout after connack
(cherry picked from commit b36a72e16a)
2026-10-06 20:05:35 +08:00
Ian Chua fe30bfe7c7 fix: serialize MQTT ws I/O and dewcouple keepalive
(cherry picked from commit 87eb8de001)
2026-10-06 20:05:35 +08:00
Ian Chua 44675778e2 fix: cancel inflight cloud signaling requests
(cherry picked from commit 220508534a)
2026-10-06 20:05:34 +08:00
Ian Chua b870835b01 fix: clarify OrcaCloud signaling endpoint contract
(cherry picked from commit 5a0810365b)
2026-10-06 20:05:34 +08:00
Ian Chua ea464401be feat: add ssl and ca_cert for OrcaPrinterAgent
(cherry picked from commit fa98ec5afe)
2026-10-06 20:05:34 +08:00
Ian Chua 4540a98c58 feat: add https compatibility for moonraker based printer agents
(cherry picked from commit 936b0bbc17)
2026-10-06 20:05:33 +08:00
Ian Chua 76cf64d6c9 fix: command_ams_refresh_rfid function signature in test_printer_agent.cpp
(cherry picked from commit 5222a37eff)
2026-10-06 20:05:33 +08:00
peachismomo 8d7df2684d fix: compilation after merge
(cherry picked from commit 6da938b660)
2026-10-06 20:05:32 +08:00
Ian Chua 62bd8c3743 fix: refresh token before configuring printer MQTT
(cherry picked from commit 2dcdfe2d32)
2026-10-06 20:05:32 +08:00
Ian Chua 936daee0e6 feat: add ssl compatibility with moonrakerprinteragent
(cherry picked from commit 6c353d9596)
2026-10-06 20:05:31 +08:00
Ian Chua 9a161692b5 fix: snapmakerprinteragent use moonraker's lifecycle-owned command worker
(cherry picked from commit ce0c759ded)
2026-10-06 20:05:31 +08:00
Ian Chua eb071f30f8 fix: re-implement missing commands on BBLPrinterAgent
(cherry picked from commit 9a7e925790)
2026-10-06 20:05:30 +08:00
Ian Chua 931b07364a fix: camera signaling API
(cherry picked from commit d9f1f8b207)
2026-10-06 20:05:30 +08:00
peachismomo 34c2c926d1 fix: load windows roots for cloud MQTT and camera signaling
(cherry picked from commit b91f48141d)
2026-10-06 20:05:29 +08:00
Ian Chua fc56ad002a fix: swtich start print command from http to mqtt command
(cherry picked from commit 5287fd0160)
2026-10-06 20:05:29 +08:00
Ian Chua 7b75fe02c8 fix: remove unimplemented pure virtual functions
(cherry picked from commit a7ab01b815)
2026-10-06 20:05:28 +08:00
Ian Chua 2f6de2ed86 fix: updated stale docs
(cherry picked from commit dcddb9283c)
2026-10-06 20:05:28 +08:00
Ian Chua 172f492b74 Revert "fix: latent ams in use bug"
This reverts commit 910dbcd2e6.

(cherry picked from commit ba9cf6e897)
2026-10-06 20:05:28 +08:00
Ian Chua b74fac92a5 feat: restore printer agent implementations
(cherry picked from commit d1f2ecb903)
2026-10-06 20:05:27 +08:00
Kris Austin e098c933f0 perf: write post-processed G-code without per-line copies to speed up export by up to 6% (#16167)
* perf: write post-processed G-code without a per-line copy

* perf: size the post-process line map from the first pass

* test: line ends of the exported G-code

* test: include the headers the line-ends test and gcode() helper use
2026-10-06 07:50:08 -03:00
HanifKoh d1afb1fed6 Use a System clang-tidy When Available and Make --fix Converge in One Pass (#16199)
* Use a System clang-tidy When Available and Make --fix Converge in One Pass

scripts/run_clang_tidy.sh only looked at CLANG_TIDY and the venv it creates,
so a clang-tidy already on the system was never used. It is now the first
choice: the pinned version outright, another version after a prompt that
says results may differ slightly from CI, which -y and an existing pinned
venv skip.

Two problems in clang_tidy_diff.py made --fix need several runs and still
leave the plain check failing:

- A deleted #include orphans uses on unchanged lines. The plain check runs
  such a file whole and reports them, but --fix kept the line filter to the
  changed lines, so they were never fixed. Fix mode now runs the file whole
  first and then fixes exactly the changed lines plus the lines that run
  found wanting, so unrelated lines are still never rewritten.

- clang-tidy exits non-zero for the findings it just fixed, so every fixed
  file was reported as failed and the user ran --fix again to see what was
  left. A file --fix changed is now checked again and the fixed files are
  listed separately from what --fix could not add.

CI runs the script without --fix and is unchanged.

* Keep the a/ b/ Diff Prefixes Whatever the User's Git Config Says

parse_diff recognises a changed file by its +++ b/ header. With
diff.noprefix or diff.mnemonicPrefix set, git prints +++ src/x.cpp or
+++ w/src/x.cpp instead, every file was dropped, and the local check
reported no changed C++ lines. The diff is now asked for the a/ and b/
prefixes outright, which overrides both settings.

* Warn When No Remote Points at OrcaSlicer/OrcaSlicer

Without one, run_clang_tidy.sh compares against origin/main. When origin
is a fork whose main already holds the commits, the check finds nothing
and says so, without hinting at why. The script now names the base it
fell back to and how to point it at the upstream repository.
2026-10-06 17:16:09 +08:00
yw4z f8dd56053c Match style of height range modifier section on sidebar (#15703)
* init

* update

* rescale layer icon
2026-10-06 11:00:04 +03:00
Kris Austin 1d577ea4e2 build: add the missing includes only a Windows build reports (#16110) 2026-10-05 21:49:32 -03:00
Kris Austin 2bd868ecd0 perf: avoid print config copies to speed up slicing by up to 5% (#16183) 2026-10-05 21:21:13 -03:00
Kris Austin 9e5bfc272c fix: Compare Presets crash on filaments with different variant counts (#16176) 2026-10-05 21:20:12 -03:00
Kiss Lorand fefedb66c4 Fix overlapping internal bridges (#16177) 2026-10-05 21:17:20 -03:00
David Eccles (gringer)andRodrigo Faselli 1dcbb2c02a Fill in truncated octahedron tops (optional setting) (#12541)
Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com>
2026-10-05 20:32:18 -03:00
Rodrigo Faselli b4577dbdc4 Hide smooth factor if there is no infill. (#16172) 2026-10-05 20:27:37 -03:00
Ian BassiandAlexandre Folle de Menezes eb5b9a77b9 Update Translations + String improvements (#16142)
Co-authored-by: Alexandre Folle de Menezes <afmenez@gmail.com>
2026-10-05 14:43:19 -03:00
Kris Austin c1e6de7e4e Fix the clang-tidy check on Windows and for unusual file paths (#16163)
run_clang_tidy.ps1 had not been run on Windows before.

- Run native commands through Invoke-Quiet. Under
  $ErrorActionPreference = "Stop", Windows PowerShell made CMake's
  first stderr line fatal, so configure always failed.
- Pass the --line-filter name with native separators. clang-tidy
  matches it against the end of the file's native path, so on Windows
  every misc-include-cleaner finding was dropped.
- Decode subprocess output as UTF-8 and let stdout replace characters
  it cannot encode. A changed line with text such as 打印 crashed the
  script under cp1252.
- Check VCToolsInstallDir and WindowsSdkDir in VsDevCmd's output
  before applying it, so a failure names the command to run and leaves
  the calling shell untouched.
- Log the git_commit_hash_header build, use -LiteralPath for logs, and
  hide VsDevCmd's stderr as build_win.bat does.

On every platform, git quotes non-ASCII paths and appends a tab to a
+++ header whose path contains a space, and parse_diff dropped both.
changed_files and the workflow's changed-files step now pass
core.quotePath=false, and parse_diff strips the tab.
2026-10-06 00:35:03 +08:00
yw4zandNoisyfox 1496906939 UI Fixes / Improvements (#15074)
* plates-toolbar-scrollbar-size

* update

* filament grouping dialog

* mixed filament list

* Update StepMeshDialog.cpp

* moves plot scrollbar

* fix position of popups

* printer agent combo box width

* match multiline text control background

* add dots to configure button

* also correct label color for multiline input

* side tools connecting text color

* recenter dialog text color

* preferences experimental features

* upgrade panel hyperlink color + right margin

* fix position of + sign on printer selector

* speed control popup

* bbl fan control window

* AMS materials setting

* monitor > ams section

* match background color of multiline text editor on project page

* Fix SwitchButton colors

* bbl camera popup

* BBL > Send print dialog

* Revert "BBL > Send print dialog"

This reverts commit 128e145897.

* Revert "bbl fan control window"

This reverts commit 4a0db62790.

* Revert "bbl camera popup"

This reverts commit e2301e3237.

* Revert "monitor > ams section"

This reverts commit 66e6894eb1.

* Revert "fix position of + sign on printer selector"

This reverts commit 1523ba24c0.

* Revert "upgrade panel hyperlink color + right margin"

This reverts commit 541f2514c5.

* Revert "side tools connecting text color"

This reverts commit a9ab074247.

* Revert "recenter dialog text color"

This reverts commit f4670d32b2.

* Revert "AMS materials setting"

This reverts commit b12827f9b5.

* Revert "Fix SwitchButton colors"

This reverts commit 2d4f1d1fe9.

* match object list background color

* match compare dialog wxDataViewCtrl background color

* fix centering of iconized buttons on linux

* fix compare dialog background color not applied on linux

* edit gcode dialog components background color

* Update Plater.cpp

* fix dev button font size

* Fix scaling issue on SwitchButton while using 150%

* transfer or discard changes dialog wiki label

* "Transfer or Discard changes" / "Unsaved Changes" dialog header color

* object table colors & header spacing

* progress dialog

* fix progressbar look on linux

* fix build

* fix build

* fix centering of iconized icon again

* fix header background color

* revert bbl fan control

* add includes

---------

Co-authored-by: Noisyfox <timemanager.rick@gmail.com>
2026-10-05 19:06:24 +03:00
Alexandre Folle de Menezes 6b8df05d40 Use the appropriate Unicode symbol for °C (Celsius) (#16135) 2026-10-05 11:55:45 -03:00
Alexandre Folle de Menezes 47f1f2de5f Add context to XYZ axis names (#16134) 2026-10-05 10:38:52 -03:00
Terasit Juntarasombut 5dcf89d094 l10n(th): align Publish 3MF and new-feature strings with Thai glossary (#15681) 2026-10-05 10:15:17 -03:00
Alexandre Folle de Menezes 318f6178ab Improve and complement pt-BR translations (#16103) 2026-10-05 10:13:31 -03:00
Surfoo 37dbad5752 i18n(fr): added French strings (#16161) 2026-10-05 10:13:03 -03:00
232 changed files with 207394 additions and 41569 deletions
+1
View File
@@ -61,6 +61,7 @@ CheckOptions:
termios\.h; termios\.h;
[/\\](um|shared)[/\\].*; [/\\](um|shared)[/\\].*;
sal\.h; sal\.h;
tchar\.h;
clipper[/\\]clipper\.hpp; clipper[/\\]clipper\.hpp;
png(lib)?conf\.h; png(lib)?conf\.h;
mcut[/\\]platform\.h; mcut[/\\]platform\.h;
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- name: Look for changed C++ files - name: Look for changed C++ files
id: changes id: changes
run: | run: |
if git diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then if git -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
echo "cpp=true" >> "$GITHUB_OUTPUT" echo "cpp=true" >> "$GITHUB_OUTPUT"
else else
echo "No C++ changes under src/ or tests/." echo "No C++ changes under src/ or tests/."
+1
View File
@@ -90,6 +90,7 @@ See the [Localization guide](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/
- Plural entries: read `nplurals` from the catalog's `Plural-Forms` header (it is **not** always 2 — ja/ko/zh/th/vi use 1, ru/cs/pl/lt use 3, uk uses 4). Each form must be genuinely inflected for its quantity; repeating one sentence across all forms is a bug in Slavic/Baltic languages, though it is correct for Turkish and Hungarian. - Plural entries: read `nplurals` from the catalog's `Plural-Forms` header (it is **not** always 2 — ja/ko/zh/th/vi use 1, ru/cs/pl/lt use 3, uk uses 4). Each form must be genuinely inflected for its quantity; repeating one sentence across all forms is a bug in Slavic/Baltic languages, though it is correct for Turkish and Hungarian.
- An entry whose `msgstr` equals its `msgid` is untranslated even though it is not empty; a plural entry with any empty form is likewise incomplete. - An entry whose `msgstr` equals its `msgid` is untranslated even though it is not empty; a plural entry with any empty form is likewise incomplete.
- Mark machine-produced translations with an `# AI Translated` translator comment. Don't add it to a human translation you didn't actually rewrite. - Mark machine-produced translations with an `# AI Translated` translator comment. Don't add it to a human translation you didn't actually rewrite.
- When you can't be sure of a machine translation's meaning or UI wording, also add `# Needs human review: <what to check>`. Never use `fuzzy` for this — fuzzy entries are hidden from users.
- Don't reflow or re-wrap unrelated entries — keep the diff limited to the strings you changed. - Don't reflow or re-wrap unrelated entries — keep the diff limited to the strings you changed.
### Verifying ### Verifying
@@ -262,9 +262,9 @@ and a band is not a "gesture" in the `CadLevel::Gesture` sense because nothing h
|---|---|---|---|---|---| |---|---|---|---|---|---|
| `Move` | — | `update_hover` / `update_solid_hover` (**non-consuming**, returns false) | — | passthrough | hover only asks for a repaint, `:10022-10025` | | `Move` | — | `update_hover` / `update_solid_hover` (**non-consuming**, returns false) | — | passthrough | hover only asks for a repaint, `:10022-10025` |
| `LeftDown` | orbit may begin | latch press, **return false** | — | consume-or-orbit is the canvas's call | `:10058-10063`; consuming here killed orbit once already | | `LeftDown` | orbit may begin | latch press, **return false** | — | consume-or-orbit is the canvas's call | `:10058-10063`; consuming here killed orbit once already |
| `LeftDrag` > 8 px | — | start + drive band, **consume**, when Shift is held or the left button has no camera action | — | otherwise the left button's drag action (Preferences > Control) | the band is Prepare's Shift+left-drag rectangle selection | | `LeftDrag` > 8 px | — | start + drive band, **consume** | — | no longer orbits in this canvas | middle-drag orbits, right-drag pans (`:10013-10016`) |
| `LeftUp` | — | commit pick **or** resolve band | — | — | `:10035-10041` | | `LeftUp` | — | commit pick **or** resolve band | — | — | `:10035-10041` |
| `MiddleDrag` / `RightDrag` | — | must not see it | — | the button's drag action (Preferences > Control) | camera gestures never reach the FSM; a right press reaches the tool only once its release shows it was a click | | `MiddleDrag` / `RightDrag` | — | must not see it | — | orbit / pan | camera gestures never reach the FSM |
| `RightClick` | — | — | offer menu | — | `snaporca-xmh6` open: a right-click that only clears the sketch selection eats the offer | | `RightClick` | — | — | offer menu | — | `snaporca-xmh6` open: a right-click that only clears the sketch selection eats the offer |
| `Esc` | — | — | `escape()` ladder | — | one route whatever holds focus (`:4228-4231`) | | `Esc` | — | — | `escape()` ladder | — | one route whatever holds focus (`:4228-4231`) |
| `Del` / `Backspace` | — | `delete_selected_or_last_sketch_entity()` | char hook | — | `:4260` | | `Del` / `Backspace` | — | `delete_selected_or_last_sketch_entity()` | char hook | — | `:4260` |
+14 -74
View File
@@ -135,7 +135,7 @@ contract between them is stated in code rather than spread across handlers.
| --- | --- | --- | | --- | --- | --- |
| `Transient` | a value field or a popup menu | closes it; the tool stays armed | | `Transient` | a value field or a popup menu | closes it; the tool stays armed |
| `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched | | `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched |
| `Tool` | a feature card, Sketch waiting for its plane, an armed sketch tool, a constrain session | exits it; drawn entities survive | | `Tool` | a feature card, an armed sketch tool, a constrain session | exits it; drawn entities survive |
| `Idle` | nothing transient | clears the selection, a Feature tree or Bodies row included; leaves a sketch session only if it is empty | | `Idle` | nothing transient | clears the selection, a Feature tree or Bodies row included; leaves a sketch session only if it is empty |
`cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a `cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a
@@ -149,66 +149,26 @@ explicit selection, the sketch ribbon's Cancel, which asks first, or `Ctrl+Z`. A
*session* is deliberately not a `Tool` level; it is the environment the `Idle` level lives in, *session* is deliberately not a `Tool` level; it is the environment the `Idle` level lives in,
which makes the destructive path unrepresentable rather than merely unlikely. which makes the destructive path unrepresentable rather than merely unlikely.
A body Move is the one `Gesture` that outlives the press: its gizmo stays up between drags until
Confirm keeps the placement or `Esc` or Cancel puts the body back. Until then the selection is
held — a click off the gizmo only steers the camera — and undo is refused, since the placement
is not in the history. Anything that starts another edit (a feature card, a sketch, placing
imported art or text, another body's Move, a rebuild) keeps the placement, as switching gizmos
keeps a move in Prepare. The panel ends the Move in one place (`DesignPanel::end_body_move`), so
the gizmo, the Move / Rotate card and the ✓/✗ cannot outlive one another.
Right-click is read at button-up against one budget, 3 px of drift, applied to the whole press Right-click is read at button-up against one budget, 3 px of drift, applied to the whole press
rather than to its end points: a press that wandered past the budget at any moment is rather than to its end points: a press that wandered past the budget at any moment is
navigation, even if it comes back to where it started, which is what stops a slow, careful navigation, even if it comes back to where it started, which is what stops a slow, careful
orbit from ending in a menu. There is no time budget — a gesture that means something different orbit from ending in a menu. There is no time budget — a gesture that means something different
when it is slow is exactly what the interaction charter rules out. The raycast uses the press when it is slow is exactly what the interaction charter rules out. The raycast uses the press
position, not the release. The sketch tool sees a right press only once the release has shown it position, not the release. An armed sketch tool that already consumed the right
was a click: the press itself goes to the camera, which may pan or orbit with that button, and the button (to terminate a chain, say) declines to also open a menu, through a read-and-clear flag.
canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a Past either budget the event is navigation, and navigation does not transition the state
chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation machine.
does not transition the state machine.
Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control
for each button, and in the Touchpad camera style a move with Alt held orbits and one with Shift for each button. The left button is shared with picking, so a whole body is swept with a
held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool rectangle on plain left-drag only while no camera action is assigned to it, and with
handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept Shift+left-drag otherwise — Prepare's own rectangle selection.
with a rectangle on plain left-drag only while no camera action is assigned to the left button,
and with Shift+left-drag otherwise — Prepare's own rectangle selection.
Entering a sketch changes two things at once so the mode is legible: a banner above the Entering a sketch changes three things at once so the mode is legible: a banner above the
canvas (a sibling of the canvas, not a child over it — on GTK a child window over a canvas (a sibling of the canvas, not a child over it — on GTK a child window over a
`wxGLCanvas` is a native window and does not reliably stack over GL), and `N` to look normal to `wxGLCanvas` is a native window and does not reliably stack over GL), the printer bed muted so
the plane. The printer bed stays: there is no sketch grid, so the plate grid is the only ground a plate grid is never read as a sketch grid, and `N` to look normal to the plane. Code that
reference a sketch has. Code that changes either belongs with a change to this section. changes any of the three belongs with a change to this section.
Sketch mode is never entered without a plane under it, so the banner, the sketch keys and the
sketch offer always have a session to act on. Sketch on a picked flat face or reference plane opens
the session on it at once. With nothing picked it stays in Feature mode and waits for one — an
armed `Tool`, left with `Esc` or ✗, and ended by anything that starts another edit — and the
reference plane or flat face clicked next opens the session. A picked plane is a selection like a
face: the sketch on it uses it up, and `Esc` or a click on nothing lets go of it, so a plane that
can no longer be seen never decides where the next sketch goes.
The reference planes — XY, XZ and YZ through the modeling origin, with their half-axes — are
drawn on demand, because three translucent squares over every model are noise once they are not
the thing being picked. Sketch brings them up while it waits for a plane, which is exactly when
they are picked, and the session the pick opens takes them away; a live session draws none. The
Feature tree's Origin row keeps them up outside a sketch. Its state is a view preference in
AppConfig rather than part of the recipe, so it costs the project format nothing. The Plane tool
keeps its own rule: the planes and the datums as Offset bases, and nothing for the other methods,
where a click on a plane would rewrite the datum's references. The `P` and `A` keys are a
separate, unpickable view helper and do not follow the Origin row.
The Bed row, under the Origin row, is the printer bed's switch in the same way: it draws or hides
the bed and its plate grid in every mode. It is a view preference in AppConfig too, and the bed is
shown until it is turned off.
The two rows are view switches, not history, and the tree says so: they sit unframed on the Feature
tree's card, above the features' own framed list, and stay put while the features scroll. A click
never selects either row, since a selected Origin or Bed would have nothing to edit, move or
delete; the eye and the right-click menu are the only targets, and a row's label dims while its
thing is hidden, as a hidden body's does. Because the block never takes the focus, `Ctrl+Shift+O`
and `Ctrl+Shift+B` flip the Origin and the Bed from the keyboard.
## Rendering the bodies ## Rendering the bodies
@@ -229,15 +189,6 @@ degenerate edges are left out (`GeometryEngine::display_edges`), and the polylin
once per shape, keyed by its `TShape`, because a recompute that leaves a body unchanged is the once per shape, keyed by its `TShape`, because a recompute that leaves a body unchanged is the
common case. common case.
While a feature card is open, its preview ghost is the whole model the candidate would produce,
drawn translucent over the bodies, so every face the feature leaves alone is in both at the same
depth. The ghost is drawn with a depth bias that pushes it back (`GLVolume::depth_bias`), so on a shared face
the body always wins instead of the two copies z-fighting, and the ghost shows only where the
result reaches past the bodies. Material a feature removes lies inside the old solid and would not
show at all, so the tools whose result mostly coincides with the body — Fillet/Chamfer, Draft,
Hole and the Mate hover — hide the bodies once the preview is valid and draw the result alone,
opaque.
## Showing what is selected ## Showing what is selected
A selection is drawn on the faces it names, never as a tint over the body: a translucent A selection is drawn on the faces it names, never as a tint over the body: a translucent
@@ -263,8 +214,7 @@ replay costs up to a recompute, so the panel finds the faces once per row and to
generation, off the UI thread, and only while no feature card is open. One selection is live at generation, off the UI thread, and only while no feature card is open. One selection is live at
a time: a viewport pick clears the feature row and a feature row clears the viewport pick, as the a time: a viewport pick clears the feature row and a feature row clears the viewport pick, as the
Feature tree and Bodies list do between themselves. `Esc`, a click on empty space and an Feature tree and Bodies list do between themselves. `Esc`, a click on empty space and an
empty rubber band all let go of it, whichever list or pick made it — except while a body Move is empty rubber band all let go of it, whichever list or pick made it.
open, which holds the selection until it ends (see the interaction contract).
## Following the app ## Following the app
@@ -282,18 +232,8 @@ The tab is a page of Orca's main window and answers to the same settings as Prep
constraint-row buttons are Orca's self-painted `Button`, because a native button cannot take a constraint-row buttons are Orca's self-painted `Button`, because a native button cannot take a
hover background on macOS. The Feature tree and Bodies lists are a custom-drawn hover background on macOS. The Feature tree and Bodies lists are a custom-drawn
`DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit, `DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit,
Show/hide and Delete on a feature, Move, Show/hide and Delete on a body, and only Show/hide on Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows
the Origin and Bed rows, a separate non-selectable list above the features — and the eye shows
whether that row is hidden. whether that row is hidden.
- **Plates.** This is the one thing the tab does not follow. The canvas has a bed of its own at
the printer bed's home position, whichever plate Prepare has current, and a new document's
modeling origin is that bed's centre. A bed that followed the current plate would slide out
from under a design: the origin is fixed once per document, baked into every sketch plane and
saved in the recipe, while the current plate can change between visits. Commit to Plate does
not need it either, since the committed object is placed on an empty spot of the current
plate. What the canvas does read from the plate is moved onto its bed: the exclude areas, the
plate box the camera orbits about when nothing is picked (`GLCanvas3D::_current_plate_box`),
and the first view, which starts as a copy of Prepare's camera.
- **Viewport text.** The status line and the active tool's values are drawn by the canvas in - **Viewport text.** The status line and the active tool's values are drawn by the canvas in
its ImGui pass, so they go with the canvas: a top-level window over GL does not follow its its ImGui pass, so they go with the canvas: a top-level window over GL does not follow its
frame and was left floating over other applications. frame and was left floating over other applications.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+61 -2
View File
@@ -52,8 +52,6 @@ src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.h
src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.cpp
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.h
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackNozzleItem.cpp
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.h
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRackUpdate.cpp
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h
@@ -301,3 +299,64 @@ src/slic3r/GUI/Gizmos/GLGizmoPrimitive.cpp
src/slic3r/GUI/Gizmos/GLGizmoSketch.cpp src/slic3r/GUI/Gizmos/GLGizmoSketch.cpp
src/slic3r/GUI/KeyChord.cpp src/slic3r/GUI/KeyChord.cpp
src/slic3r/GUI/Shortcuts.cpp src/slic3r/GUI/Shortcuts.cpp
src/libslic3r/CAD/CadDocument.cpp
src/libslic3r/Support/TreeSupport3D.cpp
src/slic3r/Config/Snapshot.cpp
src/slic3r/GUI/AmsMappingPopupUpdate.cpp
src/slic3r/GUI/AuxiliaryDataViewModel.cpp
src/slic3r/GUI/AuxiliaryDialog.cpp
src/slic3r/GUI/BBLStatusBarPrint.cpp
src/slic3r/GUI/BBLStatusBarSend.cpp
src/slic3r/GUI/BaseTransparentDPIFrame.cpp
src/slic3r/GUI/CloneDialog.cpp
src/slic3r/GUI/ColorDecomposeDialog.cpp
src/slic3r/GUI/ColorDecomposeSupport.cpp
src/slic3r/GUI/ConfigWizard.cpp
src/slic3r/GUI/DeviceCore/DevCalib.cpp
src/slic3r/GUI/DeviceCore/DevUpgrade.cpp
src/slic3r/GUI/DeviceTab/uiAMSBestPositionPopup.cpp
src/slic3r/GUI/DeviceTab/wgtMsgPanel.cpp
src/slic3r/GUI/DragDropPanel.cpp
src/slic3r/GUI/GLCanvas3D.hpp
src/slic3r/GUI/GUI_Utils.cpp
src/slic3r/GUI/Gizmos/GLGizmoEmboss.hpp
src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.cpp
src/slic3r/GUI/Gizmos/GLGizmoTextureDisplacement.hpp
src/slic3r/GUI/Gizmos/GizmoObjectManipulation.hpp
src/slic3r/GUI/Jobs/SLAImportDialog.hpp
src/slic3r/GUI/Jobs/TextureDisplacementBakeJob.cpp
src/slic3r/GUI/Jobs/TextureDisplacementDebugJob.cpp
src/slic3r/GUI/Jobs/TextureDisplacementPrepareJob.cpp
src/slic3r/GUI/MixedFilamentDialog.cpp
src/slic3r/GUI/ModelMall.cpp
src/slic3r/GUI/OAuthDialog.cpp
src/slic3r/GUI/PluginPickerDialog.cpp
src/slic3r/GUI/PluginsConfigDialog.cpp
src/slic3r/GUI/PluginsDialog.cpp
src/slic3r/GUI/PublishSettingsDialog.hpp
src/slic3r/GUI/PurgeModeDialog.cpp
src/slic3r/GUI/TerminalDialog.cpp
src/slic3r/GUI/TextureImportDialog.cpp
src/slic3r/GUI/TextureLibrary.cpp
src/slic3r/GUI/TextureProjectorFrame.cpp
src/slic3r/GUI/UVEditorCanvas.cpp
src/slic3r/GUI/UserManager.cpp
src/slic3r/GUI/WebDownPluginDlg.cpp
src/slic3r/GUI/WebViewDialog.cpp
src/slic3r/GUI/Widgets/AMSItem.cpp
src/slic3r/GUI/Widgets/DialogButtons.cpp
src/slic3r/Utils/NetworkAgentFactory.cpp
src/slic3r/plugin/PluginAuditManager.cpp
src/slic3r/plugin/PluginConfig.cpp
src/slic3r/plugin/PluginResolver.cpp
src/libslic3r/CAD/SketchEngine.cpp
src/libslic3r/CAD/GeometryEngine.cpp
src/slic3r/GUI/CAD/DesignOffer.hpp
src/slic3r/GUI/CAD/DesignSketchTool.cpp
src/slic3r/GUI/EncodedFilament.cpp
src/slic3r/GUI/HttpServer.cpp
src/slic3r/GUI/Plater.hpp
src/slic3r/GUI/PostProcessor.cpp
src/slic3r/GUI/TaskManager.cpp
src/slic3r/GUI/Widgets/ProgressDialog.hpp
src/slic3r/GUI/Widgets/StepCtrl.cpp
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -1,4 +1 @@
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M10,0h0c5.52,0,10,4.48,10,10h0c0,5.52-4.48,10-10,10h0C4.48,20,0,15.52,0,10h0C0,4.48,4.48,0,10,0Z" style="fill:#009688; isolation:isolate; opacity:.5;"/><polyline points="5.5 10.5 8.62 13.38 14.5 7.5" style="fill:none; stroke:#fff; stroke-linecap:round; stroke-linejoin:round;"/></svg>
<rect width="20" height="20" rx="10" fill="#00AF42" fill-opacity="0.2"/>
<path d="M14.0828 8.14395L9.08284 13.144C9.0393 13.1877 8.98755 13.2223 8.93057 13.246C8.87359 13.2696 8.81251 13.2818 8.75081 13.2818C8.68912 13.2818 8.62803 13.2696 8.57105 13.246C8.51408 13.2223 8.46233 13.1877 8.41878 13.144L6.23128 10.9565C6.18768 10.9129 6.15309 10.8611 6.12949 10.8041C6.1059 10.7471 6.09375 10.6861 6.09375 10.6244C6.09375 10.5628 6.1059 10.5017 6.12949 10.4447C6.15309 10.3878 6.18768 10.336 6.23128 10.2924C6.27488 10.2488 6.32665 10.2142 6.38362 10.1906C6.44059 10.167 6.50165 10.1549 6.56331 10.1549C6.62498 10.1549 6.68604 10.167 6.74301 10.1906C6.79998 10.2142 6.85174 10.2488 6.89534 10.2924L8.7512 12.1482L13.4196 7.48067C13.5076 7.39261 13.6271 7.34314 13.7516 7.34314C13.8761 7.34314 13.9956 7.39261 14.0836 7.48067C14.1717 7.56873 14.2212 7.68817 14.2212 7.8127C14.2212 7.93724 14.1717 8.05667 14.0836 8.14473L14.0828 8.14395Z" fill="#00AF42" stroke="#00AF42" stroke-width="0.833333"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 398 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9 14 9 22 15 10 15Z"/><path d="M2 9V11L10 17H22V15M10 15V17"/><path d="M6 9 14 15M10 9 18 15M6 12H18"/></svg>

Before

Width:  |  Height:  |  Size: 301 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3.15 19.57V4.43A1.28 1.28 0 0 1 4.43 3.15H17.05L20.85 6.95V19.57A1.28 1.28 0 0 1 19.57 20.85H4.43A1.28 1.28 0 0 1 3.15 19.57ZM8.2 3.15V5.68A1.28 1.28 0 0 0 9.48 6.95H14.52A1.28 1.28 0 0 0 15.8 5.68V3.15M17.05 20.85V15.8A1.28 1.28 0 0 0 15.8 14.53H8.2A1.28 1.28 0 0 0 6.95 15.8V20.85"/></svg>

Before

Width:  |  Height:  |  Size: 481 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.08,10.74 8.08,5.4M8.08,10.74 15.55,4.87M17.15,10.2 15.55,4.87M4.88,8.6 8.62,20.87M17.15,10.2 20.89,16.07M8.08,10.74 12.78,15.54 17.15,10.2M17.15,19.8 12.78,15.54 8.62,20.87M4.88,8.6 8.08,10.74 17.15,10.2 19.82,7M8.08,10.74 8.62,20.87M17.15,10.2 17.15,19.8" fill="none" stroke="#949494" stroke-width="0.6" stroke-linecap="round" stroke-linejoin="round"/><polygon points="4.88,8.6 8.08,5.4 15.55,4.87 19.82,7 20.89,16.07 17.15,19.8 8.62,20.87 4.35,17.14" fill="none" stroke="#949494" stroke-width="0.85" stroke-linejoin="round"/><polygon points="2.84,4.68 7.32,4.68 7.32,2.84 11.29,5.86 7.32,8.88 7.32,7.04 2.84,7.04" style="fill:#009688;stroke:#009688;stroke-width:0.4;stroke-linejoin:round"/></svg>

Before

Width:  |  Height:  |  Size: 794 B

-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3.15" y="3.15" width="17.7" height="17.7" rx="1.28"/><path d="M7.6 12H16.4M12 7.6V16.4"/></svg>

Before

Width:  |  Height:  |  Size: 284 B

+1 -1
View File
@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 1.5 23 23"><path d="M3.0 9.0 L16.0 9.0 L16.0 21.0 L3.0 21.0 ZM3.0 9.0 L7.0 5.0 L20.0 5.0 L16.0 9.0M20.0 5.0 L20.0 17.0 L16.0 21.0" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.168410256410256 13.423897435897436V13.868410256410256Q5.908923076923077 13.744307692307691 5.678769230769231 13.683384615384615Q5.448615384615385 13.622461538461538 5.23425641025641 13.622461538461538Q4.861948717948718 13.622461538461538 4.66 13.766871794871793Q4.458051282051283 13.91128205128205 4.458051282051283 14.177538461538461Q4.458051282051283 14.400923076923076 4.592307692307693 14.514871794871794Q4.726564102564103 14.628820512820512 5.101128205128205 14.69876923076923L5.3764102564102565 14.755179487179486Q5.886358974358975 14.852205128205128 6.128923076923078 15.09702564102564Q6.37148717948718 15.341846153846152 6.37148717948718 15.75251282051282Q6.37148717948718 16.242153846153844 6.043179487179488 16.49487179487179Q5.7148717948717955 16.747589743589742 5.080820512820513 16.747589743589742Q4.841641025641026 16.747589743589742 4.572000000000001 16.693435897435897Q4.302358974358975 16.639282051282052 4.013538461538462 16.53323076923077V16.063897435897434Q4.291076923076924 16.219589743589744 4.557333333333334 16.2985641025641Q4.823589743589745 16.37753846153846 5.080820512820513 16.37753846153846Q5.471179487179487 16.37753846153846 5.683282051282052 16.224102564102562Q5.895384615384616 16.070666666666664 5.895384615384616 15.786358974358974Q5.895384615384616 15.538153846153845 5.743076923076924 15.398256410256408Q5.590769230769231 15.258358974358973 5.243282051282051 15.188410256410256L4.965743589743591 15.134256410256409Q4.455794871794872 15.032717948717949 4.227897435897436 14.816102564102565Q4.000000000000001 14.599487179487179 4.000000000000001 14.213641025641024Q4.000000000000001 13.766871794871793 4.314769230769231 13.509641025641024Q4.629538461538462 13.252410256410256 5.182358974358975 13.252410256410256Q5.419282051282051 13.252410256410256 5.665230769230769 13.29528205128205Q5.911179487179488 13.338153846153846 6.168410256410256 13.423897435897436ZM6.615179487179487 13.313333333333333H9.46502564102564V13.696923076923076H8.269128205128204V16.682153846153845H7.811076923076923V13.696923076923076H6.615179487179487ZM9.905025641025642 13.313333333333333H12.035076923076925V13.696923076923076H10.360820512820514V14.69425641025641H11.965128205128206V15.077846153846153H10.360820512820514V16.2985641025641H12.075692307692309V16.682153846153845H9.905025641025642ZM13.280615384615384 13.687897435897435V14.953743589743588H13.85374358974359Q14.171897435897435 14.953743589743588 14.345641025641026 14.78902564102564Q14.519384615384615 14.624307692307692 14.519384615384615 14.319692307692307Q14.519384615384615 14.017333333333333 14.345641025641026 13.852615384615383Q14.171897435897435 13.687897435897435 13.85374358974359 13.687897435897435ZM12.824820512820512 13.313333333333333H13.85374358974359Q14.420102564102564 13.313333333333333 14.710051282051282 13.569435897435897Q15.0 13.82553846153846 15.0 14.319692307692307Q15.0 14.818358974358974 14.710051282051282 15.073333333333332Q14.420102564102564 15.32830769230769 13.85374358974359 15.32830769230769H13.280615384615384V16.682153846153845H12.824820512820512Z" fill="#949494" stroke="none"/><polygon points="2.72,5.99 7.02,5.99 7.02,4.22 10.82,7.12 7.02,10.01 7.02,8.25 2.72,8.25" style="fill:#009688;stroke:#009688;stroke-width:0.38;stroke-linejoin:round"/></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3.0 9.0 L16.0 9.0 L16.0 21.0 L3.0 21.0 ZM3.0 9.0 L7.0 5.0 L20.0 5.0 L16.0 9.0M20.0 5.0 L20.0 17.0 L16.0 21.0" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.168410256410256 13.423897435897436V13.868410256410256Q5.908923076923077 13.744307692307691 5.678769230769231 13.683384615384615Q5.448615384615385 13.622461538461538 5.23425641025641 13.622461538461538Q4.861948717948718 13.622461538461538 4.66 13.766871794871793Q4.458051282051283 13.91128205128205 4.458051282051283 14.177538461538461Q4.458051282051283 14.400923076923076 4.592307692307693 14.514871794871794Q4.726564102564103 14.628820512820512 5.101128205128205 14.69876923076923L5.3764102564102565 14.755179487179486Q5.886358974358975 14.852205128205128 6.128923076923078 15.09702564102564Q6.37148717948718 15.341846153846152 6.37148717948718 15.75251282051282Q6.37148717948718 16.242153846153844 6.043179487179488 16.49487179487179Q5.7148717948717955 16.747589743589742 5.080820512820513 16.747589743589742Q4.841641025641026 16.747589743589742 4.572000000000001 16.693435897435897Q4.302358974358975 16.639282051282052 4.013538461538462 16.53323076923077V16.063897435897434Q4.291076923076924 16.219589743589744 4.557333333333334 16.2985641025641Q4.823589743589745 16.37753846153846 5.080820512820513 16.37753846153846Q5.471179487179487 16.37753846153846 5.683282051282052 16.224102564102562Q5.895384615384616 16.070666666666664 5.895384615384616 15.786358974358974Q5.895384615384616 15.538153846153845 5.743076923076924 15.398256410256408Q5.590769230769231 15.258358974358973 5.243282051282051 15.188410256410256L4.965743589743591 15.134256410256409Q4.455794871794872 15.032717948717949 4.227897435897436 14.816102564102565Q4.000000000000001 14.599487179487179 4.000000000000001 14.213641025641024Q4.000000000000001 13.766871794871793 4.314769230769231 13.509641025641024Q4.629538461538462 13.252410256410256 5.182358974358975 13.252410256410256Q5.419282051282051 13.252410256410256 5.665230769230769 13.29528205128205Q5.911179487179488 13.338153846153846 6.168410256410256 13.423897435897436ZM6.615179487179487 13.313333333333333H9.46502564102564V13.696923076923076H8.269128205128204V16.682153846153845H7.811076923076923V13.696923076923076H6.615179487179487ZM9.905025641025642 13.313333333333333H12.035076923076925V13.696923076923076H10.360820512820514V14.69425641025641H11.965128205128206V15.077846153846153H10.360820512820514V16.2985641025641H12.075692307692309V16.682153846153845H9.905025641025642ZM13.280615384615384 13.687897435897435V14.953743589743588H13.85374358974359Q14.171897435897435 14.953743589743588 14.345641025641026 14.78902564102564Q14.519384615384615 14.624307692307692 14.519384615384615 14.319692307692307Q14.519384615384615 14.017333333333333 14.345641025641026 13.852615384615383Q14.171897435897435 13.687897435897435 13.85374358974359 13.687897435897435ZM12.824820512820512 13.313333333333333H13.85374358974359Q14.420102564102564 13.313333333333333 14.710051282051282 13.569435897435897Q15.0 13.82553846153846 15.0 14.319692307692307Q15.0 14.818358974358974 14.710051282051282 15.073333333333332Q14.420102564102564 15.32830769230769 13.85374358974359 15.32830769230769H13.280615384615384V16.682153846153845H12.824820512820512Z" fill="#949494" stroke="none"/></svg>

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

+1 -3
View File
@@ -1,3 +1 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path d="M4.86,13.27L14.86,5.02c.37-.3.9-.3,1.27,0l10,8.24c.23.19.36.47.36.77v12.53c0,.55-.45,1-1,1h-5c-.55,0-1-.45-1-1v-7c0-.55-.45-1-1-1h-6c-.55,0-1,.45-1,1v7c0,.55-.45,1-1,1h-5c-.55,0-1-.45-1-1v-12.53c0-.3.13-.58.36-.77Z" style="fill:#009688; isolation:isolate; opacity:.5;"/><path d="M4.86,13.27L14.86,5.02c.37-.3.9-.3,1.27,0l10,8.24c.23.19.36.47.36.77v12.53c0,.55-.45,1-1,1h-5c-.55,0-1-.45-1-1v-7c0-.55-.45-1-1-1h-6c-.55,0-1,.45-1,1v7c0,.55-.45,1-1,1h-5c-.55,0-1-.45-1-1v-12.53c0-.3.13-.58.36-.77Z" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/></svg>
<path d="M28.0424 15.4264C28.6059 15.3044 28.7112 14.8962 28.2789 14.5156L16.5815 4.28496C16.1473 3.90625 15.438 3.9044 15.0038 4.28311L3.26759 14.5156C2.83346 14.8943 2.93876 15.3026 3.5022 15.4245L4.90989 15.7275C5.47333 15.8494 5.93332 16.4202 5.93332 16.9966V26.5216C5.93332 27.0979 6.4044 27.569 6.98077 27.569H11.9354C12.5117 27.569 12.9828 27.0979 12.9828 26.5216V21.9512C12.9828 21.3748 13.4539 20.9038 14.0303 20.9038H17.3721C17.9485 20.9038 18.4196 21.3748 18.4196 21.9512V26.5216C18.4196 27.0979 18.8907 27.569 19.467 27.569H24.5657C25.1421 27.569 25.6132 27.0979 25.6132 26.5216V16.9966C25.6132 16.4202 26.0732 15.8494 26.6366 15.7293L28.0424 15.4264Z" fill="#00AE42" fill-opacity="0.2" stroke="#00AE42" stroke-width="0.784708" stroke-miterlimit="10" stroke-linecap="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 891 B

After

Width:  |  Height:  |  Size: 696 B

+1 -4
View File
@@ -1,4 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M3.39,15.61C-2,6.97,4.47.5,18.5.5c0,14.03-6.47,20.5-15.11,15.11Z" style="fill:#009688; isolation:isolate; opacity:.5;"/><path d="M3.39,15.61C-2,6.97,4.47.5,18.5.5c0,14.03-6.47,20.5-15.11,15.11Z" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round; stroke-width:1.08px;"/><line x1=".5" y1="18.5" x2="15.5" y2="3.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><line x1="5.5" y1="13.5" x2="10.5" y2="14.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><line x1="4.5" y1="8.5" x2="5.5" y2="13.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><line x1="7.5" y1="5.5" x2="8.5" y2="10.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><line x1="11.5" y1="3.5" x2="12.5" y2="6.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><line x1="8.5" y1="10.5" x2="13.5" y2="11.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/><line x1="12.5" y1="6.5" x2="15.5" y2="7.5" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/></svg>
<path opacity="0.2" d="M5.98183 18.0178C1.49214 10.5356 7.48183 3.05343 20.1981 3.80156C20.9462 16.5216 13.464 22.5075 5.98183 18.0178Z" fill="#00AF42"/>
<path d="M20.9489 3.75655C20.9381 3.57307 20.8604 3.39991 20.7305 3.26994C20.6005 3.13998 20.4273 3.06226 20.2439 3.05155C13.1067 2.63249 7.38979 4.78124 4.95229 8.81249C4.10738 10.1915 3.69103 11.7903 3.75604 13.4062C3.80948 14.8987 4.24448 16.4062 5.04885 17.8922L3.21979 19.7203C3.07906 19.861 3 20.0519 3 20.2509C3 20.4499 3.07906 20.6408 3.21979 20.7815C3.36052 20.9223 3.55139 21.0013 3.75042 21.0013C3.94944 21.0013 4.14031 20.9223 4.28104 20.7815L6.10917 18.9525C7.59417 19.7559 9.1026 20.1909 10.5942 20.2444C10.6985 20.2481 10.8026 20.25 10.9064 20.25C12.4174 20.254 13.8997 19.8379 15.1879 19.0481C19.2192 16.6106 21.3689 10.8947 20.9489 3.75655ZM14.4145 17.7656C12.2817 19.0575 9.75698 19.0781 7.22292 17.8378L15.532 9.52967C15.6017 9.45999 15.6569 9.37727 15.6947 9.28622C15.7324 9.19518 15.7518 9.0976 15.7518 8.99905C15.7518 8.9005 15.7324 8.80292 15.6947 8.71188C15.6569 8.62083 15.6017 8.53811 15.532 8.46842C15.4623 8.39874 15.3796 8.34347 15.2885 8.30575C15.1975 8.26804 15.0999 8.24863 15.0014 8.24863C14.9028 8.24863 14.8052 8.26804 14.7142 8.30575C14.6231 8.34347 14.5404 8.39874 14.4707 8.46842L6.1626 16.7812C4.92604 14.25 4.94385 11.7187 6.23479 9.58967C8.30573 6.17061 13.2285 4.29842 19.4826 4.52155C19.7067 10.7709 17.8335 15.6947 14.4145 17.7656Z" fill="#00AF42"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

+2 -2
View File
@@ -2,8 +2,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 9.51613C0 9.78336 0.237026 10 0.529412 10C3.55073 10 6 7.76142 6 5C6 2.23858 3.55073 0 0.529412 0C0.237026 0 0 0.216637 0 0.483871C0 0.751105 0.237026 0.967742 0.529412 0.967742C2.96596 0.967742 4.94118 2.77305 4.94118 5C4.94118 7.22695 2.96596 9.03226 0.529412 9.03226C0.237026 9.03226 0 9.24889 0 9.51613Z" fill="url(#paint0_linear_46765_16184)"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M0 9.51613C0 9.78336 0.237026 10 0.529412 10C3.55073 10 6 7.76142 6 5C6 2.23858 3.55073 0 0.529412 0C0.237026 0 0 0.216637 0 0.483871C0 0.751105 0.237026 0.967742 0.529412 0.967742C2.96596 0.967742 4.94118 2.77305 4.94118 5C4.94118 7.22695 2.96596 9.03226 0.529412 9.03226C0.237026 9.03226 0 9.24889 0 9.51613Z" fill="url(#paint0_linear_46765_16184)"/>
<defs> <defs>
<linearGradient id="paint0_linear_46765_16184" x1="0.622561" y1="9.52006" x2="5.48816e-08" y2="0.52972" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_46765_16184" x1="0.622561" y1="9.52006" x2="5.48816e-08" y2="0.52972" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/> <stop stop-color="#009688"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/> <stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 758 B

After

Width:  |  Height:  |  Size: 758 B

+2 -2
View File
@@ -2,8 +2,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 9.51613C0 9.78336 0.237026 10 0.529412 10C3.55073 10 6 7.76142 6 5C6 2.23858 3.55073 0 0.529412 0C0.237026 0 0 0.216637 0 0.483871C0 0.751105 0.237026 0.967742 0.529412 0.967742C2.96596 0.967742 4.94118 2.77305 4.94118 5C4.94118 7.22695 2.96596 9.03226 0.529412 9.03226C0.237026 9.03226 0 9.24889 0 9.51613Z" fill="url(#paint0_linear_46765_16184)"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M0 9.51613C0 9.78336 0.237026 10 0.529412 10C3.55073 10 6 7.76142 6 5C6 2.23858 3.55073 0 0.529412 0C0.237026 0 0 0.216637 0 0.483871C0 0.751105 0.237026 0.967742 0.529412 0.967742C2.96596 0.967742 4.94118 2.77305 4.94118 5C4.94118 7.22695 2.96596 9.03226 0.529412 9.03226C0.237026 9.03226 0 9.24889 0 9.51613Z" fill="url(#paint0_linear_46765_16184)"/>
<defs> <defs>
<linearGradient id="paint0_linear_46765_16184" x1="0.622561" y1="9.52006" x2="0" y2="0.52972" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_46765_16184" x1="0.622561" y1="9.52006" x2="0" y2="0.52972" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/> <stop stop-color="#009688"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/> <stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 748 B

After

Width:  |  Height:  |  Size: 748 B

+2 -2
View File
@@ -2,8 +2,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M0.483871 0C0.216637 0 0 0.237026 0 0.529412C0 3.55073 2.23858 6 5 6C7.76142 6 10 3.55073 10 0.529412C10 0.237026 9.78336 0 9.51613 0C9.24889 0 9.03226 0.237026 9.03226 0.529412C9.03226 2.96596 7.22695 4.94118 5 4.94118C2.77305 4.94118 0.967742 2.96596 0.967742 0.529412C0.967742 0.237026 0.751105 0 0.483871 0Z" transform="translate(-2, 2)" fill="url(#paint0_linear_46765_16184)"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M0.483871 0C0.216637 0 0 0.237026 0 0.529412C0 3.55073 2.23858 6 5 6C7.76142 6 10 3.55073 10 0.529412C10 0.237026 9.78336 0 9.51613 0C9.24889 0 9.03226 0.237026 9.03226 0.529412C9.03226 2.96596 7.22695 4.94118 5 4.94118C2.77305 4.94118 0.967742 2.96596 0.967742 0.529412C0.967742 0.237026 0.751105 0 0.483871 0Z" transform="translate(-2, 2)" fill="url(#paint0_linear_46765_16184)"/>
<defs> <defs>
<linearGradient id="paint0_linear_46765_16184" x1="0.52972" y1="0.622561" x2="9.52006" y2="0.622561" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_46765_16184" x1="0.52972" y1="0.622561" x2="9.52006" y2="0.622561" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/> <stop stop-color="#009688"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/> <stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 785 B

+2 -2
View File
@@ -2,8 +2,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 0.483871C6 0.216637 5.76297 0 5.47059 0C2.44927 0 0 2.23858 0 5C0 7.76142 2.44927 10 5.47059 10C5.76297 10 6 9.78336 6 9.51613C6 9.24889 5.76297 9.03226 5.47059 9.03226C3.03404 9.03226 1.05882 7.22695 1.05882 5C1.05882 2.77305 3.03404 0.967742 5.47059 0.967742C5.76297 0.967742 6 0.751105 6 0.483871Z" fill="url(#paint0_linear_46765_16184)"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M6 0.483871C6 0.216637 5.76297 0 5.47059 0C2.44927 0 0 2.23858 0 5C0 7.76142 2.44927 10 5.47059 10C5.76297 10 6 9.78336 6 9.51613C6 9.24889 5.76297 9.03226 5.47059 9.03226C3.03404 9.03226 1.05882 7.22695 1.05882 5C1.05882 2.77305 3.03404 0.967742 5.47059 0.967742C5.76297 0.967742 6 0.751105 6 0.483871Z" fill="url(#paint0_linear_46765_16184)"/>
<defs> <defs>
<linearGradient id="paint0_linear_46765_16184" x1="5.37744" y1="0.47994" x2="6" y2="9.47028" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_46765_16184" x1="5.37744" y1="0.47994" x2="6" y2="9.47028" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/> <stop stop-color="#009688"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/> <stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 740 B

After

Width:  |  Height:  |  Size: 740 B

+2 -2
View File
@@ -2,8 +2,8 @@
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.51613 6C9.78336 6 10 5.76297 10 5.47059C10 2.44927 7.76142 0 5 0C2.23858 0 0 2.44927 0 5.47059C0 5.76297 0.216637 6 0.483871 6C0.751105 6 0.967742 5.76297 0.967742 5.47059C0.967742 3.03404 2.77305 1.05882 5 1.05882C7.22695 1.05882 9.03226 3.03404 9.03226 5.47059C9.03226 5.76297 9.24889 6 9.51613 6Z" transform="translate(-4, -2)" fill="url(#paint0_linear_46765_16184)"/> <path fill-rule="evenodd" clip-rule="evenodd" d="M9.51613 6C9.78336 6 10 5.76297 10 5.47059C10 2.44927 7.76142 0 5 0C2.23858 0 0 2.44927 0 5.47059C0 5.76297 0.216637 6 0.483871 6C0.751105 6 0.967742 5.76297 0.967742 5.47059C0.967742 3.03404 2.77305 1.05882 5 1.05882C7.22695 1.05882 9.03226 3.03404 9.03226 5.47059C9.03226 5.76297 9.24889 6 9.51613 6Z" transform="translate(-4, -2)" fill="url(#paint0_linear_46765_16184)"/>
<defs> <defs>
<linearGradient id="paint0_linear_46765_16184" x1="9.47028" y1="5.37744" x2="0.47994" y2="5.37744" gradientUnits="userSpaceOnUse"> <linearGradient id="paint0_linear_46765_16184" x1="9.47028" y1="5.37744" x2="0.47994" y2="5.37744" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/> <stop stop-color="#009688"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/> <stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient> </linearGradient>
</defs> </defs>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 775 B

+1 -4
View File
@@ -1,4 +1 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> <?xml version="1.0" encoding="UTF-8"?><svg id="a" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M2.5.5c-.55,0-1,.45-1,1v5c0,9,8.5,12,8.5,12,0,0,8.5-3,8.5-12V1.5c0-.55-.45-1-1-1H2.5Z" style="fill:#009688; isolation:isolate; opacity:.5;"/><path d="M2.5.5c-.55,0-1,.45-1,1v5c0,9,8.5,12,8.5,12,0,0,8.5-3,8.5-12V1.5c0-.55-.45-1-1-1H2.5Z" style="fill:none; stroke:#009688; stroke-linecap:round; stroke-linejoin:round;"/></svg>
<path opacity="0.2" d="M20.25 5.25V10.5C20.25 19.5 12 21.75 12 21.75C12 21.75 3.75 19.5 3.75 10.5V5.25C3.75 5.05109 3.82902 4.86032 3.96967 4.71967C4.11032 4.57902 4.30109 4.5 4.5 4.5H19.5C19.6989 4.5 19.8897 4.57902 20.0303 4.71967C20.171 4.86032 20.25 5.05109 20.25 5.25Z" fill="#00AF42"/>
<path d="M19.5 3.75H4.5C4.10218 3.75 3.72064 3.90804 3.43934 4.18934C3.15804 4.47064 3 4.85218 3 5.25V10.5C3 15.4425 5.3925 18.4378 7.39969 20.0803C9.56156 21.8484 11.7122 22.4494 11.8059 22.4738C11.9348 22.5088 12.0708 22.5088 12.1997 22.4738C12.2934 22.4494 14.4413 21.8484 16.6059 20.0803C18.6075 18.4378 21 15.4425 21 10.5V5.25C21 4.85218 20.842 4.47064 20.5607 4.18934C20.2794 3.90804 19.8978 3.75 19.5 3.75ZM19.5 10.5C19.5 13.9753 18.2194 16.7962 15.6937 18.8831C14.5943 19.7885 13.344 20.493 12 20.9644C10.6736 20.5012 9.4387 19.8092 8.35125 18.9197C5.79563 16.8291 4.5 13.9969 4.5 10.5V5.25H19.5V10.5Z" fill="#00AF42"/>
</svg>

Before

Width:  |  Height:  |  Size: 1023 B

After

Width:  |  Height:  |  Size: 438 B

+9 -9
View File
@@ -416,8 +416,8 @@ function favDigitFromEvent(e) {
function resultCountText(total, shown, query) { function resultCountText(total, shown, query) {
return (query || "").trim() ? return (query || "").trim() ?
T("sd_result_count", "Showing %s actions", shown) : T("sd_result_count", "Matching actions: %s", shown) :
T("sd_result_count_all", "%s actions", total); T("sd_result_count_all", "Actions: %s", total);
} }
// Display label for a notebook tab. Trim any stray whitespace; pages added with an empty title // Display label for a notebook tab. Trim any stray whitespace; pages added with an empty title
@@ -758,7 +758,7 @@ window.HandleStudio = function (payload) {
var fid = payload.id; var fid = payload.id;
if (fid && FAVS.indexOf(fid) !== -1) FAVS.splice(FAVS.indexOf(fid), 1); if (fid && FAVS.indexOf(fid) !== -1) FAVS.splice(FAVS.indexOf(fid), 1);
render({ resize: true, keepScroll: true }); render({ resize: true, keepScroll: true });
flashHint(T("sd_favs_full", "Favourites are full (%s max)", (payload.limit || K_FAV_LIMIT))); flashHint(T("sd_favs_full", "Favorites are full (%s max)", (payload.limit || K_FAV_LIMIT)));
} }
}; };
@@ -821,8 +821,8 @@ function pinSvg(on) {
function setPinState(pin, on) { function setPinState(pin, on) {
pin.classList.toggle("on", on); pin.classList.toggle("on", on);
pin.innerHTML = pinSvg(on); pin.innerHTML = pinSvg(on);
pin.title = on ? T("sd_unpin_fav", "Unpin from favourites (%s)", shortcutCtrl() + "B") : pin.title = on ? T("sd_unpin_fav", "Unpin from favorites (%s)", shortcutCtrl() + "B") :
T("sd_pin_fav", "Pin to favourites (%s)", shortcutCtrl() + "B"); T("sd_pin_fav", "Pin to favorites (%s)", shortcutCtrl() + "B");
} }
// ---- render ------------------------------------------------------------------ // ---- render ------------------------------------------------------------------
@@ -858,15 +858,15 @@ function renderFav() {
badge.textContent = slot; badge.textContent = slot;
// Slot "0" is the 10th favourite (Alt/Option+0). // Slot "0" is the 10th favourite (Alt/Option+0).
var slot_num = slot === "0" ? "10" : slot; var slot_num = slot === "0" ? "10" : slot;
badge.title = T("sd_fav_slot", "Favourite %s (%s)", slot_num, shortcutAlt() + slot); badge.title = T("sd_fav_slot", "Favorite %s (%s)", slot_num, shortcutAlt() + slot);
tile.appendChild(badge); tile.appendChild(badge);
} }
// Direct removal: a hover-revealed ✕ in the tile's corner. click() stops propagation so it // Direct removal: a hover-revealed ✕ in the tile's corner. click() stops propagation so it
// unpins without activating the action. // unpins without activating the action.
var unpin = document.createElement("button"); var unpin = document.createElement("button");
unpin.className = "fav-unpin"; unpin.className = "fav-unpin";
unpin.title = T("sd_remove_fav", "Remove from favourites"); unpin.title = T("sd_remove_fav", "Remove from favorites");
unpin.setAttribute("aria-label", T("sd_remove_fav", "Remove from favourites")); unpin.setAttribute("aria-label", T("sd_remove_fav", "Remove from favorites"));
unpin.innerHTML = '<svg width="9" height="9" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>'; unpin.innerHTML = '<svg width="9" height="9" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/></svg>';
unpin.onclick = function (ev) { ev.stopPropagation(); toggleFav(id); }; unpin.onclick = function (ev) { ev.stopPropagation(); toggleFav(id); };
tile.appendChild(unpin); tile.appendChild(unpin);
@@ -1170,7 +1170,7 @@ function renderTabList() {
listEl.className = "dial-list"; listEl.className = "dial-list";
if (countEl) { if (countEl) {
countEl.hidden = false; countEl.hidden = false;
countEl.textContent = q ? T("sd_tab_match_count", "%s matches", list.length) : T("sd_tab_count", "%s tabs", list.length); countEl.textContent = q ? T("sd_tab_match_count", "Matching tabs: %s", list.length) : T("sd_tab_count", "Tabs: %s", list.length);
} }
list.forEach(function (t, i) { listEl.appendChild(renderTabRow(t, i)); }); list.forEach(function (t, i) { listEl.appendChild(renderTabRow(t, i)); });
} }
+11 -17
View File
@@ -147,7 +147,6 @@ def seed_datadir(datadir):
# ~90% case expects. A ladder whose result depends on the developer's own preferences is not # ~90% case expects. A ladder whose result depends on the developer's own preferences is not
# a gate. # a gate.
app["auto_close_sketch_loops"] = True app["auto_close_sketch_loops"] = True
app["design_show_bed"] = True
# SILENCE THE NETWORK PLUGIN PROMPT. Without this, GUI_App::post_init() re-raises "Bambu # SILENCE THE NETWORK PLUGIN PROMPT. Without this, GUI_App::post_init() re-raises "Bambu
# Network Plug-in Required" from an IDLE event — after any modal sweep this driver does at # Network Plug-in Required" from an IDLE event — after any modal sweep this driver does at
# startup — and ShowModal() then runs a nested event loop. The app is alive, its window is # startup — and ShowModal() then runs a nested event loop. The app is alive, its window is
@@ -481,14 +480,10 @@ def type_into_open_field(value, mark):
def enter_sketch(timeout=180): def enter_sketch(timeout=180):
"""Open a real sketch on a real plane, and PROVE it with the socket before drawing anything. """Open a real sketch on a real plane, and PROVE it with the socket before drawing anything.
THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. The reference planes are hidden until Shift+S, THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. Shift+S enters sketch MODE and pops the plane
with nothing picked, brings them up to choose from, so the plane is clicked AFTER it. That offer; the offer must be dismissed; and the plane itself is chosen by clicking it in the
click opens the session on the plane and pops the tool offer, and a click outside a popup viewport BEFORE Shift+S. check-gui-sketching.py has always done all four steps. This ladder
menu only closes it, so the spot is clicked twice, apart: the second click closes the offer, did two of them — Design tab, then Shift+S — and went straight to the tool letters.
and inside the session it is empty canvas. No Escape — with no offer up, Escape in an empty
sketch leaves it. Then a tool letter arms the tool. check-gui-sketching.py does the same
steps. This ladder once did two of them — Design tab, then Shift+S — and went straight to
the tool letters.
That intermediate state is the trap. `is_sketching` reads 1, every tool key is accepted and That intermediate state is the trap. `is_sketching` reads 1, every tool key is accepted and
traced, and not one click draws anything, because there is no plane under them. The ladder traced, and not one click draws anything, because there is no plane under them. The ladder
@@ -504,10 +499,10 @@ def enter_sketch(timeout=180):
click(132, 53) # Design tab click(132, 53) # Design tab
time.sleep(2.0) time.sleep(2.0)
dismiss_modals() dismiss_modals()
key("shift+s", 1.0) # nothing picked: the reference planes come up click(*PLANE_PX) # pick the plane IN THE VIEWPORT — before Shift+S
click(*PLANE_PX, pause=0.8) # picks the plane IN THE VIEWPORT: session open, offer up... key("shift+s", 1.0)
click(*PLANE_PX) # ...and this closes the offer key("Escape", 0.5) # entering sketch mode pops the offer; dismiss it
key("p", 0.6) # arms Point in the session key("p", 0.6) # any sketch tool starts the session on that plane
if try_call("sketch_describe") is not None: if try_call("sketch_describe") is not None:
# NO Escape here. Every rung already opens with one to drop whatever tool the last # NO Escape here. Every rung already opens with one to drop whatever tool the last
# one left armed, and Escape in the Design tab walks a LIFO: first press drops the # one left armed, and Escape in the Design tab walks a LIFO: first press drops the
@@ -516,15 +511,14 @@ def enter_sketch(timeout=180):
# reported all eleven checks failed with "nothing opened" — the tools were arming # reported all eleven checks failed with "nothing opened" — the tools were arming
# into an empty Feature-mode document. # into an empty Feature-mode document.
return return
die("no sketch opened after Shift+S + plane click within " die("no sketch opened after plane click + Shift+S within "
f"{timeout}s — sketch_describe never answered on {A.sock} (trace {TRACE})") f"{timeout}s — sketch_describe never answered on {A.sock} (trace {TRACE})")
# tool key, the clicks that draw it, and one distinct value per queued field. The values are # tool key, the clicks that draw it, and one distinct value per queued field. The values are
# deliberately nothing like the as-drawn size, so a committed prefill cannot coincide with them. # deliberately nothing like the as-drawn size, so a committed prefill cannot coincide with them.
# Where the plane label sits in the viewport once Shift+S has brought the planes up. Same # Where the plane label sits in the viewport before a sketch is open. Same constant the gesture
# constant the gesture ladder uses; it is a label on the 3D view, not a widget, so it moves only # ladder uses; it is a label on the 3D view, not a widget, so it moves only if the camera does.
# if the camera does.
PLANE_PX = (913, 359) PLANE_PX = (913, 359)
# Every coordinate below stays inside 1000..1400 x 500..760 — the box check-gui-sketching.py's # Every coordinate below stays inside 1000..1400 x 500..760 — the box check-gui-sketching.py's
+9 -6
View File
@@ -78,8 +78,9 @@ def load_table():
if not line.startswith('{"'): if not line.startswith('{"'):
continue continue
# id, name, row, key, action, refusal, accepts, need_bodies, need_sketches, need_sheet, # id, name, row, key, action, refusal, accepts, need_bodies, need_sketches, need_sheet,
# sketch_mode, family, ... — split on top-level commas, respecting quotes. # sketch_mode, family, ... — split on top-level commas, respecting quotes and the
f, cur, q, esc = [], "", False, False # L(...) / L_CONTEXT(...) translation markers.
f, cur, q, esc, depth = [], "", False, False, 0
for ch in line[1:]: for ch in line[1:]:
if esc: if esc:
cur += ch; esc = False; continue cur += ch; esc = False; continue
@@ -87,16 +88,18 @@ def load_table():
cur += ch; esc = True; continue cur += ch; esc = True; continue
if ch == '"': if ch == '"':
q = not q q = not q
if ch == "," and not q: if not q:
depth += (ch == "(") - (ch == ")")
if ch == "," and not q and depth == 0:
f.append(cur.strip()); cur = ""; continue f.append(cur.strip()); cur = ""; continue
if ch == "}" and not q: if ch == "}" and not q and depth == 0:
break break
cur += ch cur += ch
f.append(cur.strip()) f.append(cur.strip())
if len(f) < 12: if len(f) < 12:
continue continue
# User-facing fields are wrapped in the L("...") gettext marker; strip it. # User-facing fields are wrapped in L("...") or L_CONTEXT("...", ...); take the literal.
lit = lambda s: None if s == "nullptr" else re.sub(r'^L\((.*)\)$', r'\1', s).strip('"') lit = lambda s: None if s == "nullptr" else re.search(r'"((?:[^"\\]|\\.)*)"', s).group(1)
out.append({"id": lit(f[0]), "name": lit(f[1]), "row": int(f[2]), "key": lit(f[3]), 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), "action": lit(f[4]), "accepts": int(f[6].rstrip("u"), 0),
"need_bodies": int(f[7]), "need_sketches": int(f[8]), "need_bodies": int(f[7]), "need_sketches": int(f[8]),
+11 -28
View File
@@ -229,20 +229,7 @@ CHROME_DY = int(os.environ.get("ORCA_CAD_CHROME_DY", "26"))
DESIGN_TAB = (128, 29 + CHROME_DY) DESIGN_TAB = (128, 29 + CHROME_DY)
# The ribbon's document group lost its Bed checkbox and label when the bed toggle moved to the # Feature-tree rows, measured on the rig at 1920x1080: first row centre, then 23 px apart.
# Feature tree, and every group to its right moved left by their width: wxLEFT 6 + the 18 px
# CheckBox + wxLEFT 4 + the "Bed" label (~24 px) + wxRIGHT 4, about 56 px. The x of
# CONSTRUCTION_CHECKBOX, CON_BTN and CON_BTN_SKETCH below is the one measured before that, plus
# this. DERIVED, NOT MEASURED: re-measure on the rig and fold it in. CONFIRM_BTN sits after the
# ribbon's stretch spacer and did not move.
RIBBON_DX = -56
# Feature-tree rows, measured on the rig at 1920x1080: the list's first row is centred at y=215,
# then 23 px apart. The first two rows are the Origin and the Bed, unframed on the card above the
# features' own framed list, so the first FEATURE row sits two rows and TREE_GAP further down;
# reset_document and the reopening double-click both need a feature. TREE_GAP is the list's
# 12 px top margin plus its 1 px frame, derived rather than measured -- re-measure on the rig, like
# RIBBON_DX.
# x=300, not the label: a second click ON the label opens the inline rename, and Delete then # x=300, not the label: a second click ON the label opens the inline rename, and Delete then
# edits the text instead of removing the feature. # edits the text instead of removing the feature.
# #
@@ -250,8 +237,7 @@ RIBBON_DX = -56
# unshifted click lands 26 px BELOW the first row -- just past its 23 px height -- so the row is # unshifted click lands 26 px BELOW the first row -- just past its 23 px height -- so the row is
# never selected, Delete does nothing, and reset_document spends 40 rounds on it before dying # never selected, Delete does nothing, and reset_document spends 40 rounds on it before dying
# with "could not empty the feature tree". That names the feature tree, which is not the fault. # with "could not empty the feature tree". That names the feature tree, which is not the fault.
TREE_GAP = 12 + 1 TREE_ROW0 = (300, 215 + CHROME_DY)
TREE_ROW0 = (300, 215 + 2 * 23 + TREE_GAP + CHROME_DY)
def go_design(): def go_design():
@@ -280,21 +266,18 @@ def reset_document():
def enter_sketch(tool_key, plane_px=(913, 359)): def enter_sketch(tool_key, plane_px=(913, 359)):
"""Enter a sketch the way the design law says: pick the plane in the viewport, then the tool. """Enter a sketch the way the design law says: pick the plane in the viewport, then the tool.
leave_sketch()'s Escapes let go of every pick, so Shift+S finds nothing to sketch on and only Shift+S enters sketch MODE and pops the offer; Escape dismisses it; the tool letter then
brings the reference planes up; the click on one opens the session on it and pops the tool starts the session on the plane the click selected. All four steps are real input — nothing
offer. A click outside a popup menu only closes it, so the same spot is clicked again, apart: here goes through the socket.
inside the session it is empty canvas, so it selects nothing if the offer is slow to appear.
No Escape: with no offer up, Escape in an empty sketch leaves it. The tool letter then arms
the tool. All of it is real input — nothing here goes through the socket.
""" """
leave_sketch() leave_sketch()
click(*plane_px)
key("shift+s", 0.8) key("shift+s", 0.8)
click(*plane_px, pause=0.8) # picks the plane: the session opens and the offer pops... key("Escape", 0.4) # entering sketch mode pops the offer; dismiss it
click(*plane_px) # ...and this closes the offer
key("p", 0.6) key("p", 0.6)
if try_call("sketch_describe") is None: if try_call("sketch_describe") is None:
shot("/shots/gl-enter-failed.png") shot("/shots/gl-enter-failed.png")
die("no sketch opened after Shift+S + plane click (see /shots/gl-enter-failed.png)") die("no sketch opened after plane click + Shift+S (see /shots/gl-enter-failed.png)")
calibrate_here() # THIS sketch's own camera map, on THIS sketch's own plane calibrate_here() # THIS sketch's own camera map, on THIS sketch's own plane
key(tool_key, 0.6) key(tool_key, 0.6)
@@ -754,7 +737,7 @@ def corner_pair(ents):
die("no adjacent pair in what should be a rectangle") die("no adjacent pair in what should be a rectangle")
CONSTRUCTION_CHECKBOX = (419 + RIBBON_DX, 75 + CHROME_DY) CONSTRUCTION_CHECKBOX = (419, 75 + CHROME_DY)
def draw_line(x0, y0, x1, y1, length, angle): def draw_line(x0, y0, x1, y1, length, angle):
@@ -883,7 +866,7 @@ def rung_extend():
# because the ladder only ever clicks "perpendicular" (3) and "equal" (5), both of which sit # because the ladder only ever clicks "perpendicular" (3) and "equal" (5), both of which sit
# before the first insertion. The next rung to use "tangent" would have clicked "collinear". # before the first insertion. The next rung to use "tangent" would have clicked "collinear".
CON_BTN_Y = 76 + CHROME_DY CON_BTN_Y = 76 + CHROME_DY
CON_BTN = {n: (449 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate( CON_BTN = {n: (449 + 42 * i, CON_BTN_Y) for i, n in enumerate(
["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal", ["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal",
"equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric", "equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric",
"sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])} "sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])}
@@ -897,7 +880,7 @@ CON_BTN = {n: (449 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate(
# which already carries CHROME_DY), but the X start depends on how wide the sketch toolbar to the # which already carries CHROME_DY), but the X start depends on how wide the sketch toolbar to the
# left of this group renders, and this fork keeps mainline's top row. Re-measure before trusting # left of this group renders, and this fork keeps mainline's top row. Re-measure before trusting
# D11 here: screenshot in sketch mode and detect the icon columns, do not derive it by offset. # D11 here: screenshot in sketch mode and detect the icon columns, do not derive it by offset.
CON_BTN_SKETCH = {n: (677 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate( CON_BTN_SKETCH = {n: (677 + 42 * i, CON_BTN_Y) for i, n in enumerate(
["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal", ["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal",
"equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric", "equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric",
"sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])} "sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])}
+10 -2
View File
@@ -43,6 +43,11 @@ def tstr(s):
return "nullptr" if s is None else "L(" + cstr(s) + ")" return "nullptr" if s is None else "L(" + cstr(s) + ")"
def tstr_design(s):
# Verb name: same msgctxt as the Design panel's own tool labels.
return "L_CONTEXT(" + cstr(s) + ', "Design")'
def validate(A): def validate(A):
"""Refuse an atlas the header cannot represent, naming every fault at once. """Refuse an atlas the header cannot represent, naming every fault at once.
@@ -90,6 +95,9 @@ def main():
"#ifndef L", "#ifndef L",
"#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp", "#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp",
"#endif", "#endif",
"#ifndef L_CONTEXT",
"#define L_CONTEXT(s, context) s",
"#endif",
"", "",
"namespace Slic3r { namespace GUI {", "namespace Slic3r { namespace GUI {",
"", "",
@@ -113,7 +121,7 @@ def main():
"// nullptr -> kernel support exists, no GUI path yet (row shows disabled)", "// nullptr -> kernel support exists, no GUI path yet (row shows disabled)",
"struct OfferVerb {", "struct OfferVerb {",
" const char* id;", " const char* id;",
" const char* name; // drawing-office word (L10); marked L(); translated at use", " const char* name; // drawing-office word (L10); msgctxt \"Design\", translated at use",
" int row; // index into kOfferRowNames, the ratified address — NEVER reorder", " int row; // index into kOfferRowNames, the ratified address — NEVER reorder",
" const char* key; // shortcut shown in the row, or nullptr", " const char* key; // shortcut shown in the row, or nullptr",
" const char* action;", " const char* action;",
@@ -170,7 +178,7 @@ def main():
n = v.get("needs") or {} n = v.get("needs") or {}
lines.append( lines.append(
" {%s, %s, %d, %s, %s, %s, 0x%08xu, %d, %d, %s, %s, %s, %s, %s}," % ( " {%s, %s, %d, %s, %s, %s, 0x%08xu, %d, %d, %s, %s, %s, %s, %s}," % (
cstr(v["id"]), tstr(v["name"]), slots.index(v["slot"]), cstr(v["id"]), tstr_design(v["name"]), slots.index(v["slot"]),
cstr(v.get("key")), cstr(v.get("action")), tstr(v.get("refusal")), cstr(v.get("key")), cstr(v.get("action")), tstr(v.get("refusal")),
mask, n.get("bodies", 0), n.get("sketches", 0), mask, n.get("bodies", 0), n.get("sketches", 0),
"true" if n.get("sheet") else "false", "true" if n.get("sheet") else "false",
+2 -2
View File
@@ -229,11 +229,11 @@
"none" "none"
], ],
"needs": {}, "needs": {},
"refusal": "Select a flat face or a reference plane to sketch on", "refusal": "Click a face or a reference plane in the viewport, then a sketch tool",
"gui": true, "gui": true,
"action": "key:S+S", "action": "key:S+S",
"icon": "design_sketch", "icon": "design_sketch",
"hint": "Sketch on the selected flat face or plane, or click one next" "hint": "Click a face or a reference plane, then pick a drawing tool"
}, },
{ {
"id": "extrude", "id": "extrude",
+58 -18
View File
@@ -8,6 +8,9 @@ include) fails wherever the error is. Headers are compiled on their own, and
fail only on errors in their changed lines. Deleting an #include also fails fail only on errors in their changed lines. Deleting an #include also fails
on every use, changed or not, that now lacks the header it provided. on every use, changed or not, that now lacks the header it provided.
With -- --fix, clang-tidy adds the missing includes, on those lines only, and
each file it changed is checked again so that only what remains is reported.
The checks come from .clang-tidy at the repository root. The compile database The checks come from .clang-tidy at the repository root. The compile database
must come from a configure with SLIC3R_PCH=OFF, or the precompiled header hides must come from a configure with SLIC3R_PCH=OFF, or the precompiled header hides
missing includes. missing includes.
@@ -36,6 +39,9 @@ EXCLUDED_DIRS = ("src/glad/", "tests/catch2/")
# Per file. A deleted include can leave hundreds of follow-on errors. # Per file. A deleted include can leave hundreds of follow-on errors.
MAX_REPORTED = 30 MAX_REPORTED = 30
# Subprocess output is UTF-8 whatever the locale, which is cp1252 on Windows.
UTF8 = {"encoding": "utf-8", "errors": "replace"}
HUNK_RE = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@") HUNK_RE = re.compile(r"^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@")
DIAGNOSTIC_RE = re.compile(r"^(.+?):(\d+):(\d+): (error|warning): (.*)$") DIAGNOSTIC_RE = re.compile(r"^(.+?):(\d+):(\d+): (error|warning): (.*)$")
FIX_MESSAGE_RE = re.compile(r"^\s+Message:\s+(['\"])(.*)\1$") FIX_MESSAGE_RE = re.compile(r"^\s+Message:\s+(['\"])(.*)\1$")
@@ -72,7 +78,8 @@ def parse_diff(diff):
change = None change = None
for line in diff.splitlines(): for line in diff.splitlines():
if line.startswith("+++ "): if line.startswith("+++ "):
target = line[4:] # git appends a tab to the header of a path that contains a space.
target = line[4:].removesuffix("\t")
change = changes.setdefault(target[2:], FileChange()) if target.startswith("b/") else None change = changes.setdefault(target[2:], FileChange()) if target.startswith("b/") else None
continue continue
if change is None: if change is None:
@@ -98,8 +105,11 @@ def is_checked(path):
def changed_files(merge_base): def changed_files(merge_base):
# Against the working tree, so a local run covers uncommitted edits too. # Against the working tree, so a local run covers uncommitted edits too.
diff = subprocess.run(["git", "diff", "-U0", "--no-color", "--no-ext-diff", "--diff-filter=AMR", merge_base], # core.quotePath=false keeps a non-ASCII path unquoted, and the explicit prefixes
check=True, capture_output=True, text=True).stdout # override diff.noprefix and diff.mnemonicPrefix, so parse_diff sees its b/ prefix.
diff = subprocess.run(["git", "-c", "core.quotePath=false", "diff", "-U0", "--no-color", "--no-ext-diff",
"--src-prefix=a/", "--dst-prefix=b/", "--diff-filter=AMR", merge_base],
check=True, capture_output=True, **UTF8).stdout
return {path: change for path, change in parse_diff(diff).items() if is_checked(path)} return {path: change for path, change in parse_diff(diff).items() if is_checked(path)}
@@ -117,8 +127,9 @@ def run_clang_tidy(clang_tidy, build_dir, path, lines, extra_args):
cmd = [clang_tidy, "-p", build_dir, "--quiet", "--export-fixes=" + fixes, cmd = [clang_tidy, "-p", build_dir, "--quiet", "--export-fixes=" + fixes,
"--extra-arg=-Wno-unknown-warning-option", "--extra-arg=-ferror-limit=0", *extra_args, path] "--extra-arg=-Wno-unknown-warning-option", "--extra-arg=-ferror-limit=0", *extra_args, path]
if lines is not None: if lines is not None:
cmd.insert(1, "--line-filter=" + json.dumps([{"name": path, "lines": lines}])) # clang-tidy matches the name against the end of the file's native path.
result = subprocess.run(cmd, capture_output=True, text=True) cmd.insert(1, "--line-filter=" + json.dumps([{"name": os.path.normpath(path), "lines": lines}]))
result = subprocess.run(cmd, capture_output=True, **UTF8)
suggestions = parse_suggested_includes(fixes) suggestions = parse_suggested_includes(fixes)
output = result.stdout + result.stderr output = result.stdout + result.stderr
return result.returncode, output, parse_diagnostics(output, suggestions) return result.returncode, output, parse_diagnostics(output, suggestions)
@@ -178,7 +189,7 @@ def error_sites(errors, text):
def errors_alone_at(revision, clang_tidy, build_dir, path): def errors_alone_at(revision, clang_tidy, build_dir, path):
"""The error sites a header had when compiled on its own at `revision`.""" """The error sites a header had when compiled on its own at `revision`."""
shown = subprocess.run(["git", "show", f"{revision}:{path}"], capture_output=True, text=True) shown = subprocess.run(["git", "show", f"{revision}:{path}"], capture_output=True, **UTF8)
if shown.returncode != 0: if shown.returncode != 0:
return Counter() return Counter()
# Beside the original, so its quoted includes resolve the same way. # Beside the original, so its quoted includes resolve the same way.
@@ -194,13 +205,19 @@ def errors_alone_at(revision, clang_tidy, build_dir, path):
def check_file(clang_tidy, build_dir, merge_base, path, change, extra_args): def check_file(clang_tidy, build_dir, merge_base, path, change, extra_args):
"""Run clang-tidy on one file and return (failed, output, failing diagnostics).""" """Run clang-tidy on one file and return (failed, output, failing diagnostics, fixed)."""
fixing = any(arg.startswith("--fix") for arg in extra_args)
if fixing:
with open(path, "rb") as f:
before = f.read()
# A deleted include can orphan uses on unchanged lines, so such a file is # A deleted include can orphan uses on unchanged lines, so such a file is
# checked whole and the findings narrowed here. --fix keeps the line filter # checked whole and the findings narrowed here. --fix keeps a line filter so
# so it never rewrites unrelated code. # it never rewrites unrelated code, which for such a file means a second,
whole = bool(change.removed_includes) and not extra_args # fixing run limited to the lines the first one found wanting.
whole = bool(change.removed_includes)
returncode, output, diagnostics = run_clang_tidy(clang_tidy, build_dir, path, returncode, output, diagnostics = run_clang_tidy(clang_tidy, build_dir, path,
None if whole else change.lines, extra_args) None if whole else change.lines,
[] if whole else extra_args)
real = os.path.realpath(path) real = os.path.realpath(path)
def introduced(d): def introduced(d):
@@ -216,14 +233,25 @@ def check_file(clang_tidy, build_dir, merge_base, path, change, extra_args):
if errors and change.removed_includes: if errors and change.removed_includes:
with open(path, encoding="utf-8") as f: with open(path, encoding="utf-8") as f:
text = f.read() text = f.read()
before = errors_alone_at(merge_base, clang_tidy, build_dir, path) before_sites = errors_alone_at(merge_base, clang_tidy, build_dir, path)
failing += [d for d in errors if d not in failing failing += [d for d in errors if d not in failing
and (error_sites([d], text) - before)] and (error_sites([d], text) - before_sites)]
return bool(failing), output, failing failed = bool(failing)
if whole: elif whole:
failing = [d for d in diagnostics if d.is_compile_error or introduced(d)] failing = [d for d in diagnostics if d.is_compile_error or introduced(d)]
return bool(failing), output, failing failed = bool(failing)
return returncode != 0, output, diagnostics else:
failing, failed = diagnostics, returncode != 0
if not fixing:
return failed, output, failing, False
if whole and failing:
lines = change.lines + [[d.line, d.line] for d in failing if os.path.realpath(d.file) == real]
run_clang_tidy(clang_tidy, build_dir, path, lines, extra_args)
with open(path, "rb") as f:
if f.read() == before:
return failed, output, failing, False
# Checked again, so what is reported is what the fixes left.
return check_file(clang_tidy, build_dir, merge_base, path, change, [])[:3] + (True,)
def main(): def main():
@@ -234,6 +262,8 @@ def main():
parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count()) parser.add_argument("-j", "--jobs", type=int, default=os.cpu_count())
parser.add_argument("extra_args", nargs="*", help="passed to clang-tidy after --, e.g. -- --fix") parser.add_argument("extra_args", nargs="*", help="passed to clang-tidy after --, e.g. -- --fix")
args = parser.parse_args() args = parser.parse_args()
# A piped stdout on Windows is cp1252, which cannot encode every character clang-tidy prints.
sys.stdout.reconfigure(errors="replace")
merge_base = subprocess.run(["git", "merge-base", args.base, "HEAD"], check=True, merge_base = subprocess.run(["git", "merge-base", args.base, "HEAD"], check=True,
capture_output=True, text=True).stdout.strip() capture_output=True, text=True).stdout.strip()
@@ -254,11 +284,14 @@ def main():
annotate = os.environ.get("GITHUB_ACTIONS") == "true" annotate = os.environ.get("GITHUB_ACTIONS") == "true"
root = os.getcwd() + os.sep root = os.getcwd() + os.sep
failed = [] failed = []
fixed = []
with ThreadPoolExecutor(max_workers=args.jobs) as pool: with ThreadPoolExecutor(max_workers=args.jobs) as pool:
jobs = {path: pool.submit(check_file, args.clang_tidy, args.build_dir, merge_base, path, change, args.extra_args) jobs = {path: pool.submit(check_file, args.clang_tidy, args.build_dir, merge_base, path, change, args.extra_args)
for path, change in todo} for path, change in todo}
for path, job in jobs.items(): for path, job in jobs.items():
file_failed, output, diagnostics = job.result() file_failed, output, diagnostics, file_fixed = job.result()
if file_fixed:
fixed.append(path)
if not file_failed: if not file_failed:
continue continue
failed.append(path) failed.append(path)
@@ -273,6 +306,13 @@ def main():
if len(diagnostics) > MAX_REPORTED: if len(diagnostics) > MAX_REPORTED:
print(f"... and {len(diagnostics) - MAX_REPORTED} more") print(f"... and {len(diagnostics) - MAX_REPORTED} more")
if fixed:
print(f"\nAdded includes to {len(fixed)} file(s):")
for path in fixed:
print(f" {path}")
if failed and fixed:
print(f"\nclang-tidy still fails on {len(failed)} file(s); the findings above are what --fix could not add.")
return 1
if failed: if failed:
print(f"\nclang-tidy failed on {len(failed)} file(s). Add the includes it names, or apply its " print(f"\nclang-tidy failed on {len(failed)} file(s). Add the includes it names, or apply its "
"suggestions locally with scripts/run_clang_tidy.sh --fix (scripts\\run_clang_tidy.ps1 -Fix on Windows).") "suggestions locally with scripts/run_clang_tidy.sh --fix (scripts\\run_clang_tidy.ps1 -Fix on Windows).")
+31 -7
View File
@@ -54,6 +54,21 @@ function Has([string]$Command) {
return [bool](Get-Command $Command -ErrorAction SilentlyContinue) return [bool](Get-Command $Command -ErrorAction SilentlyContinue)
} }
# Runs a native command with its output, stderr included, streamed to $Log or dropped.
function Invoke-Quiet([scriptblock]$Command, [string]$Log) {
# Under "Stop", 2>&1 turns every stderr line of a native command into a terminating error.
$ErrorActionPreference = "Continue"
$lines = {
& $Command 2>&1 | ForEach-Object {
# "$_" turns a blank stderr line into the text System.Management.Automation.RemoteException.
if ($_ -isnot [System.Management.Automation.ErrorRecord]) { $_ }
elseif ($null -ne $_.TargetObject) { $_.TargetObject }
else { $_.Exception.Message }
}
}
if ($Log) { & $lines | Out-File -Encoding utf8 -LiteralPath $Log } else { & $lines | Out-Null }
}
function Request-Install([string]$What, [string]$Command) { function Request-Install([string]$What, [string]$Command) {
Write-Host "Missing: $What" Write-Host "Missing: $What"
if (Ask "Install it now with: $Command ?") { if (Ask "Install it now with: $Command ?") {
@@ -78,11 +93,11 @@ if (-not (Has "git")) { Request-Install "Git" "build_win.bat --install-deps" }
# Python: the py launcher, else a python.exe that is not the Microsoft Store stub. # Python: the py launcher, else a python.exe that is not the Microsoft Store stub.
$Python = $null $Python = $null
if (Has "py") { if (Has "py") {
& py -3 --version *> $null Invoke-Quiet { & py -3 --version }
if ($LASTEXITCODE -eq 0) { $Python = @("py", "-3") } if ($LASTEXITCODE -eq 0) { $Python = @("py", "-3") }
} }
if (-not $Python -and (Has "python")) { if (-not $Python -and (Has "python")) {
& python --version *> $null Invoke-Quiet { & python --version }
if ($LASTEXITCODE -eq 0) { $Python = @("python") } if ($LASTEXITCODE -eq 0) { $Python = @("python") }
} }
if (-not $Python) { Request-Install "Python 3" "winget install -e --id Python.Python.3.12" } if (-not $Python) { Request-Install "Python 3" "winget install -e --id Python.Python.3.12" }
@@ -100,7 +115,12 @@ if (-not $VsPath) { Request-Install "Visual Studio with the C++ tools" "build_wi
# Load the developer environment, as build_win.bat does. # Load the developer environment, as build_win.bat does.
$HostArch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" } $HostArch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" }
$VsDevCmd = Join-Path $VsPath "Common7\Tools\VsDevCmd.bat" $VsDevCmd = Join-Path $VsPath "Common7\Tools\VsDevCmd.bat"
$envLines = & cmd /c "`"$VsDevCmd`" -arch=$Arch -host_arch=$HostArch -no_logo >nul && set" # Ignores VsDevCmd's exit code, as build_win.bat does, and checks the variables it sets
# before applying any, cleared in cmd so values inherited from a developer shell do not count.
$envLines = & cmd /c "set VCToolsInstallDir=& set WindowsSdkDir=& `"$VsDevCmd`" -arch=$Arch -host_arch=$HostArch -no_logo >nul 2>nul & set"
if (-not ($envLines -match '^VCToolsInstallDir=.') -or -not ($envLines -match '^WindowsSdkDir=.')) {
throw "Loading the Visual Studio $Arch environment failed. Run `"$VsDevCmd`" -arch=$Arch -host_arch=$HostArch in cmd to see why."
}
foreach ($line in $envLines) { foreach ($line in $envLines) {
$i = $line.IndexOf("=") $i = $line.IndexOf("=")
if ($i -gt 0) { Set-Item -Path ("env:" + $line.Substring(0, $i)) -Value $line.Substring($i + 1) } if ($i -gt 0) { Set-Item -Path ("env:" + $line.Substring(0, $i)) -Value $line.Substring($i + 1) }
@@ -190,13 +210,17 @@ $cmakeArgs = @("-S", ".", "-B", $BuildDir, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Re
Write-Host "Configuring $BuildDir with $Compiler" Write-Host "Configuring $BuildDir with $Compiler"
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
$Log = Join-Path $BuildDir "configure.log" $Log = Join-Path $BuildDir "configure.log"
& cmake @cmakeArgs *> $Log Invoke-Quiet { & cmake @cmakeArgs } $Log
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Get-Content $Log -Tail 20 Get-Content -LiteralPath $Log -Tail 20
throw "Configuring failed; the full log is in $Log." throw "Configuring failed; the full log is in $Log."
} }
& cmake --build $BuildDir --target git_commit_hash_header *> $null $HashLog = Join-Path $BuildDir "git_commit_hash.log"
if ($LASTEXITCODE -ne 0) { throw "Generating git_commit_hash.h failed." } Invoke-Quiet { & cmake --build $BuildDir --target git_commit_hash_header } $HashLog
if ($LASTEXITCODE -ne 0) {
Get-Content -LiteralPath $HashLog -Tail 20
throw "Generating git_commit_hash.h failed; the full log is in $HashLog."
}
# --- Base revision ------------------------------------------------------------ # --- Base revision ------------------------------------------------------------
+40 -10
View File
@@ -6,8 +6,9 @@
# scripts/run_clang_tidy.sh --fix also add the missing includes it names # scripts/run_clang_tidy.sh --fix also add the missing includes it names
# #
# It configures a separate build directory (build-tidy) without the precompiled # It configures a separate build directory (build-tidy) without the precompiled
# header, installs the pinned clang-tidy into a virtual environment inside it, and # header, uses the clang-tidy on your system or installs the pinned one into a
# runs scripts/clang_tidy_diff.py the way CI does. Uncommitted changes are checked too. # virtual environment inside it, and runs scripts/clang_tidy_diff.py the way CI
# does. Uncommitted changes are checked too.
set -euo pipefail set -euo pipefail
@@ -23,7 +24,8 @@ Usage: scripts/run_clang_tidy.sh [options]
deps/build/<arch> on macOS) deps/build/<arch> on macOS)
-j, --jobs N parallel clang-tidy runs (default: all cores) -j, --jobs N parallel clang-tidy runs (default: all cores)
--fix apply clang-tidy's fixes (adds the missing includes) --fix apply clang-tidy's fixes (adds the missing includes)
-y, --yes install missing tools without asking -y, --yes install missing tools without asking; another clang-tidy
version found on the system is then not offered
-h, --help show this help -h, --help show this help
EOF EOF
} }
@@ -136,12 +138,38 @@ REQUIREMENTS="$ROOT/scripts/clang_tidy_requirements.txt"
PINNED=$(sed -n 's/^clang-tidy==//p' "$REQUIREMENTS") PINNED=$(sed -n 's/^clang-tidy==//p' "$REQUIREMENTS")
VENV="$BUILD_DIR/clang-tidy-venv" VENV="$BUILD_DIR/clang-tidy-venv"
if [ -n "${CLANG_TIDY:-}" ]; then is_pinned() {
[ -x "$1" ] && "$1" --version 2>/dev/null | grep -q "version $PINNED"
}
CLANG_TIDY="${CLANG_TIDY:-}"
if [ -n "$CLANG_TIDY" ]; then
# Set by the caller: use it as is. # Set by the caller: use it as is.
: is_pinned "$CLANG_TIDY" || echo "Warning: $CLANG_TIDY is not clang-tidy $PINNED, so results may differ from CI." >&2
else else
# One already on the system comes first: the pinned version outright, another
# version if the user accepts the difference. The pinned version is installed
# into a virtual environment otherwise.
INSTALLED=""
for candidate in $(command -v clang-tidy "clang-tidy-${PINNED%%.*}" || true) \
"/usr/lib/llvm-${PINNED%%.*}/bin/clang-tidy" \
"$(brew --prefix llvm 2>/dev/null || true)/bin/clang-tidy"; do
if is_pinned "$candidate"; then
CLANG_TIDY="$candidate"
break
fi
[ -z "$INSTALLED" ] && [ -x "$candidate" ] && INSTALLED="$candidate"
done
if [ -z "$CLANG_TIDY" ] && [ -n "$INSTALLED" ] && [ "$YES" = 0 ] && ! is_pinned "$VENV/bin/clang-tidy"; then
echo "Found $INSTALLED, which is $("$INSTALLED" --version | sed -n 's/.*version \([0-9.]*\).*/\1/p' | head -n 1), not the $PINNED CI uses, so results may differ slightly."
if ask "Use it anyway?"; then
CLANG_TIDY="$INSTALLED"
fi
fi
fi
if [ -z "$CLANG_TIDY" ]; then
CLANG_TIDY="$VENV/bin/clang-tidy" CLANG_TIDY="$VENV/bin/clang-tidy"
if [ ! -x "$CLANG_TIDY" ] || ! "$CLANG_TIDY" --version | grep -q "version $PINNED"; then if ! is_pinned "$CLANG_TIDY"; then
if ask "clang-tidy $PINNED (the version CI uses) is not installed. Install it into $VENV?"; then if ask "clang-tidy $PINNED (the version CI uses) is not installed. Install it into $VENV?"; then
mkdir -p "$BUILD_DIR" mkdir -p "$BUILD_DIR"
if ! python3 -m venv "$VENV"; then if ! python3 -m venv "$VENV"; then
@@ -158,9 +186,6 @@ else
fi fi
fi fi
fi fi
if ! "$CLANG_TIDY" --version | grep -q "version $PINNED"; then
echo "Warning: $CLANG_TIDY is not clang-tidy $PINNED, so results may differ from CI." >&2
fi
# --- Dependencies ------------------------------------------------------------- # --- Dependencies -------------------------------------------------------------
@@ -217,7 +242,12 @@ cmake --build "$BUILD_DIR" --target git_commit_hash_header >/dev/null
if [ -z "$BASE" ]; then if [ -z "$BASE" ]; then
REMOTE=$(git remote -v | awk '/github\.com[:\/]OrcaSlicer\/OrcaSlicer(\.git)? \(fetch\)/ { print $1; exit }') REMOTE=$(git remote -v | awk '/github\.com[:\/]OrcaSlicer\/OrcaSlicer(\.git)? \(fetch\)/ { print $1; exit }')
REMOTE="${REMOTE:-origin}" if [ -z "$REMOTE" ]; then
# Against a fork's main that already has the commits, nothing is checked.
echo "Warning: no remote points at github.com/OrcaSlicer/OrcaSlicer, so this compares against origin/main." >&2
echo "If origin is your fork, add the upstream remote (git remote add upstream https://github.com/OrcaSlicer/OrcaSlicer.git) or pass --base." >&2
REMOTE=origin
fi
if [ "$FETCH" = 1 ]; then if [ "$FETCH" = 1 ]; then
git fetch --quiet "$REMOTE" main git fetch --quiet "$REMOTE" main
fi fi
+96
View File
@@ -5,10 +5,13 @@ external deps).
Run from the repo root: python -m unittest discover -s scripts/tests -v Run from the repo root: python -m unittest discover -s scripts/tests -v
""" """
import json
import os import os
import subprocess
import sys import sys
import tempfile import tempfile
import unittest import unittest
from unittest import mock
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))) sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
@@ -62,6 +65,10 @@ class TestParseChangedLines(unittest.TestCase):
def test_pure_rename_has_no_changed_lines(self): def test_pure_rename_has_no_changed_lines(self):
self.assertNotIn("src/libslic3r/Renamed.cpp", self.changed) self.assertNotIn("src/libslic3r/Renamed.cpp", self.changed)
def test_path_with_a_space_drops_the_tab_git_appends(self):
changed = clang_tidy_diff.parse_diff("+++ b/src/libslic3r/Foo Bar.cpp\t\n@@ -1,0 +2 @@\n+int x;\n")
self.assertEqual(changed["src/libslic3r/Foo Bar.cpp"].lines, [[2, 2]])
class TestNamesRemovedInclude(unittest.TestCase): class TestNamesRemovedInclude(unittest.TestCase):
def test_matches_however_the_include_was_spelled(self): def test_matches_however_the_include_was_spelled(self):
@@ -128,5 +135,94 @@ class TestParseSuggestedIncludes(unittest.TestCase):
self.assertEqual(clang_tidy_diff.parse_suggested_includes("/nonexistent/fixes.yaml"), {}) self.assertEqual(clang_tidy_diff.parse_suggested_includes("/nonexistent/fixes.yaml"), {})
class TestSubprocessCalls(unittest.TestCase):
def run_patched(self, function, *args, returncode=0, stdout=""):
done = subprocess.CompletedProcess([], returncode, stdout, "")
with mock.patch.object(clang_tidy_diff.subprocess, "run", return_value=done) as run, \
mock.patch.object(clang_tidy_diff.os.path, "normpath", wraps=os.path.normpath) as normpath:
result = function(*args)
return result, run.call_args, normpath
def test_line_filter_names_the_file_with_native_separators(self):
_, call, normpath = self.run_patched(clang_tidy_diff.run_clang_tidy, "clang-tidy", "build",
"src/libslic3r/Color.cpp", [[4, 4]], [])
line_filter = next(arg for arg in call.args[0] if arg.startswith("--line-filter="))
self.assertEqual(json.loads(line_filter.split("=", 1)[1]),
[{"name": os.path.join("src", "libslic3r", "Color.cpp"), "lines": [[4, 4]]}])
normpath.assert_any_call("src/libslic3r/Color.cpp")
def test_changed_files_reads_non_ascii_paths_and_text_as_utf8(self):
diff = "+++ b/src/libslic3r/Über.cpp\n@@ -1,0 +2 @@\n+// 打印\n"
files, call, _ = self.run_patched(clang_tidy_diff.changed_files, "base", stdout=diff)
self.assertIn("core.quotePath=false", call.args[0])
# Whatever diff.noprefix or diff.mnemonicPrefix a user has set.
self.assertIn("--dst-prefix=b/", call.args[0])
self.assertEqual(call.kwargs["encoding"], "utf-8")
self.assertEqual(files["src/libslic3r/Über.cpp"].lines, [[2, 2]])
def test_clang_tidy_and_git_show_output_is_decoded_as_utf8(self):
_, call, _ = self.run_patched(clang_tidy_diff.run_clang_tidy, "clang-tidy", "build",
"src/libslic3r/Color.cpp", None, [])
self.assertEqual(call.kwargs["encoding"], "utf-8")
_, call, _ = self.run_patched(clang_tidy_diff.errors_alone_at, "base", "clang-tidy", "build",
"src/libslic3r/Color.hpp", returncode=128)
self.assertEqual(call.kwargs["encoding"], "utf-8")
class TestCheckFileFix(unittest.TestCase):
"""check_file with -- --fix: what clang-tidy is run on, and what is reported afterwards."""
def setUp(self):
self.dir = tempfile.TemporaryDirectory()
self.addCleanup(self.dir.cleanup)
self.path = os.path.join(self.dir.name, "Color.cpp")
with open(self.path, "w") as f:
f.write("int x;\n")
def finding(self, line, include=""):
return clang_tidy_diff.Diagnostic(self.path, line, 1, "error",
'no header providing "x" is directly included [misc-include-cleaner]', include)
def check(self, change, results, fix_writes=None):
"""Run check_file with run_clang_tidy answering from `results` in turn; the --fix run
rewrites the file with `fix_writes` when given. Returns (result, calls)."""
calls = []
def run(clang_tidy, build_dir, path, lines, extra_args):
calls.append((lines, extra_args))
if "--fix" in extra_args and fix_writes is not None:
with open(path, "w") as f:
f.write(fix_writes)
return results[len(calls) - 1]
with mock.patch.object(clang_tidy_diff, "run_clang_tidy", side_effect=run):
result = clang_tidy_diff.check_file("clang-tidy", "build", "base", self.path, change, ["--fix"])
return result, calls
def test_a_deleted_include_is_fixed_on_the_lines_it_orphaned_only(self):
change = clang_tidy_diff.FileChange(lines=[[4, 4]], removed_includes={"libslic3r/Point.hpp"})
orphaned = self.finding(50, "<libslic3r/Point.hpp>")
unrelated = self.finding(60, "<vector>")
(failed, _, failing, fixed), calls = self.check(
change, [(1, "", [orphaned, unrelated]), (1, "", [orphaned]), (0, "", [])], fix_writes="#include <libslic3r/Point.hpp>\n")
self.assertEqual(calls, [(None, []), ([[4, 4], [50, 50]], ["--fix"]), (None, [])])
self.assertEqual((failed, failing, fixed), (False, [], True))
def test_a_file_the_fix_did_not_change_keeps_its_findings(self):
change = clang_tidy_diff.FileChange(lines=[[4, 4]])
error = clang_tidy_diff.Diagnostic(self.path, 4, 1, "error", "unknown type name 'Foo' [clang-diagnostic-error]")
(failed, _, failing, fixed), calls = self.check(change, [(1, "", [error])])
self.assertEqual(calls, [([[4, 4]], ["--fix"])])
self.assertEqual((failed, failing, fixed), (True, [error], False))
def test_a_changed_file_is_checked_again_and_reports_what_is_left(self):
change = clang_tidy_diff.FileChange(lines=[[4, 4]])
error = clang_tidy_diff.Diagnostic(self.path, 4, 1, "error", "unknown type name 'Foo' [clang-diagnostic-error]")
(failed, _, failing, fixed), calls = self.check(
change, [(1, "", [self.finding(4, "<vector>"), error]), (1, "", [error])], fix_writes="#include <vector>\n")
self.assertEqual(calls, [([[4, 4]], ["--fix"]), ([[4, 4]], [])])
self.assertEqual((failed, failing, fixed), (True, [error], True))
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()
+2
View File
@@ -153,6 +153,8 @@ using namespace nlohmann;
#include "slic3r/GUI/GUI_ObjectList.hpp" #include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Jobs/SendJob.hpp" #include "slic3r/GUI/Jobs/SendJob.hpp"
#include <boost/nowide/convert.hpp>
#include <stdio.h>
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
+1
View File
@@ -35,6 +35,7 @@ extern "C"
#include <boost/algorithm/string/classification.hpp> #include <boost/algorithm/string/classification.hpp>
#include <stdio.h> #include <stdio.h>
#include <boost/algorithm/string/constants.hpp>
#ifdef SLIC3R_GUI #ifdef SLIC3R_GUI
class OpenGLVersionCheck class OpenGLVersionCheck
+8
View File
@@ -11,6 +11,14 @@
#include "git_commit_hash.h" #include "git_commit_hash.h"
#include "libslic3r_version.h" #include "libslic3r_version.h"
#include "StackWalker.h"
#include <algorithm>
#include <atomic>
#include <boost/nowide/fstream.hpp>
#include <cstdarg>
#include <cstddef>
#include <ctime>
#include <excpt.h>
static std::string g_log_folder; static std::string g_log_folder;
static std::atomic<int> g_crash_log_count = 0; static std::atomic<int> g_crash_log_count = 0;
+2
View File
@@ -3,6 +3,8 @@
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include "StackWalker.h" #include "StackWalker.h"
#include <eh.h> #include <eh.h>
#include <cstddef>
#include <string>
class CBaseException : public CStackWalker class CBaseException : public CStackWalker
{ {
+3
View File
@@ -2,6 +2,9 @@
#include <strsafe.h> #include <strsafe.h>
//#include <atlconv.h> //#include <atlconv.h>
#include <dbghelp.h> #include <dbghelp.h>
#include <cstdarg>
#include <cstddef>
#include <cstring>
#pragma comment(lib, "version.lib") #pragma comment(lib, "version.lib")
#pragma comment( lib, "dbghelp.lib" ) #pragma comment( lib, "dbghelp.lib" )
+1
View File
@@ -2,6 +2,7 @@
#include <Windows.h> #include <Windows.h>
#include <tchar.h> #include <tchar.h>
#include <vector> #include <vector>
#include <cstddef>
namespace textconv_helper namespace textconv_helper
{ {
+3 -4
View File
@@ -42,6 +42,9 @@
#include <boost/uuid/uuid.hpp> #include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp> #include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp> #include <boost/uuid/uuid_io.hpp>
#include <cassert>
#include <iterator>
#include <string_view>
#ifdef WIN32 #ifdef WIN32
//FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream. //FIXME replace the two following includes with <boost/md5.hpp> after it becomes mainstream.
@@ -367,10 +370,6 @@ void AppConfig::set_defaults()
// restores the conventional CAD representation for users who expect it (x0kd). // restores the conventional CAD representation for users who expect it (x0kd).
if (get("design_connector_face_glyph").empty()) if (get("design_connector_face_glyph").empty())
set_bool("design_connector_face_glyph", true); set_bool("design_connector_face_glyph", true);
// Design tab: draw the printer bed and its plate grid (the Feature tree's Bed row).
if (get("design_show_bed").empty())
set_bool("design_show_bed", true);
#endif #endif
//#ifdef SUPPORT_SHOW_HINTS //#ifdef SUPPORT_SHOW_HINTS
@@ -3,6 +3,9 @@
#include <cstdio> #include <cstdio>
#include <boost/filesystem/path.hpp> #include <boost/filesystem/path.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
#include <algorithm>
#include <string>
#include <vector>
#ifdef WIN32 #ifdef WIN32
#include <psapi.h> #include <psapi.h>
File diff suppressed because it is too large Load Diff
+18 -16
View File
@@ -1,6 +1,7 @@
#include "libslic3r/CAD/GeometryEngine.hpp" #include "libslic3r/CAD/GeometryEngine.hpp"
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/I18N.hpp"
#include <BRepMesh_IncrementalMesh.hxx> #include <BRepMesh_IncrementalMesh.hxx>
#include <BRep_Tool.hxx> #include <BRep_Tool.hxx>
@@ -73,19 +74,19 @@ std::vector<TopoDS_Shape> GeometryEngine::read_step_solids(const std::string& pa
try { try {
STEPControl_Reader reader; STEPControl_Reader reader;
if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) { if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) {
err = "cannot read STEP file"; err = _u8L("cannot read STEP file");
return out; return out;
} }
reader.TransferRoots(); reader.TransferRoots();
const TopoDS_Shape shape = reader.OneShape(); const TopoDS_Shape shape = reader.OneShape();
if (shape.IsNull()) { err = "STEP file has no geometry"; return out; } if (shape.IsNull()) { err = _u8L("STEP file has no geometry"); return out; }
// One body per top-level solid; fall back to the whole shape (shells/faces) if none. // One body per top-level solid; fall back to the whole shape (shells/faces) if none.
for (TopExp_Explorer ex(shape, TopAbs_SOLID); ex.More(); ex.Next()) for (TopExp_Explorer ex(shape, TopAbs_SOLID); ex.More(); ex.Next())
out.push_back(ex.Current()); out.push_back(ex.Current());
if (out.empty()) if (out.empty())
out.push_back(shape); out.push_back(shape);
} catch (const Standard_Failure& e) { } catch (const Standard_Failure& e) {
err = *e.what() ? e.what() : "OCCT failed to read STEP"; err = *e.what() ? e.what() : _u8L("OCCT failed to read STEP");
out.clear(); out.clear();
} }
return out; return out;
@@ -115,7 +116,7 @@ TopoDS_Shape GeometryEngine::mesh_to_brep(const indexed_triangle_set& its,
if (tolerance <= 0.0) if (tolerance <= 0.0)
throw std::runtime_error("mesh_to_brep: tolerance must be > 0"); throw std::runtime_error("mesh_to_brep: tolerance must be > 0");
if (its.indices.empty()) if (its.indices.empty())
throw std::runtime_error("mesh_to_brep: mesh has no triangles"); throw std::runtime_error(_u8L("mesh_to_brep: mesh has no triangles"));
// 1. Tolerance-quantized vertex dedup. A merged vertex keeps the exact coordinates of the // 1. Tolerance-quantized vertex dedup. A merged vertex keeps the exact coordinates of the
// first input occurrence — vertices are grouped by a cell, never snapped onto its grid. // first input occurrence — vertices are grouped by a cell, never snapped onto its grid.
@@ -150,8 +151,8 @@ TopoDS_Shape GeometryEngine::mesh_to_brep(const indexed_triangle_set& its,
} }
stats.kept_tris = int(tris.size()); stats.kept_tris = int(tris.size());
if (tris.empty()) if (tris.empty())
throw std::runtime_error("mesh_to_brep: every triangle was rejected as degenerate " throw std::runtime_error(_u8L("mesh_to_brep: every triangle was rejected as degenerate "
"(try a smaller tolerance)"); "(try a smaller tolerance)"));
// 3. One face per triangle, sharing vertices and edges through the caches. // 3. One face per triangle, sharing vertices and edges through the caches.
std::vector<TopoDS_Vertex> vertex_cache(verts.size()); std::vector<TopoDS_Vertex> vertex_cache(verts.size());
@@ -361,7 +362,7 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
// A too-large radius (e.g. >= half the smallest spanned dimension) makes the // A too-large radius (e.g. >= half the smallest spanned dimension) makes the
// operation degenerate; OCCT leaves IsDone() false. Report it instead of // operation degenerate; OCCT leaves IsDone() false. Report it instead of
// silently returning the unfilleted solid (which reads as a false success). // silently returning the unfilleted solid (which reads as a false success).
if (!fillet.IsDone()) throw std::runtime_error("fillet radius too large for this geometry"); if (!fillet.IsDone()) throw std::runtime_error(_u8L("fillet radius too large for this geometry"));
return fillet.Shape(); return fillet.Shape();
} }
@@ -377,7 +378,7 @@ TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double dis
chamfer.Add(distance, edge); // symmetric chamfer chamfer.Add(distance, edge); // symmetric chamfer
chamfer.Build(); chamfer.Build();
if (!chamfer.IsDone()) throw std::runtime_error("chamfer distance too large for this geometry"); if (!chamfer.IsDone()) throw std::runtime_error(_u8L("chamfer distance too large for this geometry"));
return chamfer.Shape(); return chamfer.Shape();
} }
@@ -404,7 +405,7 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
} }
mk.Build(); mk.Build();
if (!mk.IsDone()) throw std::runtime_error("apply_fillet: OCCT fillet failed"); if (!mk.IsDone()) throw std::runtime_error(_u8L("apply_fillet: OCCT fillet failed"));
return mk.Shape(); return mk.Shape();
} }
@@ -421,7 +422,7 @@ TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double dis
} }
mk.Build(); mk.Build();
if (!mk.IsDone()) throw std::runtime_error("apply_chamfer: OCCT chamfer failed"); if (!mk.IsDone()) throw std::runtime_error(_u8L("apply_chamfer: OCCT chamfer failed"));
return mk.Shape(); return mk.Shape();
} }
@@ -547,12 +548,13 @@ GeometryEngine::MassProps GeometryEngine::mass_properties(const TopoDS_Shape& sh
std::string GeometryEngine::primitive_name(PrimitiveType type) std::string GeometryEngine::primitive_name(PrimitiveType type)
{ {
switch (type) { switch (type) {
case PrimitiveType::Box: return "Box"; // TRN Default name of an object created from the box primitive shape.
case PrimitiveType::Cylinder: return "Cylinder"; case PrimitiveType::Box: return _u8L("Box");
case PrimitiveType::Sphere: return "Sphere"; case PrimitiveType::Cylinder: return _u8L("Cylinder");
case PrimitiveType::Cone: return "Cone"; case PrimitiveType::Sphere: return _u8L("Sphere");
case PrimitiveType::Torus: return "Torus"; case PrimitiveType::Cone: return _u8L("Cone");
default: return "Unknown"; case PrimitiveType::Torus: return _u8L("Torus");
default: return _u8L("Unknown");
} }
} }
+30 -29
View File
@@ -1,6 +1,7 @@
#include "libslic3r/CAD/SketchEngine.hpp" #include "libslic3r/CAD/SketchEngine.hpp"
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/TriangleMesh.hpp" #include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/I18N.hpp"
#include <Standard_Handle.hxx> #include <Standard_Handle.hxx>
#include <GeomAbs_SurfaceType.hxx> #include <GeomAbs_SurfaceType.hxx>
@@ -138,7 +139,7 @@ Vec3d SketchPlane::to_world(const Vec2d& pt) const
TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
{ {
if (points.size() < 2) if (points.size() < 2)
throw std::runtime_error("Profile has fewer than 2 points"); throw std::runtime_error(_u8L("Profile has fewer than 2 points"));
BRepBuilderAPI_MakeWire builder; BRepBuilderAPI_MakeWire builder;
for (size_t i = 0; i < points.size(); ++i) { for (size_t i = 0; i < points.size(); ++i) {
@@ -150,7 +151,7 @@ TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
} }
builder.Build(); builder.Build();
if (!builder.IsDone()) if (!builder.IsDone())
throw std::runtime_error("Failed to build wire from profile"); throw std::runtime_error(_u8L("Failed to build wire from profile"));
return builder.Wire(); return builder.Wire();
} }
@@ -158,9 +159,9 @@ TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
TopoDS_Shape SketchEngine::make_prism(const TopoDS_Shape& base, const gp_Vec& vec) TopoDS_Shape SketchEngine::make_prism(const TopoDS_Shape& base, const gp_Vec& vec)
{ {
if (vec.Magnitude() < 1e-9) throw std::runtime_error("extrude depth is zero"); if (vec.Magnitude() < 1e-9) throw std::runtime_error(_u8L("extrude depth is zero"));
BRepPrimAPI_MakePrism prism(base, vec); BRepPrimAPI_MakePrism prism(base, vec);
if (!prism.IsDone()) throw std::runtime_error("extrude failed"); if (!prism.IsDone()) throw std::runtime_error(_u8L("extrude failed"));
return prism.Shape(); return prism.Shape();
} }
@@ -169,7 +170,7 @@ static TopoDS_Shape extrude_face_internal(const TopoDS_Face& face, const gp_Dir&
if (symmetric) { if (symmetric) {
gp_Vec halfVec = gp_Vec(dir) * (length / 2.0); gp_Vec halfVec = gp_Vec(dir) * (length / 2.0);
BRepAlgoAPI_Fuse fuse(SketchEngine::make_prism(face, halfVec), SketchEngine::make_prism(face, -halfVec)); BRepAlgoAPI_Fuse fuse(SketchEngine::make_prism(face, halfVec), SketchEngine::make_prism(face, -halfVec));
if (!fuse.IsDone()) throw std::runtime_error("Fuse failed"); if (!fuse.IsDone()) throw std::runtime_error(_u8L("Fuse failed"));
return fuse.Shape(); return fuse.Shape();
} }
return SketchEngine::make_prism(face, gp_Vec(dir) * length); return SketchEngine::make_prism(face, gp_Vec(dir) * length);
@@ -179,7 +180,7 @@ TopoDS_Shape SketchEngine::make_extrude(const TopoDS_Wire& wire, const SketchPla
double length, bool symmetric, double taper_deg) double length, bool symmetric, double taper_deg)
{ {
BRepBuilderAPI_MakeFace fm(wire); BRepBuilderAPI_MakeFace fm(wire);
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire"); if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
return make_extrude(fm.Face(), plane, length, symmetric, taper_deg); return make_extrude(fm.Face(), plane, length, symmetric, taper_deg);
} }
@@ -194,7 +195,7 @@ TopoDS_Shape SketchEngine::make_extrude_two_sided(const TopoDS_Wire& wire, const
double up, double down) double up, double down)
{ {
BRepBuilderAPI_MakeFace fm(wire); BRepBuilderAPI_MakeFace fm(wire);
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire"); if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
return make_extrude_two_sided(fm.Face(), plane, up, down); return make_extrude_two_sided(fm.Face(), plane, up, down);
} }
@@ -206,7 +207,7 @@ TopoDS_Shape SketchEngine::make_extrude_two_sided(const TopoDS_Face& face, const
if (d < 1e-9) return make_prism(face, gp_Vec(dir) * u); if (d < 1e-9) return make_prism(face, gp_Vec(dir) * u);
if (u < 1e-9) return make_prism(face, gp_Vec(dir) * -d); if (u < 1e-9) return make_prism(face, gp_Vec(dir) * -d);
BRepAlgoAPI_Fuse fuse(make_prism(face, gp_Vec(dir) * u), make_prism(face, gp_Vec(dir) * -d)); BRepAlgoAPI_Fuse fuse(make_prism(face, gp_Vec(dir) * u), make_prism(face, gp_Vec(dir) * -d));
if (!fuse.IsDone()) throw std::runtime_error("two-sided extrude fuse failed"); if (!fuse.IsDone()) throw std::runtime_error(_u8L("two-sided extrude fuse failed"));
return fuse.Shape(); return fuse.Shape();
} }
@@ -216,7 +217,7 @@ TopoDS_Shape SketchEngine::make_extrude_taper(const TopoDS_Wire& wire, const Ske
gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z()); gp_Dir dir(plane.normal.x(), plane.normal.y(), plane.normal.z());
auto straight = [&]() -> TopoDS_Shape { auto straight = [&]() -> TopoDS_Shape {
BRepBuilderAPI_MakeFace fm(wire); BRepBuilderAPI_MakeFace fm(wire);
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire"); if (!fm.IsDone()) throw std::runtime_error(_u8L("Failed to make face from wire"));
return make_prism(fm.Face(), gp_Vec(dir) * length); return make_prism(fm.Face(), gp_Vec(dir) * length);
}; };
if (std::abs(taper_deg) >= 89.0 || std::abs(length) < 1e-9) return straight(); if (std::abs(taper_deg) >= 89.0 || std::abs(length) < 1e-9) return straight();
@@ -262,7 +263,7 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
const SketchPlane& plane, double length, bool symmetric) const SketchPlane& plane, double length, bool symmetric)
{ {
// The loop below skips regions that fail, so a zero depth is rejected before it. // The loop below skips regions that fail, so a zero depth is rejected before it.
if (std::abs(length) < 1e-9) throw std::runtime_error("extrude depth is zero"); if (std::abs(length) < 1e-9) throw std::runtime_error(_u8L("extrude depth is zero"));
// Drop consecutive coincident points and the closing duplicate. FreeType / // Drop consecutive coincident points and the closing duplicate. FreeType /
// SVG flattening routinely emits repeated points which would build a // SVG flattening routinely emits repeated points which would build a
@@ -348,7 +349,7 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
} }
} }
if (count == 0) throw std::runtime_error("imported regions produced no extrudable geometry"); if (count == 0) throw std::runtime_error(_u8L("imported regions produced no extrudable geometry"));
return count == 1 ? last : TopoDS_Shape(comp); // avoid a compound-of-one return count == 1 ? last : TopoDS_Shape(comp); // avoid a compound-of-one
} }
@@ -356,7 +357,7 @@ TopoDS_Shape SketchEngine::make_revolve(const TopoDS_Wire& wire, const gp_Ax1& a
{ {
BRepBuilderAPI_MakeFace faceMaker(wire); BRepBuilderAPI_MakeFace faceMaker(wire);
if (!faceMaker.IsDone()) if (!faceMaker.IsDone())
throw std::runtime_error("Failed to make face from wire"); throw std::runtime_error(_u8L("Failed to make face from wire"));
TopoDS_Face face = faceMaker.Face(); TopoDS_Face face = faceMaker.Face();
// A profile on both sides of the axis sweeps through itself; MakeRevol then fails with no // A profile on both sides of the axis sweeps through itself; MakeRevol then fails with no
@@ -387,7 +388,7 @@ TopoDS_Shape SketchEngine::make_revolve(const TopoDS_Wire& wire, const gp_Ax1& a
if (angle_rad < 0) { axis.Reverse(); angle_rad = -angle_rad; } if (angle_rad < 0) { axis.Reverse(); angle_rad = -angle_rad; }
BRepPrimAPI_MakeRevol rev(face, axis, angle_rad); BRepPrimAPI_MakeRevol rev(face, axis, angle_rad);
if (!rev.IsDone()) if (!rev.IsDone())
throw std::runtime_error("Failed to revolve"); throw std::runtime_error(_u8L("Failed to revolve"));
if (!BRepCheck_Analyzer(rev.Shape()).IsValid()) if (!BRepCheck_Analyzer(rev.Shape()).IsValid())
throw std::runtime_error("the profile crosses the revolve axis — it must lie on one side of it"); throw std::runtime_error("the profile crosses the revolve axis — it must lie on one side of it");
return rev.Shape(); return rev.Shape();
@@ -397,29 +398,29 @@ TopoDS_Shape SketchEngine::make_sweep(const TopoDS_Wire& profile, const TopoDS_W
{ {
BRepBuilderAPI_MakeFace faceMaker(profile); BRepBuilderAPI_MakeFace faceMaker(profile);
if (!faceMaker.IsDone()) if (!faceMaker.IsDone())
throw std::runtime_error("Failed to make face from sweep profile"); throw std::runtime_error(_u8L("Failed to make face from sweep profile"));
TopoDS_Face face = faceMaker.Face(); TopoDS_Face face = faceMaker.Face();
BRepOffsetAPI_MakePipe pipe(path, face); BRepOffsetAPI_MakePipe pipe(path, face);
pipe.Build(); pipe.Build();
if (!pipe.IsDone()) if (!pipe.IsDone())
throw std::runtime_error("Failed to sweep profile along path"); throw std::runtime_error(_u8L("Failed to sweep profile along path"));
return pipe.Shape(); return pipe.Shape();
} }
TopoDS_Shape SketchEngine::make_loft(const std::vector<TopoDS_Wire>& profiles, bool ruled) TopoDS_Shape SketchEngine::make_loft(const std::vector<TopoDS_Wire>& profiles, bool ruled)
{ {
if (profiles.size() < 2) if (profiles.size() < 2)
throw std::runtime_error("loft needs at least 2 profiles"); throw std::runtime_error(_u8L("loft needs at least 2 profiles"));
BRepOffsetAPI_ThruSections loft(true /*solid*/, ruled); BRepOffsetAPI_ThruSections loft(true /*solid*/, ruled);
for (const TopoDS_Wire& w : profiles) { for (const TopoDS_Wire& w : profiles) {
if (w.IsNull()) throw std::runtime_error("loft: null profile wire"); if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
loft.AddWire(w); loft.AddWire(w);
} }
loft.Build(); loft.Build();
if (!loft.IsDone()) throw std::runtime_error("loft failed"); if (!loft.IsDone()) throw std::runtime_error(_u8L("loft failed"));
TopoDS_Shape s = loft.Shape(); TopoDS_Shape s = loft.Shape();
if (s.IsNull()) throw std::runtime_error("loft produced no solid"); if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no solid"));
return s; return s;
} }
@@ -427,16 +428,16 @@ TopoDS_Shape SketchEngine::make_loft(const std::vector<TopoDS_Wire>& profiles, b
TopoDS_Shape SketchEngine::make_loft_surface(const std::vector<TopoDS_Wire>& profiles, bool ruled) TopoDS_Shape SketchEngine::make_loft_surface(const std::vector<TopoDS_Wire>& profiles, bool ruled)
{ {
if (profiles.size() < 2) if (profiles.size() < 2)
throw std::runtime_error("loft needs at least 2 profiles"); throw std::runtime_error(_u8L("loft needs at least 2 profiles"));
BRepOffsetAPI_ThruSections loft(false /*shell, no end caps*/, ruled); BRepOffsetAPI_ThruSections loft(false /*shell, no end caps*/, ruled);
for (const TopoDS_Wire& w : profiles) { for (const TopoDS_Wire& w : profiles) {
if (w.IsNull()) throw std::runtime_error("loft: null profile wire"); if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
loft.AddWire(w); loft.AddWire(w);
} }
loft.Build(); loft.Build();
if (!loft.IsDone()) throw std::runtime_error("loft failed"); if (!loft.IsDone()) throw std::runtime_error(_u8L("loft failed"));
TopoDS_Shape s = loft.Shape(); TopoDS_Shape s = loft.Shape();
if (s.IsNull()) throw std::runtime_error("loft produced no shape"); if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no shape"));
return s; return s;
} }
@@ -444,11 +445,11 @@ TopoDS_Shape SketchEngine::make_pocket(const TopoDS_Wire& wire, const SketchPlan
const TopoDS_Shape& target, double depth) const TopoDS_Shape& target, double depth)
{ {
BRepBuilderAPI_MakeFace fm(wire); BRepBuilderAPI_MakeFace fm(wire);
if (!fm.IsDone()) throw std::runtime_error("Pocket face failed"); if (!fm.IsDone()) throw std::runtime_error(_u8L("Pocket face failed"));
TopoDS_Shape tool = extrude_face_internal(fm.Face(), TopoDS_Shape tool = extrude_face_internal(fm.Face(),
gp_Dir(plane.normal.x(), plane.normal.y(), plane.normal.z()), depth + 1.0, false); gp_Dir(plane.normal.x(), plane.normal.y(), plane.normal.z()), depth + 1.0, false);
BRepAlgoAPI_Cut cut(target, tool); BRepAlgoAPI_Cut cut(target, tool);
if (!cut.IsDone()) throw std::runtime_error("Pocket cut failed"); if (!cut.IsDone()) throw std::runtime_error(_u8L("Pocket cut failed"));
return cut.Shape(); return cut.Shape();
} }
@@ -929,7 +930,7 @@ static TopoDS_Face checked_profile_face(const TopoDS_Face& f)
TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires, TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
const SketchPlane& plane) const SketchPlane& plane)
{ {
if (wires.empty()) throw std::runtime_error("sketch has no closed loop"); if (wires.empty()) throw std::runtime_error(_u8L("sketch has no closed loop"));
// The ASSEMBLED face below is built on the SKETCH's own plane rather than on a surface OCCT // The ASSEMBLED face below is built on the SKETCH's own plane rather than on a surface OCCT
// infers from the outer wire. The inferred plane has no reason to share the sketch's normal, // infers from the outer wire. The inferred plane has no reason to share the sketch's normal,
@@ -944,7 +945,7 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
if (wires.size() == 1) { if (wires.size() == 1) {
BRepBuilderAPI_MakeFace fm(wires[0]); BRepBuilderAPI_MakeFace fm(wires[0]);
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face"); if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
return checked_profile_face(fm.Face()); return checked_profile_face(fm.Face());
} }
@@ -956,7 +957,7 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
areas.reserve(wires.size()); areas.reserve(wires.size());
for (const TopoDS_Wire& w : wires) { for (const TopoDS_Wire& w : wires) {
BRepBuilderAPI_MakeFace fm(w); BRepBuilderAPI_MakeFace fm(w);
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face"); if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
faces.push_back(fm.Face()); faces.push_back(fm.Face());
GProp_GProps props; GProp_GProps props;
BRepGProp::SurfaceProperties(faces.back(), props); BRepGProp::SurfaceProperties(faces.back(), props);
@@ -982,10 +983,10 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
got = true; got = true;
break; break;
} }
if (!got) throw std::runtime_error("sketch loop does not bound a face"); if (!got) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
BRepClass_FaceClassifier fc(faces[outer], p, 1e-7); BRepClass_FaceClassifier fc(faces[outer], p, 1e-7);
if (fc.State() != TopAbs_IN) if (fc.State() != TopAbs_IN)
throw std::runtime_error("sketch has two disjoint regions; put each in its own sketch"); throw std::runtime_error(_u8L("sketch has two disjoint regions; put each in its own sketch"));
// Add the hole loop AS-IS and let ShapeFix_Face sort the orientations out below. // Add the hole loop AS-IS and let ShapeFix_Face sort the orientations out below.
// Reversing it here only works when the sketch happened to wind both loops the same // Reversing it here only works when the sketch happened to wind both loops the same
// way: a circle drawn clockwise inside a counter-clockwise rectangle comes out matching // way: a circle drawn clockwise inside a counter-clockwise rectangle comes out matching
@@ -995,7 +996,7 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
// 147520 — a body larger than its own bounding box, which is the signature of it. // 147520 — a body larger than its own bounding box, which is the signature of it.
fm.Add(wires[i]); fm.Add(wires[i]);
} }
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face"); if (!fm.IsDone()) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
// Winding-independent classification of outer vs holes — the same idiom make_extrude_regions // Winding-independent classification of outer vs holes — the same idiom make_extrude_regions
// already uses for imported glyphs, which is why holed TEXT extruded correctly all along // already uses for imported glyphs, which is why holed TEXT extruded correctly all along
// while a holed SKETCH did not. // while a holed SKETCH did not.
+1
View File
@@ -43,6 +43,7 @@
#include "libslic3r/AABBTreeLines.hpp" // search structure for found close points #include "libslic3r/AABBTreeLines.hpp" // search structure for found close points
#include "libslic3r/Line.hpp" #include "libslic3r/Line.hpp"
#include "libslic3r/BoundingBox.hpp" #include "libslic3r/BoundingBox.hpp"
#include <sstream>
// Experimentaly suggested ration of font ascent by multiple fonts // Experimentaly suggested ration of font ascent by multiple fonts
// to get approx center of normal text line // to get approx center of normal text line
@@ -28,6 +28,7 @@
#include "FuzzySkin.hpp" #include "FuzzySkin.hpp"
#include "libnoise/noise.h" #include "libnoise/noise.h"
#include <functional>
// #define DEBUG_FUZZY // #define DEBUG_FUZZY
+8
View File
@@ -301,6 +301,9 @@ struct SurfaceFillParams
float skin_infill_depth = 0; float skin_infill_depth = 0;
bool symmetric_infill_y_axis = false; bool symmetric_infill_y_axis = false;
// Top fill for 3D honeycomb
bool infill_complete_top = false;
// Params for Lateral honeycomb // Params for Lateral honeycomb
float infill_overhang_angle = 60.f; float infill_overhang_angle = 60.f;
@@ -344,6 +347,7 @@ struct SurfaceFillParams
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_1); RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_1);
RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2); RETURN_COMPARE_NON_EQUAL(lateral_lattice_angle_2);
RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis); RETURN_COMPARE_NON_EQUAL(symmetric_infill_y_axis);
RETURN_COMPARE_NON_EQUAL(infill_complete_top);
RETURN_COMPARE_NON_EQUAL(infill_lock_depth); RETURN_COMPARE_NON_EQUAL(infill_lock_depth);
RETURN_COMPARE_NON_EQUAL(skin_infill_depth); RETURN_COMPARE_NON_EQUAL(skin_infill_depth);
RETURN_COMPARE_NON_EQUAL(infill_overhang_angle); RETURN_COMPARE_NON_EQUAL(infill_overhang_angle);
@@ -931,6 +935,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis;
} else if (params.pattern == ipZigZag) { } else if (params.pattern == ipZigZag) {
params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis; params.symmetric_infill_y_axis = region_config.symmetric_infill_y_axis;
} else if (params.pattern == ip3DHoneycomb) {
params.infill_complete_top = region_config.infill_complete_top;
} }
if (surface.is_solid()) { if (surface.is_solid()) {
@@ -1430,6 +1436,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
} else if (surface_fill.params.pattern == ipZigZag) { } else if (surface_fill.params.pattern == ipZigZag) {
params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis; params.symmetric_infill_y_axis = surface_fill.params.symmetric_infill_y_axis;
} else if (surface_fill.params.pattern == ip3DHoneycomb) {
params.infill_complete_top = surface_fill.params.infill_complete_top;
} }
if (surface_fill.params.pattern == ipGrid) if (surface_fill.params.pattern == ipGrid)
params.can_reverse = false; params.can_reverse = false;
+243 -127
View File
@@ -61,7 +61,7 @@ static coordf_t troctWave(coordf_t pos, coordf_t gridSize, coordf_t Zpos)
// Identify the important points of curve change within a truncated // Identify the important points of curve change within a truncated
// octahedron wave (as waveform fraction t): // octahedron wave (as waveform fraction t):
// 1. Start of wave (always 0.0) // 1. Start of wave (always 0.0; not needed if the pattern base starts here)
// 2. Transition to upper "horizontal" part // 2. Transition to upper "horizontal" part
// 3. Transition from upper "horizontal" part // 3. Transition from upper "horizontal" part
// 4. Transition to lower "horizontal" part // 4. Transition to lower "horizontal" part
@@ -73,17 +73,11 @@ static coordf_t troctWave(coordf_t pos, coordf_t gridSize, coordf_t Zpos)
* \ / * \ /
* o---o * o---o
*/ */
static std::vector<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize) static std::vector<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize)
{ {
std::vector<coordf_t> res = {0.}; std::vector<coordf_t> res;
coordf_t perpOffset = abs(triWave(Zpos, gridSize) / 2.); coordf_t perpOffset = abs(triWave(Zpos, gridSize) / 2.);
coordf_t normalisedOffset = perpOffset / gridSize; coordf_t normalisedOffset = perpOffset / gridSize;
// // for debugging: just generate evenly-distributed points
// for(coordf_t i = 0; i < 2; i += 0.05){
// res.push_back(gridSize * i);
// }
// note: 0 == straight line
if(normalisedOffset > 0){ if(normalisedOffset > 0){
res.push_back(gridSize * (0. + normalisedOffset)); res.push_back(gridSize * (0. + normalisedOffset));
res.push_back(gridSize * (1. - normalisedOffset)); res.push_back(gridSize * (1. - normalisedOffset));
@@ -93,113 +87,251 @@ static std::vector<coordf_t> getCriticalPoints(coordf_t Zpos, coordf_t gridSize)
return(res); return(res);
} }
// Generate an array of points that are in the same direction as the // Add additional dense fill in line with the pattern direction to
// basic printing line (i.e. Y points for columns, X points for rows) // cover the top squares of the pattern
// Note: a negative offset only causes a change in the perpendicular static Polylines addTops(coordf_t Zpos, coordf_t gridSize, coordf_t lengthX, coordf_t lengthY, coordf_t spacing,
// direction size_t multiline_count, size_t topDistance)
static std::vector<coordf_t> colinearPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
const size_t baseLocation, size_t gridLength)
{ {
std::vector<coordf_t> points; coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
points.push_back(baseLocation); coordf_t zHalfCycle = fmod(zCycle, 0.5) * 2.;
for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= (gridSize*2)) { bool printVert = zCycle < 0.5;
for(size_t pi = 0; pi < critPoints.size(); pi++){ coordf_t offsetX = multiline_count;
points.push_back(baseLocation + cLoc + critPoints[pi]); coordf_t offsetY = multiline_count;
coordf_t perpOffset = abs(triWave(Zpos, gridSize) / 2.);
coordf_t gridPoint = gridSize * (0. + perpOffset / gridSize);
coordf_t topOffset = gridSize / 2.0 - abs(troctWave(gridPoint, gridSize, Zpos));
coordf_t multilineAdjust = (sqrt(2) - 1.0) / 2.;
Polylines lines;
size_t pointCount = 0;
coordf_t gridStartL = gridSize * 0.5 - topOffset;
coordf_t gridEndL = gridSize * 0.5 + topOffset;
if((topDistance == 0) && (multiline_count == 1)){
// extend out a little bit on the first layer to help fuse the cover
gridStartL -= spacing;
gridEndL += spacing;
} else if(multiline_count > 1) {
// match start point to the corner edge
gridStartL -= spacing * multiline_count * multilineAdjust;
gridEndL += spacing * multiline_count * multilineAdjust;
}
// top cover extents perpendicular to the direction of travel
coordf_t gridStartP = gridSize * 0.5 - topOffset + spacing * multiline_count / 2. + spacing / 2.;
coordf_t gridEndP = gridSize * 0.5 + topOffset - spacing * multiline_count / 2. - spacing / 2.;
coordf_t x, y;
int xm, ym;
// if the print direction needs to be rotated, then swap the extents
if((topDistance % 2) == 0){
std::swap(gridStartL, gridStartP);
std::swap(gridEndL, gridEndP);
}
// adjust spacing so that it starts and ends on exactly the right place
// and increase fill density slightly to reduce gaps
coordf_t region_count = floor((gridEndP - gridStartP) / (spacing / sqrt(2)));
if(region_count <= 0){
return lines;
}
spacing = (gridEndP - gridStartP) / region_count;
for (x = offsetX, xm = 0; x <= (lengthX); x+= gridSize, xm = xm ^ 1) {
for (y = offsetY, ym = 0; y <= (lengthY); y += gridSize, ym = ym ^ 1) {
if(((xm ^ ym) == 1) == printVert){
continue;
}
// // For debugging: remove 0,0 -> 1,1 top to help understand orientation
// if((x <= (gridSize + EPSILON)) && (y <= (gridSize + EPSILON)) && ((y - x) < EPSILON)){
// continue;
// }
Polyline newPoints;
int dirMod = xm ^ ym;
if(printVert == (topDistance % 2)){
if(y < (lengthY - spacing * multiline_count * 1.5)){
coordf_t endPMod = std::min(lengthX - (multiline_count * (spacing + 1) / 2.), x + gridEndP) - x;
coordf_t endLMod = std::min(lengthY - (multiline_count * (spacing + 1) / 2.), y + gridEndL) - y;
for(coordf_t xi = gridStartP; xi < (endPMod + EPSILON); xi += spacing, dirMod = dirMod ^ 1){
newPoints.points.push_back((dirMod == 0) ? Point(x + xi, y + gridStartL) : Point(x + xi, y + endLMod));
newPoints.points.push_back((dirMod == 0) ? Point(x + xi, y + endLMod) : Point(x + xi, y + gridStartL));
pointCount += 2;
}
}
} else {
if(x < (lengthX - spacing * multiline_count * 1.5)){
coordf_t endPMod = std::min(lengthY - (multiline_count * (spacing + 1) / 2.), y + gridEndP) - y;
coordf_t endLMod = std::min(lengthX - (multiline_count * (spacing + 1) / 2.), x + gridEndL) - x;
for(coordf_t yi = gridStartP; yi < (endPMod + EPSILON); yi += spacing, dirMod = dirMod ^ 1){
newPoints.points.push_back((dirMod == 0) ? Point(x + gridStartL, y + yi) : Point(x + endLMod, y + yi));
newPoints.points.push_back((dirMod == 0) ? Point(x + endLMod, y + yi) : Point(x + gridStartL, y + yi));
pointCount += 2;
}
}
}
lines.push_back(newPoints);
} }
} }
points.push_back(gridLength); return lines;
return points;
} }
// Generate an array of points for the dimension that is perpendicular to // Generate a set of polylines that complete octahedron curves on the
// the basic printing line (i.e. X points for columns, Y points for rows) // extremities of a pattern
static std::vector<coordf_t> perpendPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints, static Polylines makeEndPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
size_t baseLocation, size_t gridLength, coordf_t lengthX, coordf_t lengthY, coordf_t spacing, size_t multiline_count)
size_t offsetBase, coordf_t perpDir)
{ {
std::vector<coordf_t> points; Polylines lines;
points.push_back(offsetBase); coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= gridSize*2) { bool printVert = zCycle < 0.5;
for(size_t pi = 0; pi < critPoints.size(); pi++){ bool printHoriz = zCycle >= 0.5;
int zFlipOffset = ((sgn(fmod(zCycle, 0.5) - 0.25) > 0) == printVert) ? 0 : 1;
// create templates for copying
Polylines startLines, endLines;
for(size_t li = 0; li < multiline_count; li++){
coordf_t oAdj = (li - ((multiline_count - 1) / 2.)) * spacing; // orthogonal line adjustment
coordf_t dAdj = oAdj * sqrt(2); // diagonal line adjustment
Polyline startLine, endLine;
// Left Bottom; Bottom Left
startLine.points.push_back(printHoriz ? Point(oAdj, -dAdj) : Point(-dAdj, oAdj));
// Right Bottom; Top Left
endLine.points.push_back(printHoriz ? Point(-oAdj, -dAdj) : Point(-dAdj, -oAdj));
for(size_t pi = 0; pi < 2; pi++){
int pDir = pi * 2 - 1;
coordf_t pAdj = pDir * (sqrt(2) - 1) * oAdj;
coordf_t troctOffset = abs(troctWave(critPoints[pi], gridSize, Zpos));
startLine.points.push_back(printHoriz ?
Point(-troctOffset, critPoints[pi] + pAdj) :
Point(critPoints[pi] + pAdj, -troctOffset));
endLine.points.push_back(printHoriz ?
Point(troctOffset, critPoints[pi] + pAdj) :
Point(critPoints[pi] + pAdj, troctOffset));
}
// Left Top; Bottom Right
startLine.points.push_back(printHoriz ? Point(oAdj, gridSize + dAdj) : Point(gridSize + dAdj, oAdj));
// Right Top; Top Right
endLine.points.push_back(printHoriz ? Point(-oAdj, gridSize + dAdj) : Point(gridSize + dAdj, -oAdj));
startLines.push_back(startLine);
endLines.push_back(endLine);
}
coordf_t gridMaxX = ceil((lengthX - EPSILON) / gridSize) * gridSize;
coordf_t gridMaxY = ceil((lengthY - EPSILON) / gridSize) * gridSize;
for(size_t li = 0; li < multiline_count; li++){
coordf_t mlFactor = (li - ((multiline_count - 1) / 2.)) * spacing;
for (coordf_t cLoc = zFlipOffset * gridSize; cLoc < ((printHoriz ? gridMaxY : gridMaxX) - EPSILON); cLoc += gridSize * 2) {
Polyline tsLine(startLines[li]);
Polyline teLine(endLines[li]);
tsLine.translate(printVert ? Point(cLoc, -mlFactor) : Point(-mlFactor, cLoc));
teLine.translate(printVert ? Point(cLoc, gridMaxY + mlFactor) : Point(gridMaxX + mlFactor, cLoc));
lines.push_back(tsLine);
lines.push_back(teLine);
}
}
return lines;
}
// Generate a polyline that describes a single path segment through
// the infill in the same direction as the basic printing line (i.e. X
// points for columns, Y points for rows)
static Polyline patternPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
coordf_t gridLength, coordf_t perpDir, int print_dir, coordf_t oAdj)
{
Polyline line;
coordf_t dAdj = oAdj * (sqrt(2) - 1); // additional diagonal adjustment
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
int zFlipDirection = sgn(fmod(zCycle, 0.5) - 0.25);
bool hitEnd = false;
int endPi = -1;
size_t pi = 0;
size_t piOfs = 0;
line.points.push_back((print_dir == 1) ? Point(dAdj, 0.) : Point(0., dAdj));
coordf_t gridMax = ceil((gridLength - EPSILON) / gridSize) * gridSize;
for (coordf_t cLoc = 0; cLoc < gridMax; cLoc += gridSize, piOfs = (piOfs + 2) % 4) {
for(pi = piOfs; pi < (piOfs + 2); pi++){
coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos); coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos);
points.push_back(offsetBase + (offset * perpDir)); coordf_t offsetFlip = sgn(offset);
coordf_t posFlip = floor(((pi + 1) % 4) / 2) * 2 - 1;
coordf_t posLin = cLoc - (piOfs * gridSize / 2.) + critPoints[pi];
coordf_t posPerp = offset * perpDir;
line.points.push_back((print_dir == 1) ?
Point(posPerp, posLin + posFlip * dAdj * perpDir * zFlipDirection * print_dir) :
Point(posLin + posFlip * dAdj * perpDir * zFlipDirection * print_dir, posPerp));
} }
} }
points.push_back(offsetBase); line.points.push_back((print_dir == 1) ? Point(dAdj, gridMax) : Point(gridMax, dAdj));
return points; return line;
}
static inline Pointfs zip(const std::vector<coordf_t> &x, const std::vector<coordf_t> &y)
{
assert(x.size() == y.size());
Pointfs out;
out.reserve(x.size());
for (size_t i = 0; i < x.size(); ++ i)
out.push_back(Vec2d(x[i], y[i]));
return out;
} }
// Generate a set of curves (array of array of 2d points) that describe a // Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron. // horizontal slice of a truncated regular octahedron.
static std::vector<Pointfs> makeActualGrid(coordf_t Zpos, coordf_t gridSize, size_t boundsX, size_t boundsY) static Polylines makeZigZag(coordf_t Zpos, coordf_t gridSize, coordf_t lengthX, coordf_t lengthY,
coordf_t spacing, size_t multiline_count)
{ {
std::vector<Pointfs> points; Polylines lines;
std::vector<coordf_t> critPoints = getCriticalPoints(Zpos, gridSize); std::vector<coordf_t> critPoints = getCriticalPoints(Zpos, gridSize);
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.); coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
bool printVert = zCycle < 0.5; bool printVert = zCycle < 0.5;
if (printVert) { BoundingBox extents;
int perpDir = -1; int perpDir = -1;
for (coordf_t x = 0; x <= (boundsX); x+= gridSize, perpDir *= -1) { int perpDirPattern = -1;
points.push_back(Pointfs()); coordf_t gridMax = ceil(((printVert ? lengthX : lengthY) - EPSILON) / gridSize) * gridSize;
Pointfs &newPoints = points.back(); for (coordf_t pPos = 0; pPos < gridMax; pPos += gridSize, perpDirPattern *= -1) {
newPoints = zip( for (size_t li = 0; li < multiline_count; li++){
perpendPoints(Zpos, gridSize, critPoints, 0, boundsY, x, perpDir), coordf_t oAdj = (li - ((multiline_count - 1) / 2.)) * spacing; // orthogonal line adjustment
colinearPoints(Zpos, gridSize, critPoints, 0, boundsY)); Polyline newPoints;
newPoints = patternPoints(Zpos, gridSize, critPoints,
printVert ? lengthY : lengthX,
perpDirPattern, printVert ? 1 : -1, oAdj);
if (perpDir == 1) if (perpDir == 1)
std::reverse(newPoints.begin(), newPoints.end()); std::reverse(newPoints.points.begin(), newPoints.points.end());
} newPoints.translate(printVert ? Point(pPos + oAdj, 0.) : Point(0., pPos + oAdj));
} else { extents.merge(newPoints.points);
int perpDir = 1; lines.push_back(newPoints);
for (coordf_t y = gridSize; y <= (boundsY); y+= gridSize, perpDir *= -1) { perpDir *= -1;
points.push_back(Pointfs());
Pointfs &newPoints = points.back();
newPoints = zip(
colinearPoints(Zpos, gridSize, critPoints, 0, boundsX),
perpendPoints(Zpos, gridSize, critPoints, 0, boundsX, y, perpDir));
if (perpDir == -1)
std::reverse(newPoints.begin(), newPoints.end());
} }
} }
return points; return lines;
} }
// Generate a set of curves (array of array of 2d points) that describe a // Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron with a specified // horizontal slice of a truncated regular octahedron with a specified
// grid square size. // grid square size.
// gridWidth and gridHeight define the width and height of the bounding box respectively // gridWidth and gridHeight define the width and height of the bounding box respectively
static Polylines makeGrid(coordf_t z, coordf_t gridSize, coordf_t boundWidth, coordf_t boundHeight, bool fillEvenly) // Note: this uses the 'complete' infill parameter to determine if the
// square tops should be enclosed (true) or open (false). Alternatively,
// a rotation angle of 180 degrees or greater can be used.
static Polylines makeGrid(coordf_t z, coordf_t zLast, coordf_t gridSize,
coordf_t lengthX, coordf_t lengthY,
bool completeTops, coordf_t spacing, size_t multiline_count, size_t layer_count)
{ {
std::vector<Pointfs> polylines = makeActualGrid(z, gridSize, boundWidth, boundHeight); coordf_t zCycle = fmod(z + gridSize/2, gridSize * 2.) / (gridSize * 2.);
bool printVert = zCycle < 0.5;
coordf_t zCycleLast = fmod(zLast + gridSize/2, gridSize * 2.) / (gridSize * 2.);
bool printVertLast = zCycleLast < 0.5;
Polylines result; Polylines result;
result.reserve(polylines.size()); Polylines polyZag = makeZigZag(z, gridSize, lengthX, lengthY, spacing, multiline_count);
for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin(); result.insert(result.end(), polyZag.begin(), polyZag.end());
it_polylines != polylines.end(); ++ it_polylines) { // add end connectors
result.push_back(Polyline()); std::vector<coordf_t> critPoints = getCriticalPoints(z, gridSize);
Polyline &polyline = result.back(); Polylines endPoints = makeEndPoints(z, gridSize, critPoints, lengthX, lengthY, spacing, multiline_count);
for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it) result.insert(result.end(), endPoints.begin(), endPoints.end());
polyline.points.push_back(Point(coord_t((*it)(0)), coord_t((*it)(1)))); // add tops for the first <multiline_count> layers in each cycle
if(completeTops && (printVert != printVertLast)){
coordf_t layerHeight = (z - zLast) / (multiline_count * layer_count);
size_t top_distance = 0;
for(coordf_t zCheck = z; zCheck >= (zLast + EPSILON); zCheck -= layerHeight * layer_count, top_distance++){
coordf_t zCheckCycle = fmod(zCheck + gridSize/2, gridSize * 2.) / (gridSize * 2.);
if(printVert != (zCheckCycle < 0.5)){
break;
}
}
Polylines polytops = addTops(z, gridSize, lengthX, lengthY, spacing, multiline_count, top_distance);
result.insert(result.end(), polytops.begin(), polytops.end());
} }
return result; return result;
} }
// FillParams has the following useful information: // FillParams has the following useful information:
// density <0 .. 1> [proportion of space to fill] // density <0 .. 1> [proportion of space to fill]
// anchor_length [???]
// anchor_length_max [???]
// dont_connect() [avoid connect lines] // dont_connect() [avoid connect lines]
// dont_adjust [avoid filling space evenly] // dont_adjust [avoid filling space evenly]
// monotonic [fill strictly left to right] // monotonic [fill strictly left to right]
// complete [complete each loop] // complete [complete each loop]
// multiline [number of lines to draw for each pattern line]
// complete_top [should the top surfaces of the pattern be filled]
void Fill3DHoneycomb::_fill_surface_single( void Fill3DHoneycomb::_fill_surface_single(
const FillParams &params, const FillParams &params,
@@ -208,78 +340,62 @@ void Fill3DHoneycomb::_fill_surface_single(
ExPolygon expolygon, ExPolygon expolygon,
Polylines &polylines_out) Polylines &polylines_out)
{ {
// no rotation is supported for this infill pattern
// Support infill angle // Support infill angle
auto infill_angle = float(this->angle); auto infill_angle = float(this->angle);
if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle); if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle);
BoundingBox bb = expolygon.contour.bounding_box(); BoundingBox bb = expolygon.contour.bounding_box();
// Expand the bounding box to avoid artifacts at the edges // Increase the bounding box outwards to avoid edge clipping artefacts
coord_t expand = 5 * (scale_(this->spacing)); coord_t expandSize = 5. * scale_(this->spacing);
bb.offset(expand); bb.offset(expandSize);
// Adjustment for combining infill setting
size_t layersPerSlice = 1;
if(thickness_layers > 0){
layersPerSlice = thickness_layers;
}
// Note: with equally-scaled X/Y/Z, the pattern will create a vertically-stretched // Note: with equally-scaled X/Y/Z, the pattern will create a vertically-stretched
// truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2) // truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2)
coordf_t zScale = sqrt(2); coordf_t zScale = sqrt(2);
// adjustment to account for the additional distance of octagram curves // Density adjustment to account for the additional distance of
// note: this only strictly applies for a rectangular area where the total // octagram curves. [This only strictly applies for a rectangular
// Z travel distance is a multiple of the spacing... but it should // area where the total Z travel distance is a multiple of the
// be at least better than the prevous estimate which assumed straight // spacing]
// lines
// = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25) // = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25)
// = (sqrt(2) + 1) / 2 [... I think] // = (sqrt(2) + 1) / 2 [... I think]
// make a first guess at the preferred grid Size // make a first guess at the preferred grid Size (in unscaled units)
coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density); coordf_t gridSize = (scale_(this->spacing) *
((zScale + 1.) / 2.) * params.multiline / params.density);
// This density calculation is incorrect for many values > 25%, possibly coordf_t layerHeight = scale_(params.layer_height);
// due to quantisation error, so this value is used as a first guess, then the
// Z scale is adjusted to make the layer patterns consistent / symmetric
// This means that the resultant infill won't be an ideal truncated octahedron,
// but it should look better than the equivalent quantised version
//Orca: uses a fixed layer height to avoid inconsistent bridges and variable layer height artifacts.
//coordf_t layerHeight = scale_(thickness_layers);
coordf_t layerHeight = scale_(1.0);
// ceiling to an integer value of layers per Z
// (with a little nudge in case it's close to perfect)
coordf_t layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05); coordf_t layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05);
if(params.density > 0.42){ // exact layer pattern for >42% density // If a density over 42% is requested, set an exact layer pattern
if((params.density > 0.42) || (layersPerModule < 2)){
layersPerModule = 2; layersPerModule = 2;
// re-adjust the grid size for a partial octahedral path // re-adjust the grid size for a partial octahedral path
// (scale of 1.1 guessed based on modeling) // (scale of 1.1 guessed based on modeling)
gridSize = (scale_(this->spacing) * 1.1 * params.multiline / params.density); gridSize = (scale_(this->spacing) * 1.1 * params.multiline / params.density);
// re-adjust zScale to make layering consistent // re-adjust zScale to make layering consistent
zScale = (gridSize * 2) / (layersPerModule * layerHeight); zScale = (gridSize * 2) / (layersPerModule * layerHeight);
} else {
if(layersPerModule < 2){
layersPerModule = 2;
}
// re-adjust zScale to make layering consistent
zScale = (gridSize * 2) / (layersPerModule * layerHeight);
// re-adjust the grid size to account for the new zScale
gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density);
// re-calculate layersPerModule and zScale
layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05);
if(layersPerModule < 2){
layersPerModule = 2;
}
zScale = (gridSize * 2) / (layersPerModule * layerHeight);
} }
// align bounding box to a multiple of our honeycomb grid module // align bounding box to a multiple of the octahedron grid so that
// (a module is 2*$gridSize since one $gridSize half-module is // layers with different starting points have matching origins
// growing while the other $gridSize half-module is shrinking) bb.merge(align_to_grid(bb.min, Point(gridSize * 2., gridSize * 2.)));
bb.merge(align_to_grid(bb.min, Point(gridSize*4, gridSize*4)));
// Z adjustment to start at the widest point for the lowest layer
coordf_t startOffset = gridSize / 2. + scale_(params.layer_height / 2.);
// generate pattern // generate pattern
Polylines polylines = Polylines polylines =
makeGrid( makeGrid(
scale_(this->z) * zScale, scale_(this->z) * zScale + startOffset,
gridSize, scale_(this->z - (params.layer_height * params.multiline * layersPerSlice)) * zScale + startOffset,
bb.size()(0), gridSize, bb.size()(0), bb.size()(1),
bb.size()(1), params.infill_complete_top,
!params.dont_adjust); scale_(this->spacing),
params.multiline,
layersPerSlice);
// move pattern in place // move pattern in place
for (Polyline &pl : polylines){ for (Polyline &pl : polylines){
@@ -290,8 +406,8 @@ void Fill3DHoneycomb::_fill_surface_single(
smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution)); smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution));
} }
// Apply multiline offset if needed // Note: multiline fill adjustment is carried out in this code,
multiline_fill(polylines, params, spacing); // rather than using the multiline_fill function
// clip pattern to boundaries, chain the clipped polylines // clip pattern to boundaries, chain the clipped polylines
polylines = intersection_pl(std::move(polylines), to_polygons(expolygon)); polylines = intersection_pl(std::move(polylines), to_polygons(expolygon));
+1
View File
@@ -118,6 +118,7 @@ struct FillParams
float horiz_move{0.0}; //move infill to get cross zag pattern float horiz_move{0.0}; //move infill to get cross zag pattern
bool symmetric_infill_y_axis{false}; bool symmetric_infill_y_axis{false};
bool infill_complete_top{false};
coord_t symmetric_y_axis{0}; coord_t symmetric_y_axis{0};
bool locked_zag{false}; bool locked_zag{false};
float infill_lock_depth{0.0}; float infill_lock_depth{0.0};
+1
View File
@@ -119,6 +119,7 @@
#endif #endif
#include <Shiny/Shiny.h> #include <Shiny/Shiny.h>
#include <stdio.h>
using namespace std::literals::string_view_literals; using namespace std::literals::string_view_literals;
+83 -105
View File
@@ -751,6 +751,50 @@ template<typename T>
} }
} }
namespace {
// Writes G-code to a file in blocks and records in lines_ends the file offset after every '\n'
class GCodeFileWriter
{
public:
GCodeFileWriter(FilePtr& out, const std::string& out_path, std::vector<size_t>& lines_ends, const char* error_message)
: m_out(out), m_out_path(out_path), m_lines_ends(lines_ends), m_error_message(error_message)
{}
~GCodeFileWriter() { assert(m_buffer.empty() || std::uncaught_exceptions() > 0); }
void append(std::string_view text)
{
const size_t text_pos = m_file_pos + m_buffer.size();
for (size_t i = text.find('\n'); i != std::string_view::npos; i = text.find('\n', i + 1))
m_lines_ends.emplace_back(text_pos + i + 1);
m_buffer += text;
if (m_buffer.size() >= GCodeProcessor::Output_Block_Size)
flush();
}
void flush()
{
if (m_buffer.empty())
return;
fwrite(m_buffer.data(), 1, m_buffer.size(), m_out.f);
if (ferror(m_out.f)) {
m_out.close();
boost::nowide::remove(m_out_path.c_str());
throw Slic3r::RuntimeError(m_error_message);
}
m_file_pos += m_buffer.size();
m_buffer.clear();
}
private:
FilePtr& m_out;
const std::string& m_out_path;
std::vector<size_t>& m_lines_ends;
const char* m_error_message;
std::string m_buffer;
size_t m_file_pos{0};
};
} // namespace
// Helper class to modify and export gcode to file // Helper class to modify and export gcode to file
class ExportLines class ExportLines
{ {
@@ -765,16 +809,6 @@ public:
enum class EWriteType { BySize, ByTime }; enum class EWriteType { BySize, ByTime };
private: private:
static void update_lines_ends_and_out_file_pos(const std::string& out_string, std::vector<size_t>& lines_ends, size_t* out_file_pos)
{
for (size_t i = 0; i < out_string.size(); ++i) {
if (out_string[i] == '\n')
lines_ends.emplace_back((out_file_pos != nullptr) ? *out_file_pos + i + 1 : i + 1);
}
if (out_file_pos != nullptr)
*out_file_pos += out_string.size();
}
struct LineData struct LineData
{ {
std::string line; std::string line;
@@ -814,12 +848,14 @@ private:
EWriteType m_write_type{EWriteType::BySize}; EWriteType m_write_type{EWriteType::BySize};
// Time machines containing g1 times cache // Time machines containing g1 times cache
const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines; const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines;
// Output file writer
GCodeFileWriter& m_writer;
// Current time // Current time
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_times{0.0f, 0.0f}; std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_times{0.0f, 0.0f};
// Current size in bytes // Current size of the cache in bytes
size_t m_size{0}; size_t m_size{0};
// gcode lines cache // gcode lines cache, used only when writing by time
std::deque<LineData> m_lines; std::deque<LineData> m_lines;
size_t m_added_lines_counter{0}; size_t m_added_lines_counter{0};
// map of gcode line ids from original to final // map of gcode line ids from original to final
@@ -827,16 +863,16 @@ private:
std::vector<std::pair<size_t, size_t>> m_gcode_lines_map; std::vector<std::pair<size_t, size_t>> m_gcode_lines_map;
size_t m_times_cache_id{0}; size_t m_times_cache_id{0};
size_t m_out_file_pos{0};
public: public:
ExportLines(EWriteType type, const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines) ExportLines(EWriteType type, const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines, GCodeFileWriter& writer)
#ifndef NDEBUG #ifndef NDEBUG
: m_statistics(*this) : m_statistics(*this)
, m_write_type(type) , m_write_type(type)
, m_machines(machines){} , m_machines(machines)
, m_writer(writer){}
#else #else
: m_write_type(type), m_machines(machines) : m_write_type(type), m_machines(machines), m_writer(writer)
{} {}
#endif // NDEBUG #endif // NDEBUG
@@ -890,11 +926,14 @@ public:
if (line.empty()) if (line.empty())
return; return;
m_lines.push_back({line, m_times}); if (m_write_type == EWriteType::ByTime) {
m_lines.push_back({line, m_times});
#ifndef NDEBUG #ifndef NDEBUG
m_statistics.add_line(line.length()); m_statistics.add_line(line.length());
#endif // NDEBUG #endif // NDEBUG
m_size += line.length(); m_size += line.length();
} else
m_writer.append(line);
++m_added_lines_counter; ++m_added_lines_counter;
if (!ignore_from_move) { if (!ignore_from_move) {
assert(!m_gcode_lines_map.empty()); assert(!m_gcode_lines_map.empty());
@@ -960,65 +999,35 @@ public:
} }
} }
// write to file: // when writing by time, pass the cached lines older than m_times[Normal] - backtrace_time to the writer
// m_write_type == EWriteType::ByTime - all lines older than m_time - backtrace_time void write(float backtrace_time)
// m_write_type == EWriteType::BySize - all lines if current size is greater than 65535 bytes
void write(FilePtr& out, float backtrace_time, GCodeProcessorResult& result, const std::string& out_path)
{ {
if (m_lines.empty()) if (m_write_type != EWriteType::ByTime)
return; return;
// collect lines to write into a single string while (!m_lines.empty() && m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) {
std::string out_string; const LineData& data = m_lines.front();
if (!m_lines.empty()) { m_writer.append(data.line);
if (m_write_type == EWriteType::ByTime) { m_size -= data.line.length();
while (m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) { m_lines.pop_front();
const LineData& data = m_lines.front();
out_string += data.line;
m_size -= data.line.length();
m_lines.pop_front();
#ifndef NDEBUG #ifndef NDEBUG
m_statistics.remove_line(); m_statistics.remove_line();
#endif // NDEBUG #endif // NDEBUG
}
} else {
if (m_size > 65535) {
while (!m_lines.empty()) {
out_string += m_lines.front().line;
m_lines.pop_front();
}
m_size = 0;
#ifndef NDEBUG
m_statistics.remove_all_lines();
#endif // NDEBUG
}
}
}
{
write_to_file(out, out_string, result, out_path);
update_lines_ends_and_out_file_pos(out_string, result.lines_ends, &m_out_file_pos);
} }
} }
// flush the current content of the cache to file // flush the current content of the cache and the writer to file
void flush(FilePtr& out, GCodeProcessorResult& result, const std::string& out_path) void flush()
{ {
// collect lines to flush into a single string
std::string out_string;
while (!m_lines.empty()) { while (!m_lines.empty()) {
out_string += m_lines.front().line; m_writer.append(m_lines.front().line);
m_lines.pop_front(); m_lines.pop_front();
} }
m_size = 0; m_size = 0;
#ifndef NDEBUG #ifndef NDEBUG
m_statistics.remove_all_lines(); m_statistics.remove_all_lines();
#endif // NDEBUG #endif // NDEBUG
m_writer.flush();
{
write_to_file(out, out_string, result, out_path);
update_lines_ends_and_out_file_pos(out_string, result.lines_ends, &m_out_file_pos);
}
} }
void synchronize_moves(GCodeProcessorResult& result) const void synchronize_moves(GCodeProcessorResult& result) const
@@ -1051,20 +1060,7 @@ public:
size_t get_size() const { return m_size; } size_t get_size() const { return m_size; }
private: void reserve(size_t lines_count) { m_gcode_lines_map.reserve(lines_count); }
void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path)
{
if (!out_string.empty()) {
if (true) {
fwrite((const void*) out_string.c_str(), 1, out_string.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError("GCode processor post process export failed.\nIs the disk full?");
}
}
}
}
}; };
void GCodeProcessor::run_post_process() void GCodeProcessor::run_post_process()
@@ -1160,8 +1156,13 @@ void GCodeProcessor::run_post_process()
last_exported_stop[i] = time_in_minutes(m_time_processor.machines[i].time); last_exported_stop[i] = time_in_minutes(m_time_processor.machines[i].time);
} }
m_result.lines_ends.clear();
// m_result.lines_ends.emplace_back(std::vector<size_t>());
GCodeFileWriter writer(out, out_path, m_result.lines_ends, "GCode processor post process export failed.\nIs the disk full?");
ExportLines export_line(m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize, ExportLines export_line(m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize,
m_time_processor.machines); m_time_processor.machines, writer);
// The line map holds an entry for each line of the file, and the first pass counted them
export_line.reserve(m_line_id);
// replace placeholder lines with the proper final value // replace placeholder lines with the proper final value
// gcode_line is in/out parameter, to reduce expensive memory allocation // gcode_line is in/out parameter, to reduce expensive memory allocation
@@ -1535,9 +1536,6 @@ void GCodeProcessor::run_post_process()
} }
}; };
m_result.lines_ends.clear();
// m_result.lines_ends.emplace_back(std::vector<size_t>());
// Orca: freshly collect SKIPPABLE ranges each post-process pass. The ranges are stored on the // Orca: freshly collect SKIPPABLE ranges each post-process pass. The ranges are stored on the
// member (rather than a local) so the injection pass can consume them, hence the clear here to // member (rather than a local) so the injection pass can consume them, hence the clear here to
// avoid stale ranges on re-invocation. // avoid stale ranges on re-invocation.
@@ -1802,7 +1800,7 @@ void GCodeProcessor::run_post_process()
if (!gcode_line.empty()) if (!gcode_line.empty())
export_line.append_line(gcode_line); export_line.append_line(gcode_line);
export_line.write(out, 1.1f * max_backtrace_time, m_result, out_path); export_line.write(1.1f * max_backtrace_time);
gcode_line.clear(); gcode_line.clear();
} }
} }
@@ -1844,7 +1842,7 @@ void GCodeProcessor::run_post_process()
} }
} }
export_line.flush(out, m_result, out_path); export_line.flush();
out.close(); out.close();
in.close(); in.close();
@@ -1992,31 +1990,13 @@ void GCodeProcessor::run_second_pass_injection()
// The rewrite may shift byte positions (once the injector inserts lines), so rebuild lines_ends from scratch. // The rewrite may shift byte positions (once the injector inserts lines), so rebuild lines_ends from scratch.
// With an empty map the scanned '\n' offsets reproduce the current lines_ends exactly. // With an empty map the scanned '\n' offsets reproduce the current lines_ends exactly.
m_result.lines_ends.clear(); m_result.lines_ends.clear();
size_t out_file_pos = 0; GCodeFileWriter writer(out, out_path, m_result.lines_ends, "GCode processor pre-heat injection pass failed.\nIs the disk full?\n");
auto write_out = [&out, &out_path, this, &out_file_pos](std::string& str) {
if (str.empty())
return;
fwrite((const void*) str.c_str(), 1, str.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError(std::string("GCode processor pre-heat injection pass failed.\nIs the disk full?\n"));
}
for (size_t i = 0; i < str.size(); ++i) {
if (str[i] == '\n')
m_result.lines_ends.emplace_back(out_file_pos + i + 1);
}
out_file_pos += str.size();
str.clear();
};
// Orca: read/split lines with EOL-preserving semantics (keep the original \r and \n bytes, and // Orca: read/split lines with EOL-preserving semantics (keep the original \r and \n bytes, and
// synthesize no trailing newline). This is required for the empty-map identity: normalizing every // synthesize no trailing newline). This is required for the empty-map identity: normalizing every
// line ending to "\n" would not be byte-identical if the finished file used \r\n or lacked a // line ending to "\n" would not be byte-identical if the finished file used \r\n or lacked a
// final newline. // final newline.
std::string gcode_line; std::string gcode_line;
std::string export_buffer;
unsigned int line_id = 0; unsigned int line_id = 0;
auto op_it = inserted_operation_lines.begin(); auto op_it = inserted_operation_lines.begin();
std::vector<char> buffer(65536 * 10, 0); std::vector<char> buffer(65536 * 10, 0);
@@ -2058,16 +2038,14 @@ void GCodeProcessor::run_second_pass_injection()
} }
++op_it; ++op_it;
} }
export_buffer += gcode_line; writer.append(gcode_line);
gcode_line.clear(); gcode_line.clear();
if (export_buffer.length() >= 65536)
write_out(export_buffer);
} }
} }
if (eof) if (eof)
break; break;
} }
write_out(export_buffer); writer.flush();
out.close(); out.close();
in.close(); in.close();
+3
View File
@@ -491,6 +491,9 @@ class Print;
static const std::string VFlush_End_Tag; static const std::string VFlush_End_Tag;
static const std::string External_Purge_Tag; static const std::string External_Purge_Tag;
public: public:
// Size of the blocks the post-processing passes write the G-code in
static constexpr size_t Output_Block_Size = 65536;
// Orca: SKIPPABLE region tags, stored as static strings (the FLUSH idiom above) rather than // Orca: SKIPPABLE region tags, stored as static strings (the FLUSH idiom above) rather than
// a CustomETags/CustomTags array. Public so the emission sites (WipeTower / change_filament // a CustomETags/CustomTags array. Public so the emission sites (WipeTower / change_filament
// path) can reference them single-sourced. // path) can reference them single-sourced.
+1
View File
@@ -12,6 +12,7 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp> #include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp> #include <boost/nowide/fstream.hpp>
#include <ios>
#ifdef _WIN32 #ifdef _WIN32
#include <boost/interprocess/sync/file_lock.hpp> #include <boost/interprocess/sync/file_lock.hpp>
#include <boost/nowide/convert.hpp> #include <boost/nowide/convert.hpp>
+3 -3
View File
@@ -378,7 +378,7 @@ void Layer::simplify_support_entity_collection(ExtrusionEntityCollection* entity
//BBS: method to simplify support path //BBS: method to simplify support path
void Layer::simplify_support_path(ExtrusionPath * path) void Layer::simplify_support_path(ExtrusionPath * path)
{ {
const auto print_config = this->object()->print()->config(); const auto &print_config = this->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode; const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting; const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value); const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -393,7 +393,7 @@ void Layer::simplify_support_path(ExtrusionPath * path)
//BBS: method to simplify support path //BBS: method to simplify support path
void Layer::simplify_support_multi_path(ExtrusionMultiPath* multipath) void Layer::simplify_support_multi_path(ExtrusionMultiPath* multipath)
{ {
const auto print_config = this->object()->print()->config(); const auto &print_config = this->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode; const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting; const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value); const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -410,7 +410,7 @@ void Layer::simplify_support_multi_path(ExtrusionMultiPath* multipath)
//BBS: method to simplify support path //BBS: method to simplify support path
void Layer::simplify_support_loop(ExtrusionLoop* loop) void Layer::simplify_support_loop(ExtrusionLoop* loop)
{ {
const auto print_config = this->object()->print()->config(); const auto &print_config = this->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode; const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting; const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value); const auto scaled_resolution = scaled<double>(print_config.resolution.value);
+3 -3
View File
@@ -1101,7 +1101,7 @@ void LayerRegion::simplify_entity_collection(ExtrusionEntityCollection* entity_c
void LayerRegion::simplify_path(ExtrusionPath* path) void LayerRegion::simplify_path(ExtrusionPath* path)
{ {
const auto print_config = this->layer()->object()->print()->config(); const auto &print_config = this->layer()->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode; const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting; const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value); const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -1119,7 +1119,7 @@ void LayerRegion::simplify_path(ExtrusionPath* path)
void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath) void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath)
{ {
const auto print_config = this->layer()->object()->print()->config(); const auto &print_config = this->layer()->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode; const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting; const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value); const auto scaled_resolution = scaled<double>(print_config.resolution.value);
@@ -1139,7 +1139,7 @@ void LayerRegion::simplify_multi_path(ExtrusionMultiPath* multipath)
void LayerRegion::simplify_loop(ExtrusionLoop* loop) void LayerRegion::simplify_loop(ExtrusionLoop* loop)
{ {
const auto print_config = this->layer()->object()->print()->config(); const auto &print_config = this->layer()->object()->print()->config();
const bool spiral_mode = print_config.spiral_mode; const bool spiral_mode = print_config.spiral_mode;
const bool enable_arc_fitting = print_config.enable_arc_fitting; const bool enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value); const auto scaled_resolution = scaled<double>(print_config.resolution.value);
+1
View File
@@ -12,6 +12,7 @@
#include <system_error> #include <system_error>
#include <fast_float/fast_float.h> #include <fast_float/fast_float.h>
#include <clocale>
// Defined where the floating point std::to_chars can be called, which with Apple's libc++ runtime is from macOS 13.3. // Defined where the floating point std::to_chars can be called, which with Apple's libc++ runtime is from macOS 13.3.
#if defined(_LIBCPP_VERSION) #if defined(_LIBCPP_VERSION)
+1
View File
@@ -1209,6 +1209,7 @@ static std::vector<std::string> s_Preset_print_options{
"infill_lock_depth", "infill_lock_depth",
"skin_infill_depth", "skin_infill_depth",
"skin_infill_density", "skin_infill_density",
"infill_complete_top",
"align_infill_direction_to_model", "align_infill_direction_to_model",
"extra_solid_infills", "extra_solid_infills",
"center_of_surface_pattern", "center_of_surface_pattern",
+13 -6
View File
@@ -4574,6 +4574,13 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced; def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false)); def->set_default_value(new ConfigOptionBool(false));
def = this->add("infill_complete_top", coBool);
def->label = L("Fill pattern tops");
def->category = L("Strength");
def->tooltip = L("Choose this option if you want to completely fill in the tops of the infill pattern");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(false));
// Orca: max layer height for combined infill // Orca: max layer height for combined infill
def = this->add("infill_combination_max_layer_height", coFloatOrPercent); def = this->add("infill_combination_max_layer_height", coFloatOrPercent);
def->label = L("Infill combination - Max layer height"); def->label = L("Infill combination - Max layer height");
@@ -5301,7 +5308,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionEnum<InputShaperType>(InputShaperType::Default)); def->set_default_value(new ConfigOptionEnum<InputShaperType>(InputShaperType::Default));
def = this->add("input_shaping_freq_x", coFloat); def = this->add("input_shaping_freq_x", coFloat);
def->label = L("X"); def->label = L_CONTEXT("X", "Axis");
def->tooltip = L("Resonant frequency for the X axis input shaper.\nZero will use the firmware frequency.\nTo disable input shaping, use the Disable type.\nRRF: X and Y values are equal."); def->tooltip = L("Resonant frequency for the X axis input shaper.\nZero will use the firmware frequency.\nTo disable input shaping, use the Disable type.\nRRF: X and Y values are equal.");
def->sidetext = L("Hz"); // Hertz, CIS languages need translation def->sidetext = L("Hz"); // Hertz, CIS languages need translation
def->min = 0; def->min = 0;
@@ -5310,7 +5317,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0)); def->set_default_value(new ConfigOptionFloat(0));
def = this->add("input_shaping_freq_y", coFloat); def = this->add("input_shaping_freq_y", coFloat);
def->label = L("Y"); def->label = L_CONTEXT("Y", "Axis");
def->tooltip = L("Resonant frequency for the Y axis input shaper.\nZero will use the firmware frequency.\nTo disable input shaping, use the Disable type."); def->tooltip = L("Resonant frequency for the Y axis input shaper.\nZero will use the firmware frequency.\nTo disable input shaping, use the Disable type.");
def->sidetext = L("Hz"); // Hertz, CIS languages need translation def->sidetext = L("Hz"); // Hertz, CIS languages need translation
def->min = 0; def->min = 0;
@@ -5319,7 +5326,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0)); def->set_default_value(new ConfigOptionFloat(0));
def = this->add("input_shaping_damp_x", coFloat); def = this->add("input_shaping_damp_x", coFloat);
def->label = L("X"); def->label = L_CONTEXT("X", "Axis");
def->tooltip = L("Damping ratio for the X axis input shaper.\nZero will use the firmware damping ratio.\nTo disable input shaping, use the Disable type.\nRRF: X and Y values are equal."); def->tooltip = L("Damping ratio for the X axis input shaper.\nZero will use the firmware damping ratio.\nTo disable input shaping, use the Disable type.\nRRF: X and Y values are equal.");
def->min = 0; def->min = 0;
def->max = 1; def->max = 1;
@@ -5327,7 +5334,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0.1)); def->set_default_value(new ConfigOptionFloat(0.1));
def = this->add("input_shaping_damp_y", coFloat); def = this->add("input_shaping_damp_y", coFloat);
def->label = L("Y"); def->label = L_CONTEXT("Y", "Axis");
def->tooltip = L("Damping ratio for the Y axis input shaper.\nZero will use the firmware damping ratio.\nTo disable input shaping, use the Disable type."); def->tooltip = L("Damping ratio for the Y axis input shaper.\nZero will use the firmware damping ratio.\nTo disable input shaping, use the Disable type.");
def->min = 0; def->min = 0;
def->max = 1; def->max = 1;
@@ -8530,14 +8537,14 @@ void PrintConfigDef::init_sla_params()
def = this->add("display_pixels_x", coInt); def = this->add("display_pixels_x", coInt);
//def->full_label = L(""); //def->full_label = L("");
def->label = ("X"); def->label = L_CONTEXT("X", "Axis");
//def->tooltip = L(""); //def->tooltip = L("");
def->min = 100; def->min = 100;
def->set_default_value(new ConfigOptionInt(2560)); def->set_default_value(new ConfigOptionInt(2560));
def = this->add("display_pixels_y", coInt); def = this->add("display_pixels_y", coInt);
//def->full_label = L(""); //def->full_label = L("");
def->label = ("Y"); def->label = L_CONTEXT("Y", "Axis");
//def->tooltip = L(""); //def->tooltip = L("");
def->min = 100; def->min = 100;
def->set_default_value(new ConfigOptionInt(1440)); def->set_default_value(new ConfigOptionInt(1440));
+1
View File
@@ -1358,6 +1358,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, bottom_layer_direction)) ((ConfigOptionFloat, bottom_layer_direction))
((ConfigOptionString, solid_infill_rotate_template)) ((ConfigOptionString, solid_infill_rotate_template))
((ConfigOptionBool, symmetric_infill_y_axis)) ((ConfigOptionBool, symmetric_infill_y_axis))
((ConfigOptionBool, infill_complete_top))
((ConfigOptionFloat, infill_shift_step)) ((ConfigOptionFloat, infill_shift_step))
((ConfigOptionString, sparse_infill_rotate_template)) ((ConfigOptionString, sparse_infill_rotate_template))
((ConfigOptionPercent, sparse_infill_density)) ((ConfigOptionPercent, sparse_infill_density))
+7 -7
View File
@@ -3450,14 +3450,14 @@ void PrintObject::bridge_over_infill()
const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve || const bool turning_pattern = region_config.sparse_infill_pattern == ipHilbertCurve ||
region_config.sparse_infill_pattern == ipOctagramSpiral; region_config.sparse_infill_pattern == ipOctagramSpiral;
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true); const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing()); ExPolygons bridge_components = intersection_ex(expand(candidate.new_polys, flow.scaled_spacing()), deep_infill_area);
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area); // Orca: Filter whole bridge areas so their holes remain holes.
bridge_components.erase(std::remove_if(bridge_components.begin(), bridge_components.end(),
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(), [&internal_unsupported_area](const ExPolygon &component) {
[internal_unsupported_area](const Polygon &p) { return intersection_ex(component, internal_unsupported_area).empty();
return intersection({p}, internal_unsupported_area).empty();
}), }),
area_to_be_bridge.end()); bridge_components.end());
Polygons area_to_be_bridge = to_polygons(std::move(bridge_components));
Polygons limiting_area = union_(area_to_be_bridge, expansion_area); Polygons limiting_area = union_(area_to_be_bridge, expansion_area);
+2 -2
View File
@@ -1944,7 +1944,7 @@ Polygons TreeSupport::get_trim_support_regions(
static const double no_overlap_xy_gap = 0.2f; static const double no_overlap_xy_gap = 0.2f;
double gap_xy_scaled = scale_(gap_xy); double gap_xy_scaled = scale_(gap_xy);
SupportLayer& support_layer = *support_layer_ptr; SupportLayer& support_layer = *support_layer_ptr;
auto m_print_config = object.print()->config(); const PrintConfig& print_config = object.print()->config();
size_t idx_object_layer_overlapping = size_t(-1); size_t idx_object_layer_overlapping = size_t(-1);
@@ -1991,7 +1991,7 @@ Polygons TreeSupport::get_trim_support_regions(
const Layer& object_layer = *object.layers()[i]; const Layer& object_layer = *object.layers()[i];
bool some_region_overlaps = false; bool some_region_overlaps = false;
for (LayerRegion* region : object_layer.regions()) { for (LayerRegion* region : object_layer.regions()) {
coordf_t bridging_height = region->region().bridging_height_avg(m_print_config); coordf_t bridging_height = region->region().bridging_height_avg(print_config);
if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON) if (object_layer.print_z - bridging_height > support_layer.print_z + gap_extra_above - EPSILON)
break; break;
some_region_overlaps = true; some_region_overlaps = true;
+1
View File
@@ -22,6 +22,7 @@
#include <tbb/task_arena.h> #include <tbb/task_arena.h>
#include "Thread.hpp" #include "Thread.hpp"
#include <cstring>
namespace Slic3r { namespace Slic3r {
+1
View File
@@ -119,6 +119,7 @@
// We are using quite an old TBB 2017 U7, which does not support global control API officially. // We are using quite an old TBB 2017 U7, which does not support global control API officially.
// Before we update our build servers, let's use the old API, which is deprecated in up to date TBB. // Before we update our build servers, let's use the old API, which is deprecated in up to date TBB.
#include <tbb/tbb.h> #include <tbb/tbb.h>
#include <string.h>
namespace boost::posix_time { class ptime; } namespace boost::posix_time { class ptime; }
#if ! defined(TBB_VERSION_MAJOR) #if ! defined(TBB_VERSION_MAJOR)
-7
View File
@@ -310,7 +310,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, force_native_color(false) , force_native_color(false)
, force_neutral_color(false) , force_neutral_color(false)
, force_sinking_contours(false) , force_sinking_contours(false)
, depth_bias(false)
, picking(false) , picking(false)
, tverts_range(0, size_t(-1)) , tverts_range(0, size_t(-1))
{ {
@@ -1333,17 +1332,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
shader->set_uniform("projection_matrix", projection_matrix); shader->set_uniform("projection_matrix", projection_matrix);
const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose(); const Matrix3d view_normal_matrix = view_matrix.matrix().block(0, 0, 3, 3) * model_matrix.matrix().block(0, 0, 3, 3).inverse().transpose();
shader->set_uniform("view_normal_matrix", view_normal_matrix); shader->set_uniform("view_normal_matrix", view_normal_matrix);
if (volume.first->depth_bias) {
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
glsafe(::glPolygonOffset(1.0f, 1.0f));
}
//BBS: add outline related logic //BBS: add outline related logic
if (volume.first->selected && shader_can_outline && GUI::wxGetApp().show_outline()) if (volume.first->selected && shader_can_outline && GUI::wxGetApp().show_outline())
volume.first->render_with_outline(cnv_size); volume.first->render_with_outline(cnv_size);
else else
volume.first->render(); volume.first->render();
if (volume.first->depth_bias)
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
#if ENABLE_ENVIRONMENT_MAP #if ENABLE_ENVIRONMENT_MAP
if (use_environment_texture) if (use_environment_texture)
-4
View File
@@ -232,10 +232,6 @@ public:
bool force_neutral_color : 1; bool force_neutral_color : 1;
// Whether or not to force rendering of sinking contours // Whether or not to force rendering of sinking contours
bool force_sinking_contours : 1; bool force_sinking_contours : 1;
// Orca: draw this volume with a positive depth bias (glPolygonOffset, pushed away from the
// camera), so on a surface it shares with another volume the other volume wins the depth
// test instead of z-fighting it
bool depth_bias : 1;
// Is render for picking // Is render for picking
bool picking : 1; bool picking : 1;
// slice error // slice error
+3 -3
View File
@@ -1228,11 +1228,11 @@ void AMSDryCtrWin::update_normal_description(DevAms* dev_ams)
for (const auto& lim : ams_limits) { for (const auto& lim : ams_limits) {
if (dev_ams->GetAmsType() == lim.type) { if (dev_ams->GetAmsType() == lim.type) {
if (temp_val > lim.max_temp) { if (temp_val > lim.max_temp) {
wxString msg = wxString::Format(_L("%s maximum drying temperature is %d°C."), wxString(lim.name), lim.max_temp); wxString msg = wxString::Format(_L("%s maximum drying temperature is %d\u2103." /* °C */), wxString(lim.name), lim.max_temp);
warning_text += msg + "\n"; warning_text += msg + "\n";
can_enable_button = false; can_enable_button = false;
} else if (temp_val < lim.min_temp) { } else if (temp_val < lim.min_temp) {
wxString msg = wxString::Format(_L("%s minimum drying temperature is %d°C."), wxString(lim.name), lim.min_temp); wxString msg = wxString::Format(_L("%s minimum drying temperature is %d\u2103." /* °C */), wxString(lim.name), lim.min_temp);
warning_text += msg + "\n"; warning_text += msg + "\n";
can_enable_button = false; can_enable_button = false;
} }
@@ -1260,7 +1260,7 @@ void AMSDryCtrWin::update_normal_description(DevAms* dev_ams)
auto limit_temperature = preset.value().filament_dev_ams_drying_heat_distortion_temperature; auto limit_temperature = preset.value().filament_dev_ams_drying_heat_distortion_temperature;
if (temp_val > limit_temperature) { if (temp_val > limit_temperature) {
warning_text += _L("The temperature shall not exceed the filament's heat distortion temperature") + "(" + warning_text += _L("The temperature shall not exceed the filament's heat distortion temperature") + "(" +
wxString::Format(wxT("%d"), static_cast<int>(limit_temperature)) + wxString::FromUTF8("°C)\n"); wxString::Format(wxT("%d"), static_cast<int>(limit_temperature)) + wxString::FromUTF8(u8"\u2103" /* °C */) + ")\n";
can_enable_button = false; can_enable_button = false;
} }
} }
+3 -2
View File
@@ -264,7 +264,8 @@ AboutDialog::AboutDialog()
vesizer->Add(0, 0, 1, wxEXPAND, FromDIP(5)); vesizer->Add(0, 0, 1, wxEXPAND, FromDIP(5));
auto version_string = std::string(SoftFever_VERSION); // _L("Orca Slicer ") + " " + std::string(SoftFever_VERSION); auto version_string = std::string(SoftFever_VERSION); // _L("Orca Slicer ") + " " + std::string(SoftFever_VERSION);
wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize); wxStaticText* version = new wxStaticText(this, wxID_ANY, version_string.c_str(), wxDefaultPosition, wxDefaultSize);
wxStaticText* credits_string = new wxStaticText(this, wxID_ANY, wxString::Format("Build %s", build_commit_label), wxDefaultPosition, wxDefaultSize); // TRN %s is the commit the application was built from
wxStaticText* credits_string = new wxStaticText(this, wxID_ANY, wxString::Format(_L("Build %s"), build_commit_label), wxDefaultPosition, wxDefaultSize);
credits_string->SetFont(_build_string_font); credits_string->SetFont(_build_string_font);
wxFont version_font = GetFont(); wxFont version_font = GetFont();
version_font = version_font.Scaled(1.85f); // SetPointSize(20) not works on macOS because it uses a 72 PPI reference version_font = version_font.Scaled(1.85f); // SetPointSize(20) not works on macOS because it uses a 72 PPI reference
@@ -330,7 +331,7 @@ AboutDialog::AboutDialog()
copyright_hor_sizer->Add(copyright_ver_sizer, 0, wxLEFT, FromDIP(20)); copyright_hor_sizer->Add(copyright_ver_sizer, 0, wxLEFT, FromDIP(20));
wxStaticText *html_text = new wxStaticText(this, wxID_ANY, "Copyright(C) 2026 OrcaSlicer Pte Ltd All Rights Reserved", wxDefaultPosition, wxDefaultSize); wxStaticText *html_text = new wxStaticText(this, wxID_ANY, _L("Copyright(C) 2026 OrcaSlicer Pte Ltd All Rights Reserved"), wxDefaultPosition, wxDefaultSize);
html_text->SetForegroundColour(wxColour(107, 107, 107)); html_text->SetForegroundColour(wxColour(107, 107, 107));
copyright_ver_sizer->Add(html_text, 0, wxALL , 0); copyright_ver_sizer->Add(html_text, 0, wxALL , 0);
+7 -6
View File
@@ -329,7 +329,8 @@ void AmsMapingPopup::update_mapping_items(MachineObject* obj, const std::vector<
if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) { if (m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) {
auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL); auto sizer_mapping_list = new wxBoxSizer(wxHORIZONTAL);
const auto& shown_name = td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID ? "Ext-R" : "Ext-L"; // TRN Short labels of the external spool of the right and left nozzle
const auto& shown_name = td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID ? _L("Ext-R") : _L("Ext-L");
auto ams_mapping_item_container = new MappingContainer(m_right_marea_panel, shown_name, 1); auto ams_mapping_item_container = new MappingContainer(m_right_marea_panel, shown_name, 1);
ams_mapping_item_container->SetName(m_right_marea_panel->GetName()); ams_mapping_item_container->SetName(m_right_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list); ams_mapping_item_container->SetSizer(sizer_mapping_list);
@@ -506,7 +507,7 @@ void AmsMapingPopup::update_ams_tips(MachineObject* obj)
} }
if (obj && obj->GetFilaSwitch()->IsInstalled()) { if (obj && obj->GetFilaSwitch()->IsInstalled()) {
const auto& msg = _L("External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."); const auto& msg = _L("External spools are not supported since Filament Track Switch has been installed. If you want to use an external spool, please uninstall it.");
m_ams_tips_msg_panel->AddMessage(msg, "#FF6F00", ""); m_ams_tips_msg_panel->AddMessage(msg, "#FF6F00", "");
} }
@@ -588,7 +589,7 @@ void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data,
// slots Ext-R / Ext-L inline in the two-nozzle left+right views (matches that overload's result). // slots Ext-R / Ext-L inline in the two-nozzle left+right views (matches that overload's result).
if ((m_show_type == ShowType::LEFT_AND_RIGHT || m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC) if ((m_show_type == ShowType::LEFT_AND_RIGHT || m_show_type == ShowType::LEFT_AND_RIGHT_DYNAMIC)
&& (tray_data[i].id == VIRTUAL_TRAY_MAIN_ID || tray_data[i].id == VIRTUAL_TRAY_DEPUTY_ID)) { && (tray_data[i].id == VIRTUAL_TRAY_MAIN_ID || tray_data[i].id == VIRTUAL_TRAY_DEPUTY_ID)) {
m_mapping_item->set_tray_index(tray_data[i].id == VIRTUAL_TRAY_MAIN_ID ? wxString("Ext-R") : wxString("Ext-L")); m_mapping_item->set_tray_index(tray_data[i].id == VIRTUAL_TRAY_MAIN_ID ? _L("Ext-R") : _L("Ext-L"));
} else { } else {
m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id)); m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id));
} }
@@ -607,7 +608,7 @@ void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data,
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id); can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) { if (!can_pick_the_item) {
item_tooltip_msg = _L( item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."); "External spools are not supported since Filament Track Switch has been installed. If you want to use an external spool, please uninstall it.");
} }
} }
} }
@@ -624,7 +625,7 @@ void AmsMapingPopup::add_ams_mapping(std::vector<TrayData> tray_data,
can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id); can_pick_the_item = !devPrinterUtil::IsVirtualSlot(m_mapping_item->m_ams_id);
if (!can_pick_the_item) { if (!can_pick_the_item) {
item_tooltip_msg = _L( item_tooltip_msg = _L(
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it."); "External spools are not supported since Filament Track Switch has been installed. If you want to use an external spool, please uninstall it.");
} }
} else if (m_show_type != ShowType::RIGHT && m_show_type != ShowType::LEFT_AND_RIGHT) { } else if (m_show_type != ShowType::RIGHT && m_show_type != ShowType::LEFT_AND_RIGHT) {
can_pick_the_item = false; can_pick_the_item = false;
@@ -718,7 +719,7 @@ void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
}); });
} }
item->set_tray_index("Ext"); item->set_tray_index(_L("Ext"));
} }
} // namespace Slic3r::GUI } // namespace Slic3r::GUI
+3
View File
@@ -65,6 +65,8 @@
#include "Widgets/Label.hpp" #include "Widgets/Label.hpp"
#include "slic3r/GUI/I18N.hpp" #include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include <wx/dcgraph.h>
#include <wx/dcmemory.h>
namespace fs = boost::filesystem; namespace fs = boost::filesystem;
@@ -1152,6 +1154,7 @@ void AuxiliaryPanel::update_all_cover()
m_sizer_description->Add(m_text_description, 0, wxALIGN_TOP | wxRIGHT, FromDIP(10)); m_sizer_description->Add(m_text_description, 0, wxALIGN_TOP | wxRIGHT, FromDIP(10));
m_input_description = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, m_input_description = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(450), FromDIP(300)), wxTE_MULTILINE | wxTE_PROCESS_ENTER); wxSize(FromDIP(450), FromDIP(300)), wxTE_MULTILINE | wxTE_PROCESS_ENTER);
m_input_description->SetBackgroundColour(*wxWHITE);
m_input_description->SetFont(::Label::Body_14); m_input_description->SetFont(::Label::Body_14);
m_sizer_description->Add(m_input_description, 0, wxALIGN_CENTER, 0); m_sizer_description->Add(m_input_description, 0, wxALIGN_CENTER, 0);
+1 -1
View File
@@ -20,7 +20,7 @@ namespace GUI {
AuxiliaryDialog::AuxiliaryDialog(wxWindow * parent) AuxiliaryDialog::AuxiliaryDialog(wxWindow * parent)
: DPIDialog(parent, wxID_ANY, _L("Auxiliaryies"), wxDefaultPosition, : DPIDialog(parent, wxID_ANY, _L("Auxiliaries"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{ {
m_aux_list = new AuxiliaryList(this); m_aux_list = new AuxiliaryList(this);
@@ -60,6 +60,9 @@
#include "libslic3r/PresetBundle.hpp" #include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/PartPlate.hpp" #include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/Utils/PrintHost.hpp" #include "slic3r/Utils/PrintHost.hpp"
#ifdef _WIN32
#include <excpt.h>
#endif
namespace Slic3r { namespace Slic3r {
+29 -41
View File
@@ -8,7 +8,6 @@
#include "slic3r/GUI/Camera.hpp" // N: look down the sketch plane normal #include "slic3r/GUI/Camera.hpp" // N: look down the sketch plane normal
#include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/Plater.hpp" #include "slic3r/GUI/Plater.hpp"
#include "slic3r/GUI/PartPlate.hpp" // the current plate's origin: the first view's offset
#include "slic3r/GUI/ImGuiWrapper.hpp" #include "slic3r/GUI/ImGuiWrapper.hpp"
#include "slic3r/GUI/GLToolbar.hpp" #include "slic3r/GUI/GLToolbar.hpp"
#include "slic3r/GUI/Event.hpp" #include "slic3r/GUI/Event.hpp"
@@ -92,7 +91,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
m_canvas->enable_plate_chrome(false); m_canvas->enable_plate_chrome(false);
m_canvas->enable_labels(false); m_canvas->enable_labels(false);
m_canvas->enable_sinking_contours(false); // they would be sliced from the plater's meshes m_canvas->enable_sinking_contours(false); // they would be sliced from the plater's meshes
m_canvas->set_design_canvas(true); // home-position bed, triad and CAD grid at the modeling origin m_canvas->set_axes_at_bed_center(true); // triad at bed centre = modeling origin
m_canvas->set_design_sketch_tool(&m_sketch_tool); m_canvas->set_design_sketch_tool(&m_sketch_tool);
m_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) { m_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) {
@@ -191,11 +190,8 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
refresh_bed(); refresh_bed();
// The view this canvas opens on. Built lazily, on the way into the Design tab, so this // The view this canvas opens on. Built lazily, on the way into the Design tab, so this
// is the view the user is looking at right now — moved off the current plate onto the // is the view the user is looking at right now.
// Design bed, which stays at the printer bed's home whichever plate is current.
m_parked_camera = wxGetApp().plater()->get_camera(); m_parked_camera = wxGetApp().plater()->get_camera();
if (PartPlate* plate = wxGetApp().plater()->get_partplate_list().get_curr_plate())
m_parked_camera.translate_world(-plate->get_origin());
// Before any of this class's own Binds below: wx calls dynamically bound handlers in // Before any of this class's own Binds below: wx calls dynamically bound handlers in
// reverse order of binding, and GLCanvas3D swallows several events without skipping them — // reverse order of binding, and GLCanvas3D swallows several events without skipping them —
@@ -382,14 +378,10 @@ void DesignCanvas::reload(bool keep_view)
v->set_color(c); v->set_color(c);
} }
} else if (obj_idx == 1) { } else if (obj_idx == 1) {
// The ghost is the whole resulting model, normally drawn as a faint blue overlay on // The ghost is normally a faint blue overlay on the visible body. In preview-only
// the visible bodies. Every face the feature leaves alone is in both, at the same // mode it IS the result (base bodies hidden), so render it opaque so it reads as a
// depth, so the ghost is drawn with a depth bias: the bodies win on those faces instead // finished solid rather than a see-through hint.
// of the two copies z-fighting, and the ghost shows only where the result reaches past
// the bodies. In preview-only mode it IS the result (base bodies hidden), so render it
// opaque so it reads as a finished solid rather than a see-through hint.
v->set_color(m_body_hidden ? ColorRGBA(0.40f, 0.82f, 1.0f, 1.0f) : ghost); v->set_color(m_body_hidden ? ColorRGBA(0.40f, 0.82f, 1.0f, 1.0f) : ghost);
v->depth_bias = true;
} }
} }
@@ -414,7 +406,6 @@ void DesignCanvas::set_bodies(const std::vector<TriangleMesh>* body_meshes,
if (body_meshes == nullptr || body_meshes->empty()) { clear_mesh(); return; } if (body_meshes == nullptr || body_meshes->empty()) { clear_mesh(); return; }
m_body_meshes = body_meshes; m_body_meshes = body_meshes;
m_sketch_tool.refresh_body_edges(); // of the bodies set_solid_pick() pointed the tool at
m_lit_faces = m_sketch_tool.selected_faces(); m_lit_faces = m_sketch_tool.selected_faces();
rebuild_bodies(); rebuild_bodies();
reload(!m_first_frame); reload(!m_first_frame);
@@ -424,8 +415,6 @@ void DesignCanvas::clear_mesh()
{ {
m_body_meshes = nullptr; m_body_meshes = nullptr;
m_volumes.clear(); m_volumes.clear();
// No solid, so no edge lines, pick or hover either, as after a rebuild that leaves no body.
m_sketch_tool.set_solid_pick(nullptr, nullptr, nullptr, nullptr);
if (!m_model.objects.empty()) { if (!m_model.objects.empty()) {
m_model.delete_object((size_t)0); m_model.delete_object((size_t)0);
reload(true); reload(true);
@@ -481,6 +470,13 @@ void DesignCanvas::begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode
if (m_canvas_widget) m_canvas_widget->Refresh(); if (m_canvas_widget) m_canvas_widget->Refresh();
} }
void DesignCanvas::set_sketch_plane(const SketchPlane& plane)
{
m_sketch_tool.set_plane(plane); // keeps the 2D entities; only the carrier plane changes
if (m_canvas) m_canvas->set_as_dirty();
if (m_canvas_widget) m_canvas_widget->Refresh();
}
void DesignCanvas::edit_sketch(const std::vector<SketchEntity>& entities, void DesignCanvas::edit_sketch(const std::vector<SketchEntity>& entities,
const std::vector<SketchEntityConstraintDef>& constraints, const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane) const SketchPlane& plane)
@@ -554,7 +550,6 @@ void DesignCanvas::refresh_bed()
double printable_height = 100.0; double printable_height = 100.0;
const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height"); const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height");
if (ph_opt) printable_height = ph_opt->value; if (ph_opt) printable_height = ph_opt->value;
// No position: the Design bed stays at the printer bed's home, whichever plate is current.
m_bed.set_shape(bed_shape_opt->values, printable_height, {}, {}, "", false); // mainline added extruder_areas/heights params m_bed.set_shape(bed_shape_opt->values, printable_height, {}, {}, "", false); // mainline added extruder_areas/heights params
} }
@@ -736,6 +731,8 @@ void DesignCanvas::clear_move_gizmo()
request_repaint(); request_repaint();
} }
bool DesignCanvas::moving_body() const { return m_sketch_tool.moving_body(); }
void DesignCanvas::set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb) void DesignCanvas::set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb)
{ {
m_sketch_tool.on_body_move_changed = std::move(cb); m_sketch_tool.on_body_move_changed = std::move(cb);
@@ -1028,11 +1025,6 @@ void DesignCanvas::set_on_datum_base_picked(std::function<void(int)> cb)
m_sketch_tool.on_datum_base_picked = std::move(cb); m_sketch_tool.on_datum_base_picked = std::move(cb);
} }
void DesignCanvas::set_selected_base(std::function<int()> cb)
{
m_sketch_tool.selected_base = std::move(cb);
}
void DesignCanvas::set_on_sketch_exit(std::function<void()> cb) void DesignCanvas::set_on_sketch_exit(std::function<void()> cb)
{ {
m_sketch_tool.on_exit = std::move(cb); m_sketch_tool.on_exit = std::move(cb);
@@ -1055,18 +1047,15 @@ void DesignCanvas::set_on_context_menu(std::function<void(const wxPoint&)> cb)
return; return;
m_ctx_bound = true; m_ctx_bound = true;
// Bound AFTER GLCanvas3D's own handlers, so this runs first and can consume the event. // Bound AFTER GLCanvas3D's own handlers, so this runs first and can consume the event.
// It only consumes when it actually opens the offer. // It only consumes when it actually opens the offer; every other right-click still falls
// Right-drag may pan or orbit (Preferences > Control). Without remembering where the press // through to the polyline-chain end and the move gizmo, which were there first.
// landed, every such drag ended by popping the offer over wherever the camera stopped — the // Right-drag pans. Without remembering where the press landed, every pan ended by popping
// menu appearing as the reward for moving the view. A right-click is the release of a // the offer over wherever the camera stopped — the menu appearing as the reward for moving
// STATIONARY press (kCadRightClickDriftPx); only that reaches the sketch tool or the offer. // the view. The offer is the release of a STATIONARY right-click (kCadRightClickDriftPx).
m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) { m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
m_ctx_press = e.GetPosition(); m_ctx_press = e.GetPosition();
m_ctx_travelled = false; m_ctx_travelled = false;
// Drop any press the tool still keeps (only a click's release takes it, so a pan's stays) e.Skip(); // the canvas still needs the press to seed the orbit
// before the canvas offers it this one, which ImGui may take instead.
m_sketch_tool.drop_right_click();
e.Skip(); // the canvas still needs the press to seed a pan or an orbit
}); });
m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) { m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) {
if (e.RightIsDown()) { if (e.RightIsDown()) {
@@ -1077,13 +1066,12 @@ void DesignCanvas::set_on_context_menu(std::function<void(const wxPoint&)> cb)
}); });
m_canvas_widget->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent& e) { m_canvas_widget->Bind(wxEVT_RIGHT_UP, [this](wxMouseEvent& e) {
const wxPoint d = e.GetPosition() - m_ctx_press; const wxPoint d = e.GetPosition() - m_ctx_press;
// Click, or navigation? A press that travelled panned or orbited; one that did not, did not. // Always read-and-clear, even when another guard already rules the offer out, or a
// terminator recorded under one condition would still be pending under the next.
const bool terminated = m_sketch_tool.take_right_consumed();
// Click, or navigation? A press that travelled orbited; one that did not, did not.
const bool is_click = !m_ctx_travelled && std::max(std::abs(d.x), std::abs(d.y)) <= kCadRightClickDriftPx; const bool is_click = !m_ctx_travelled && std::max(std::abs(d.x), std::abs(d.y)) <= kCadRightClickDriftPx;
// Ending a chain or abandoning an anchor uses the click up. if (m_on_context_menu && !terminated && !inline_busy() && is_click) {
const bool terminated = is_click && m_canvas && m_sketch_tool.take_right_click(*m_canvas);
if (terminated)
m_canvas->set_as_dirty(); // drawn by the canvas's own RightUp (e.Skip below) or at idle
else if (m_on_context_menu && !inline_busy() && is_click) {
// The menu belongs to what you POINTED AT — and pointing happened at the PRESS, not // The menu belongs to what you POINTED AT — and pointing happened at the PRESS, not
// at the release, so the raycast uses the press position. Within a 3 px budget the // at the release, so the raycast uses the press position. Within a 3 px budget the
// two are the same pixel in practice; using the press is what makes that a // two are the same pixel in practice; using the press is what makes that a
@@ -1179,6 +1167,9 @@ void DesignCanvas::set_readout(const std::string& text)
if (m_canvas) m_canvas->set_as_dirty(); // drawn by the next frame (the tool feeds this from one) if (m_canvas) m_canvas->set_as_dirty(); // drawn by the next frame (the tool feeds this from one)
} }
// Clear of the view cube and the two round view buttons, which own the bottom-left corner.
static constexpr float kStatusHudLeftInset = 190.f;
void DesignCanvas::set_status_text(const wxString& text, const wxColour& colour) void DesignCanvas::set_status_text(const wxString& text, const wxColour& colour)
{ {
if (text == m_status_hud_last && colour == m_status_hud_colour) return; if (text == m_status_hud_last && colour == m_status_hud_colour) return;
@@ -1213,11 +1204,8 @@ void DesignCanvas::render_hud()
ImGuiWrapper::pop_common_window_style(); ImGuiWrapper::pop_common_window_style();
}; };
if (!m_status_hud_last.IsEmpty()) { if (!m_status_hud_last.IsEmpty()) {
// Past the view cube and the round view buttons, which own the bottom-left corner. Asked
// of the canvas, which lays them out: they follow the monitor's DPI on Windows, where `em`
// does not, so a fixed inset in `em` let them cover the start of the line at 150%.
// A sentence can be a sentence: it wraps to the room left of the readout chip. // A sentence can be a sentence: it wraps to the room left of the readout chip.
const float left = m_canvas->get_canvas_toolbar_right() + margin; const float left = kStatusHudLeftInset * em;
const ImVec4 col = m_status_hud_colour.IsOk() const ImVec4 col = m_status_hud_colour.IsOk()
? ImVec4(m_status_hud_colour.Red() / 255.f, m_status_hud_colour.Green() / 255.f, ? ImVec4(m_status_hud_colour.Red() / 255.f, m_status_hud_colour.Green() / 255.f,
m_status_hud_colour.Blue() / 255.f, 1.f) m_status_hud_colour.Blue() / 255.f, 1.f)
+4 -7
View File
@@ -3,7 +3,6 @@
#include <vector> #include <vector>
#include "libslic3r/Point.hpp" #include "libslic3r/Point.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/Color.hpp" #include "libslic3r/Color.hpp"
#include <utility> #include <utility>
#include <wx/colour.h> #include <wx/colour.h>
@@ -49,11 +48,10 @@ public:
// Multi-body display: one GLVolume per body, each coloured distinctly (per-body colour). // Multi-body display: one GLVolume per body, each coloured distinctly (per-body colour).
// `visible` (optional, indexed by body) hides bodies whose flag is false. `body_meshes` is kept // `visible` (optional, indexed by body) hides bodies whose flag is false. `body_meshes` is kept
// by address (a stable panel member) and read again whenever the selection changes. The // by address (a stable panel member) and read again whenever the selection changes.
// bodies' edge lines are refreshed with them (DesignSketchTool::refresh_body_edges).
void set_bodies(const std::vector<TriangleMesh>* body_meshes, void set_bodies(const std::vector<TriangleMesh>* body_meshes,
const std::vector<bool>& visible = {}); const std::vector<bool>& visible = {});
void clear_mesh(); // no bodies: drops their volumes and the tool's edge lines, pick and selection void clear_mesh();
void set_preview_mesh(const TriangleMesh& mesh); void set_preview_mesh(const TriangleMesh& mesh);
void clear_preview(); void clear_preview();
@@ -68,6 +66,7 @@ public:
const std::vector<SketchEntityConstraintDef>& constraints, const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane); const SketchPlane& plane);
void set_sketch_tool(DesignSketchTool::Mode mode); void set_sketch_tool(DesignSketchTool::Mode mode);
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when a reference plane is clicked in 3D
void set_sketch_construction(bool c); void set_sketch_construction(bool c);
// Flip the sketch selection between construction and real geometry; returns the // Flip the sketch selection between construction and real geometry; returns the
// number of entities changed (0 = nothing selected, caller falls back to the mode). // number of entities changed (0 = nothing selected, caller falls back to the mode).
@@ -84,8 +83,6 @@ public:
void finish_sketch(); void finish_sketch();
bool is_sketching() const; bool is_sketching() const;
void refresh_bed(); // re-sync the bed to the current printer (call on tab activation) void refresh_bed(); // re-sync the bed to the current printer (call on tab activation)
// Centre of the Design bed: the printer bed at its home position, whichever plate is current.
Vec2d bed_center() const { return m_bed.build_volume().bed_center(); }
// The Camera is Plater-owned and shared with Prepare/Preview/Assemble; GLCanvas3D has no // The Camera is Plater-owned and shared with Prepare/Preview/Assemble; GLCanvas3D has no
// per-canvas camera, so every orbit here would otherwise overwrite what the editor tabs // per-canvas camera, so every orbit here would otherwise overwrite what the editor tabs
// show. Exactly one of the two views is live at a time, so entering and leaving are the // show. Exactly one of the two views is live at a time, so entering and leaving are the
@@ -160,6 +157,7 @@ public:
void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform, void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform,
double body_radius); double body_radius);
void clear_move_gizmo(); void clear_move_gizmo();
bool moving_body() const;
void set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb); void set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb);
// Visual Fillet/Chamfer radius gizmo: when a solid edge is picked, anchor a radius arrow on // Visual Fillet/Chamfer radius gizmo: when a solid edge is picked, anchor a radius arrow on
// it; drag/edit fire the radius callback. Returns false if no edge is currently picked. // it; drag/edit fire the radius callback. Returns false if no edge is currently picked.
@@ -236,7 +234,6 @@ public:
std::vector<std::string> labels = {}); // clickable labelled reference planes std::vector<std::string> labels = {}); // clickable labelled reference planes
void clear_base_pick(); void clear_base_pick();
void set_on_datum_base_picked(std::function<void(int)> cb); void set_on_datum_base_picked(std::function<void(int)> cb);
void set_selected_base(std::function<int()> cb); // the reference plane drawn selected, or -1
void set_on_sketch_exit(std::function<void()> cb); // Esc -> exit the tool void set_on_sketch_exit(std::function<void()> cb); // Esc -> exit the tool
void set_on_sketch_exit_refused(std::function<void()> cb); // Esc declined: sketch has work void set_on_sketch_exit_refused(std::function<void()> cb); // Esc declined: sketch has work
void set_on_sketch_notice(std::function<void(const std::string&, bool)> cb); // tool refusals/side effects void set_on_sketch_notice(std::function<void(const std::string&, bool)> cb); // tool refusals/side effects
+96 -93
View File
@@ -13,6 +13,9 @@
#ifndef L #ifndef L
#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp #define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp
#endif #endif
#ifndef L_CONTEXT
#define L_CONTEXT(s, context) s
#endif
namespace Slic3r { namespace GUI { namespace Slic3r { namespace GUI {
@@ -52,7 +55,7 @@ inline uint32_t offer_bit(OfferSel s) { return 1u << int(s); }
// nullptr -> kernel support exists, no GUI path yet (row shows disabled) // nullptr -> kernel support exists, no GUI path yet (row shows disabled)
struct OfferVerb { struct OfferVerb {
const char* id; const char* id;
const char* name; // drawing-office word (L10); marked L(); translated at use const char* name; // drawing-office word (L10); msgctxt "Design", translated at use
int row; // index into kOfferRowNames, the ratified address — NEVER reorder int row; // index into kOfferRowNames, the ratified address — NEVER reorder
const char* key; // shortcut shown in the row, or nullptr const char* key; // shortcut shown in the row, or nullptr
const char* action; const char* action;
@@ -99,107 +102,107 @@ static const bool kOfferRowFlat[] = {
}; };
static const OfferVerb kOfferVerbs[] = { static const OfferVerb kOfferVerbs[] = {
{"sketch", L("Sketch"), 0, "Shift+S", "key:S+S", L("Select a flat face or a reference plane to sketch on"), 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", L("Sketch on the selected flat face or plane, or click one next")}, {"sketch", L_CONTEXT("Sketch", "Design"), 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")}, {"extrude", L_CONTEXT("Extrude", "Design"), 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")}, {"revolve", L_CONTEXT("Revolve", "Design"), 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")}, {"sweep", L_CONTEXT("Sweep", "Design"), 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")}, {"loft", L_CONTEXT("Loft", "Design"), 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)")}, {"thicken", L_CONTEXT("Thicken", "Design"), 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")}, {"rib", L_CONTEXT("Rib", "Design"), 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")}, {"boolean", L_CONTEXT("Join", "Design"), 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_subtract", L_CONTEXT("Subtract", "Design"), 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")}, {"bool_intersect", L_CONTEXT("Intersect", "Design"), 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_extrude", L_CONTEXT("Surface Extrude", "Design"), 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_revolve", L_CONTEXT("Surface Revolve", "Design"), 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_loft", L_CONTEXT("Surface Loft", "Design"), 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")}, {"surf_fill", L_CONTEXT("Surface Fill", "Design"), 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")}, {"thicken_surf", L_CONTEXT("Thicken Surface", "Design"), 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")}, {"hole", L_CONTEXT("Hole", "Design"), 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")}, {"thread", L_CONTEXT("Thread", "Design"), 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")}, {"shell", L_CONTEXT("Shell", "Design"), 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")}, {"cut", L_CONTEXT("Cut", "Design"), 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")}, {"split", L_CONTEXT("Split", "Design"), 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")}, {"fillet", L_CONTEXT("Fillet", "Design"), 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")}, {"chamfer", L_CONTEXT("Chamfer", "Design"), 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")}, {"draft", L_CONTEXT("Draft", "Design"), 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")}, {"surf_offset", L_CONTEXT("Surface Offset", "Design"), 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", L_CONTEXT("Linear pattern", "Design"), 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")}, {"pattern_circular", L_CONTEXT("Circular pattern", "Design"), 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")}, {"mirror", L_CONTEXT("Mirror", "Design"), 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")}, {"pat_curve", L_CONTEXT("Pattern on Curve", "Design"), 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")}, {"transform", L_CONTEXT("Move", "Design"), 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)")}, {"mate", L_CONTEXT("Mate", "Design"), 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")}, {"align", L_CONTEXT("Align to", "Design"), 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)")}, {"plane", L_CONTEXT("Plane", "Design"), 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)")}, {"axis", L_CONTEXT("Axis", "Design"), 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)")}, {"coordsys_v", L_CONTEXT("Coordinate system", "Design"), 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")}, {"helix", L_CONTEXT("Helix", "Design"), 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")}, {"project", L_CONTEXT("Project", "Design"), 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")}, {"measure", L_CONTEXT("Measure", "Design"), 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")}, {"mass_props", L_CONTEXT("Volume and area", "Design"), 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")}, {"interference", L_CONTEXT("Interference", "Design"), 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")}, {"edit_feature", L_CONTEXT("Edit", "Design"), 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…"), 8, "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)")}, {"rename", L_CONTEXT("Rename…", "Design"), 8, "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")}, {"delete_face", L_CONTEXT("Delete Face", "Design"), 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"), 8, nullptr, "btn:colour", nullptr, 0x000001feu, 1, 0, false, false, nullptr, "color_palette", L("Set the selected body's display color")}, {"colour", L_CONTEXT("Color", "Design"), 8, 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", L_CONTEXT("Delete", "Design"), 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")}, {"delete_body", L_CONTEXT("Delete Body", "Design"), 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_line_t", L_CONTEXT("Line", "Design"), 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_polyline", L_CONTEXT("Polyline", "Design"), 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", L_CONTEXT("Corner rectangle", "Design"), 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_center", L_CONTEXT("Center rectangle", "Design"), 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_oblique", L_CONTEXT("Oblique rectangle", "Design"), 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_rect_rounded", L_CONTEXT("Rounded rectangle", "Design"), 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", L_CONTEXT("Center circle", "Design"), 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_2pt", L_CONTEXT("2-point circle", "Design"), 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_circle_3pt", L_CONTEXT("3-point circle", "Design"), 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_t", L_CONTEXT("3-point arc", "Design"), 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_tangent", L_CONTEXT("Tangent arc", "Design"), 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_arc_center", L_CONTEXT("Center-point arc", "Design"), 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", L_CONTEXT("Slot", "Design"), 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_slot_arc", L_CONTEXT("Arc slot", "Design"), 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", L_CONTEXT("Ellipse", "Design"), 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_ellipse_arc", L_CONTEXT("Elliptical arc", "Design"), 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_spline", L_CONTEXT("Spline", "Design"), 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_3", L_CONTEXT("Triangle", "Design"), 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_4", L_CONTEXT("Square", "Design"), 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_poly_5", L_CONTEXT("Pentagon", "Design"), 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_polygon", L_CONTEXT("Hexagon", "Design"), 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_8", L_CONTEXT("Octagon", "Design"), 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_12", L_CONTEXT("Dodecagon", "Design"), 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_inscribed", L_CONTEXT("Inscribed", "Design"), 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_poly_circumscribed", L_CONTEXT("Circumscribed", "Design"), 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_point_t", L_CONTEXT("Point", "Design"), 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; it becomes a Text feature on this sketch's plane, editable later")}, {"sk_text", L_CONTEXT("Text", "Design"), 0, nullptr, "btn:text", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_text", L("Type text; it becomes a Text feature on this sketch's plane, editable later")},
{"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_svg", L_CONTEXT("SVG", "Design"), 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_offset", L_CONTEXT("Offset", "Design"), 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_trim", L_CONTEXT("Trim", "Design"), 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_fillet", L_CONTEXT("Fillet", "Design"), 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_chamfer", L_CONTEXT("Chamfer", "Design"), 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", L_CONTEXT("Linear array", "Design"), 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_array_polar", L_CONTEXT("Polar array", "Design"), 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_mirror", L_CONTEXT("Mirror", "Design"), 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_move", L_CONTEXT("Move", "Design"), 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_rotate", L_CONTEXT("Rotate", "Design"), 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_scale", L_CONTEXT("Scale", "Design"), 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_dimension", L_CONTEXT("Dimension", "Design"), 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")}, {"sk_constrain", L_CONTEXT("Constrain", "Design"), 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 // 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 // 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. // 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")}, {"constrain", L_CONTEXT("Constrain sketch", "Design"), 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_construct", L_CONTEXT("Construction", "Design"), 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_extend", L_CONTEXT("Extend", "Design"), 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")}, {"sk_delete", L_CONTEXT("Delete", "Design"), 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 // 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 // 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 // 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 // selection. Without these, an element's own numbers were reachable only by arming the
// Dimension tool and re-picking geometry that was already selected. // Dimension tool and re-picking geometry that was already selected.
{"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_length", L_CONTEXT("Length…", "Design"), 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_radius", L_CONTEXT("Radius / diameter…", "Design"), 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")}, {"sk_angdist", L_CONTEXT("Angle / distance…", "Design"), 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; static const int kOfferVerbCount = 92;
File diff suppressed because it is too large Load Diff
+6 -31
View File
@@ -383,16 +383,7 @@ private:
void update_rib_gizmo(); // in-plane slab footprint + thickness handles (Rib card) void update_rib_gizmo(); // in-plane slab footprint + thickness handles (Rib card)
void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport
void refresh_mate_connectors(); // push connector frames so verse + polarity are visible void refresh_mate_connectors(); // push connector frames so verse + polarity are visible
void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, plane choice, Plane tool void update_reference_planes(); // persistent XY/XZ/YZ reference planes (fallback when no object)
void toggle_origin(); // the Origin row's eye (and Ctrl+Shift+O): keep the reference planes up
void toggle_bed(); // the Bed row's eye (and Ctrl+Shift+B): draw the printer bed, or not
void refresh_pinned(); // rebuild the Origin and Bed rows (their eyes and label colours)
// Sketch: open a session on the picked face or plane, or with none picked, put the planes up
// and wait for one (m_choosing_sketch_plane).
void start_sketch();
void start_sketch_on_target(bool offer_tools); // a session on sketch_plane_from_selection()
void end_sketch_plane_choice(); // leave the plane choice without a sketch; no-op outside it
bool sketch_plane_pick_live() const; // may a pick made now still open the sketch?
CadDocument m_doc; CadDocument m_doc;
@@ -422,6 +413,7 @@ private:
ScalableButton* m_commit_btn{nullptr}; // main face: runs the current commit mode ScalableButton* m_commit_btn{nullptr}; // main face: runs the current commit mode
DropDown* m_commit_drop{nullptr}; // commit-mode choices, owned via m_flyout_keepalive DropDown* m_commit_drop{nullptr}; // commit-mode choices, owned via m_flyout_keepalive
wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place) wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place)
CheckBox* m_show_bed{nullptr}; // view option: draw the printer bed + plate grid, or not
wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle) wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle)
wxSizer* m_box_sketch{nullptr}; wxSizer* m_box_sketch{nullptr};
wxSizer* m_box_extrude{nullptr}; wxSizer* m_box_extrude{nullptr};
@@ -579,20 +571,8 @@ private:
// deliberately no dropdown for it. e1p. // deliberately no dropdown for it. e1p.
int m_ref_plane{0}; int m_ref_plane{0};
// m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY" // m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY"
// from "nobody has chosen anything yet". This does. A selection like a picked face: the sketch // from "nobody has chosen anything yet". This does.
// opened on it uses it up, and Esc or a click on nothing lets go of it.
bool m_plane_picked{false}; bool m_plane_picked{false};
// Sketch was pressed with nothing to sketch on: the reference planes are up and the next
// reference plane or flat face clicked opens the sketch on it. Still Feature mode — sketch
// mode is entered only with the session (start_sketch_on_target).
bool m_choosing_sketch_plane{false};
// The Feature tree's Origin row: keeps the reference planes and their axes up
// (update_reference_planes). A view preference (AppConfig "design_show_origin"), not part of
// the recipe.
bool m_show_origin{false};
// The Feature tree's Bed row: draws the printer bed and its plate grid. A view preference
// (AppConfig "design_show_bed"), not part of the recipe.
bool m_show_bed{true};
ComboBox* m_shape{nullptr}; ComboBox* m_shape{nullptr};
ComboBox* m_mode{nullptr}; ComboBox* m_mode{nullptr};
wxSpinCtrlDouble* m_width{nullptr}; wxSpinCtrlDouble* m_width{nullptr};
@@ -933,11 +913,9 @@ private:
std::function<void(double)> m_value_cont; // deferred apply, run on Confirm std::function<void(double)> m_value_cont; // deferred apply, run on Confirm
std::function<void()> m_value_cancel; // optional action when the card is cancelled std::function<void()> m_value_cancel; // optional action when the card is cancelled
// Feature tree: the Origin and Bed rows, a fixed block of view switches that never scrolls // Feature tree: one row per feature, in feature order, with a per-type icon and the row's
// and selects nothing, then, in a frame of its own below them, one row per feature, in feature // own Edit / Show-hide / Delete icons. Callers use row indices via
// order, with a per-type icon and the row's own Edit / Show-hide / Delete icons. Callers use // tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows.
// row indices via tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows.
DesignRowList* m_pinned{nullptr};
DesignRowList* m_tree{nullptr}; DesignRowList* m_tree{nullptr};
// The faces the selected feature row made (CadDocument::faces_made_by), drawn as selected. // The faces the selected feature row made (CadDocument::faces_made_by), drawn as selected.
// Finding them replays the history, so they are kept per row and topology generation. // Finding them replays the history, so they are kept per row and topology generation.
@@ -950,7 +928,6 @@ private:
void request_feature_highlight(); void request_feature_highlight();
void update_feature_highlight(); void update_feature_highlight();
bool deselect_rows(); // Esc / a click on nothing: drop the tree and Bodies rows bool deselect_rows(); // Esc / a click on nothing: drop the tree and Bodies rows
bool drop_plane_pick(); // ...and a picked reference plane; true if one was picked
// Bodies list under the feature tree: one row per body (parallel to m_doc.bodies). Selecting // Bodies list under the feature tree: one row per body (parallel to m_doc.bodies). Selecting
// one highlights that body and makes it the target for the next op. // one highlights that body and makes it the target for the next op.
DesignRowList* m_parts{nullptr}; DesignRowList* m_parts{nullptr};
@@ -987,8 +964,6 @@ private:
void rebuild_disp_meshes(); // recompute m_disp_* from m_doc + m_body_xform void rebuild_disp_meshes(); // recompute m_disp_* from m_doc + m_body_xform
void feed_bodies(); // push m_disp_* + visibility/xform to the viewport void feed_bodies(); // push m_disp_* + visibility/xform to the viewport
void on_move_body(); // start the move gizmo on the selected body void on_move_body(); // start the move gizmo on the selected body
bool body_move_pending() const { return m_move_body >= 0; } // the Move button's session is open
void end_body_move(bool keep); // leave it: keep the dragged pose, or put the body back
void arm_transform_gizmo(); // arm the move gizmo on the Transform card's body (add mode only) void arm_transform_gizmo(); // arm the move gizmo on the Transform card's body (add mode only)
void on_set_body_color(); // Color tool: pick a per-body display colour override void on_set_body_color(); // Color tool: pick a per-body display colour override
void on_boolean_tool(); // Boolean (combine bodies): needs two solids, then opens the tool void on_boolean_tool(); // Boolean (combine bodies): needs two solids, then opens the tool
+12 -22
View File
@@ -29,19 +29,11 @@ static constexpr int kCellDip = 20; // square action cell, hover chip included
static constexpr int kPadDip = 4; // row edges, and the gap before the action cells static constexpr int kPadDip = 4; // row edges, and the gap before the action cells
static constexpr int kGapDip = 6; // type icon to label static constexpr int kGapDip = 6; // type icon to label
DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable, long style) DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
// wxVListBox defaults to wxBORDER_THEME; `style` defaults to the simple frame the sidebar's // wxVListBox defaults to wxBORDER_THEME; the sidebar's lists take a simple frame.
// lists take, and a list meant to sit unframed on its card passes wxBORDER_NONE. : wxVListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE)
: wxVListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style)
, m_max_visible(std::max(max_visible, 1)) , m_max_visible(std::max(max_visible, 1))
, m_selectable(selectable)
{ {
// A non-selectable list takes no focus: from the mouse because it consumes the presses that
// would focus it, below, and from the keyboard through AcceptsFocus(). wxGTK reads that once,
// while the base class is being built, before the override exists, so it is told here.
if (!m_selectable)
SetCanFocus(false);
Bind(wxEVT_LISTBOX, [this](wxCommandEvent&) { if (on_select) on_select(); }); Bind(wxEVT_LISTBOX, [this](wxCommandEvent&) { if (on_select) on_select(); });
Bind(wxEVT_LISTBOX_DCLICK, [this](wxCommandEvent&) { if (on_activate) on_activate(); }); Bind(wxEVT_LISTBOX_DCLICK, [this](wxCommandEvent&) { if (on_activate) on_activate(); });
@@ -54,20 +46,18 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable,
m_pressed = Hit{}; m_pressed = Hit{};
e.Skip(); e.Skip();
}); });
// A press on an action cell is remembered and skipped, so the list still selects the row. A // A press on an action cell is remembered and skipped, so the list still selects the row.
// non-selectable list keeps every press from wxVListBox, which would select the row and take
// the focus.
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) { Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) {
const Hit h = hit_test(e.GetPosition()); const Hit h = hit_test(e.GetPosition());
m_pressed = h.cell >= 0 ? h : Hit{}; m_pressed = h.cell >= 0 ? h : Hit{};
if (m_selectable) e.Skip(); e.Skip();
}); });
// The second press of a double-click on a cell is a press too, and is not skipped: the list // The second press of a double-click on a cell is a press too, and is not skipped: the list
// would otherwise turn it into a row double-click, so a quick double toggle of the eye would // would otherwise turn it into a row double-click, so a quick double toggle of the eye would
// also open the feature for editing. // also open the feature for editing.
Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& e) { Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& e) {
const Hit h = hit_test(e.GetPosition()); const Hit h = hit_test(e.GetPosition());
if (h.cell < 0) { m_pressed = Hit{}; if (m_selectable) e.Skip(); return; } if (h.cell < 0) { m_pressed = Hit{}; e.Skip(); return; }
m_pressed = h; m_pressed = h;
}); });
Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) { Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
@@ -79,9 +69,9 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable,
const int id = m_rows[pressed.row].actions[pressed.cell].id; const int id = m_rows[pressed.row].actions[pressed.cell].id;
// After the click has finished dispatching: the action may rebuild these rows. The row // After the click has finished dispatching: the action may rebuild these rows. The row
// must still exist and still be the selected one, or the click is dropped rather than // must still exist and still be the selected one, or the click is dropped rather than
// applied to whatever row took its place. A non-selectable list has no selected row. // applied to whatever row took its place.
CallAfter([this, row = pressed.row, id] { CallAfter([this, row = pressed.row, id] {
if (row < int(GetItemCount()) && (!m_selectable || row == GetSelection()) && on_action) if (row < int(GetItemCount()) && row == GetSelection() && on_action)
on_action(row, id); on_action(row, id);
}); });
}); });
@@ -89,7 +79,7 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable,
// press suppresses the wxEVT_CONTEXT_MENU that follows it. // press suppresses the wxEVT_CONTEXT_MENU that follows it.
Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) { Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
const Hit h = hit_test(e.GetPosition()); const Hit h = hit_test(e.GetPosition());
if (m_selectable && h.row != wxNOT_FOUND) select(h.row); if (h.row != wxNOT_FOUND) select(h.row);
e.Skip(); e.Skip();
}); });
Bind(wxEVT_CONTEXT_MENU, [this](wxContextMenuEvent& e) { Bind(wxEVT_CONTEXT_MENU, [this](wxContextMenuEvent& e) {
@@ -105,7 +95,7 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable,
return; return;
// On MSW the menu comes with the button's release, and the pointer may have moved to // On MSW the menu comes with the button's release, and the pointer may have moved to
// another row since the press selected one: the menu is for the row it opens over. // another row since the press selected one: the menu is for the row it opens over.
if (m_selectable) select(row); select(row);
if (on_menu) on_menu(row, screen); if (on_menu) on_menu(row, screen);
}); });
// wxVListBox takes every wheel event, even with nothing to scroll, so a short list would stop // wxVListBox takes every wheel event, even with nothing to scroll, so a short list would stop
@@ -264,7 +254,7 @@ void DesignRowList::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) con
wxColour bg; wxColour bg;
if (IsSelected(n)) if (IsSelected(n))
bg = StateColor::darkModeColorFor(wxColour("#BFE1DE")); bg = StateColor::darkModeColorFor(wxColour("#BFE1DE"));
else if (m_selectable && int(n) == m_hover.row) // a non-selectable row lights only its icons else if (int(n) == m_hover.row)
bg = StateColor::darkModeColorFor(wxColour("#E5F0EE")); bg = StateColor::darkModeColorFor(wxColour("#E5F0EE"));
if (!bg.IsOk()) if (!bg.IsOk())
return; // the list already cleared to its background colour return; // the list already cleared to its background colour
@@ -324,7 +314,7 @@ DesignRowList::Hit DesignRowList::hit_test(const wxPoint& pt) const
const wxRect rect = GetItemRect(h.row); const wxRect rect = GetItemRect(h.row);
const auto& acts = m_rows[h.row].actions; const auto& acts = m_rows[h.row].actions;
for (size_t i = 0; i < acts.size(); ++i) for (size_t i = 0; i < acts.size(); ++i)
if (!acts[i].icon.empty() && cell_rect(rect, acts.size(), i).Contains(pt)) { if (cell_rect(rect, acts.size(), i).Contains(pt)) {
h.cell = int(i); h.cell = int(i);
break; break;
} }
+5 -16
View File
@@ -29,16 +29,12 @@ namespace Slic3r { namespace GUI {
// Selection follows wxTreeCtrl's contract, which DesignPanel's mutual exclusion between the two // Selection follows wxTreeCtrl's contract, which DesignPanel's mutual exclusion between the two
// lists relies on: on_select runs on every change, whether the user or select() made it, and // lists relies on: on_select runs on every change, whether the user or select() made it, and
// selecting the row that is already selected changes nothing and notifies nobody. // selecting the row that is already selected changes nothing and notifies nobody.
//
// A list built non-selectable is a row of switches, not of objects: a click never selects a row,
// the pointer highlights only the action icon under it, and the list never takes the focus. Its
// action icons and its context menu are the only things that answer.
class DesignRowList : public wxVListBox class DesignRowList : public wxVListBox
{ {
public: public:
struct Action { struct Action {
int id; // the owner's code for it, handed back to on_action int id; // the owner's code for it, handed back to on_action
std::string icon; // icon name, e.g. "design_eye"; empty for a blank cell that only holds a column std::string icon; // icon name, e.g. "design_eye"
wxString tip; wxString tip;
}; };
struct Row { struct Row {
@@ -54,8 +50,7 @@ public:
static constexpr const char* hover_chip = "#D4D4D4"; static constexpr const char* hover_chip = "#D4D4D4";
// The list is as tall as its rows, at least one and at most `max_visible`; past that it scrolls. // The list is as tall as its rows, at least one and at most `max_visible`; past that it scrolls.
// `style` is its frame: wxBORDER_NONE for a list that sits unframed on its card. DesignRowList(wxWindow* parent, int max_visible);
DesignRowList(wxWindow* parent, int max_visible, bool selectable = true, long style = wxBORDER_SIMPLE);
// Replace every row. Clears the selection without notifying and cancels a rename in progress; // Replace every row. Clears the selection without notifying and cancels a rename in progress;
// the owner re-selects the row it keeps, which notifies. // the owner re-selects the row it keeps, which notifies.
@@ -72,20 +67,15 @@ public:
std::function<void()> on_select; // the selection changed std::function<void()> on_select; // the selection changed
std::function<void()> on_activate; // the selected row was double-clicked std::function<void()> on_activate; // the selected row was double-clicked
// A row's action icon was clicked. Runs after the click has finished dispatching, while the // A row's action icon was clicked. Runs after the click has finished dispatching, and only
// row still exists and — in a selectable list, where the click itself selected it — is still // while that row is still the selected one; the click itself selected it.
// the selected one.
std::function<void(int row, int id)> on_action; std::function<void(int row, int id)> on_action;
// Context menu on a row, at a screen position; the row under the pointer is selected first, // Context menu on a row, at a screen position; the row under the pointer is selected first.
// unless the list is non-selectable. Pop the menu from inside the call: wxGTK sends it on the
// right press, and a menu deferred past that event closes on the button's release.
std::function<void(int row, const wxPoint& screen)> on_menu; std::function<void(int row, const wxPoint& screen)> on_menu;
// The editor committed `name` for `row`, trimmed and never empty (an empty commit cancels). // The editor committed `name` for `row`, trimmed and never empty (an empty commit cancels).
// Runs after the editor's own events have finished, so the owner may rebuild the rows here. // Runs after the editor's own events have finished, so the owner may rebuild the rows here.
std::function<void(int row, const wxString& name)> on_rename; std::function<void(int row, const wxString& name)> on_rename;
bool AcceptsFocus() const override { return m_selectable && wxVListBox::AcceptsFocus(); }
protected: protected:
void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const override; void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const override;
void OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const override; void OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const override;
@@ -113,7 +103,6 @@ private:
std::vector<Row> m_rows; std::vector<Row> m_rows;
std::map<std::string, ScalableBitmap> m_icons; std::map<std::string, ScalableBitmap> m_icons;
int m_max_visible; int m_max_visible;
bool m_selectable;
int m_row_h{0}; // every row's height, from measure_row() int m_row_h{0}; // every row's height, from measure_row()
Hit m_hover; Hit m_hover;
Hit m_pressed; // action cell under the last left press Hit m_pressed; // action cell under the last left press
File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More