Compare commits

..
Author SHA1 Message Date
Lam Wei Lun f608698439 Initial Commit for switching files operations for OrcaPrinterAgent to using MQTT 2026-10-06 18:25:16 +08:00
Lam Wei Lun 432d6be518 Print/Delete support for .gcode.3mf files 2026-10-06 15:42:32 +08:00
Ian Chua 3e7f0c027a fix: fixes from run_clang_tidy.sh --fix 2026-10-06 14:54:51 +08:00
Lam Wei Lun 504a143b98 Missing headers 2026-10-06 14:19:42 +08:00
Lam Wei Lun a87639ac41 Merge main and fix conflicts 2026-10-06 13:43:22 +08:00
Lam Wei Lun ecbd03fed7 merge main 2026-10-06 13:08:46 +08:00
Lam Wei Lun ccbb73baaf ## Summary
Follow-up to the feature/OrcaSonar review. Scopes the new behavior to the
OrcaSonar agent, fixes several AMS-sync and lifecycle bugs, and makes the
MQTT tests run in CI. Non-OrcaSonar agents keep their existing behavior.

## Changes

Discovery
- Gate the background SSDP scan to a configured OrcaSonar machine instead of
  running for every non-BBL profile; drive discovery on demand from the
  machine-select popup and back off idle rounds from 5s to 60s.
- Add NSLocalNetworkUsageDescription and NSBonjourServices to both macOS
  Info.plist templates.

AMS sync
- Resolve id-less printer-set trays by material type in get_ams_cobox_infos
  and the sidebar badge loop, matching sync_ams_list, so combo arrays and
  badges stay aligned.
- Keep a printer-set tray that resolved by type out of the "unknown or
  incompatible filaments" dialog.

AMS device UI
- Gate the "Empty" tray classification, and the editable temperature fields,
  to the OrcaSonar agent; Bambu keeps "?" and read-only presets.
- Validate AMS material temps on confirm: reject out-of-range values and
  min > max before the command is sent.

Storage tab
- Bind the show/hide handler on the panel's ancestors (StoragePanel and
  MonitorPanel) and unbind on destruction, so the Bambu file system stops
  when the tab is left.

Transport
- Load /etc/ssl/cert.pem on macOS in add_platform_root_certificates, after
  set_default_verify_paths.
- Release lifecycle_mutex before start()'s connect wait so stop() can cancel
  the attempt and signal completion instead of stalling the UI thread.

Localization
- Add OrcaFilesPanel.cpp to list.txt and regenerate the catalogs.

## Tests
- Un-hide the loopback MockBroker integration tests (ephemeral port, no
  network) so ctest registers and runs them.
- Mock broker tracks SUBSCRIBE/UNSUBSCRIBE and delivers push_report only to a
  subscribed topic; the reconnect test asserts the re-SUBSCRIBE.
- Add bbl empty-tray cases, an AMS combo-alignment case, and unknowns
  assertions to the AMS sync tests.
2026-10-06 12:09:23 +08: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
Ian Chua 029bc7928f AMS Sync, Filament Mapping, Storage Tab (#16156)
## Summary

Adds the remaining OrcaPrinterAgent work on top of feature/OrcaSonar:
connector capability gating, AMS filament mapping, and the printer
Storage tab. Contains no changes to the other printer agents (Moonraker,
Qidi, Snapmaker, BBL, Creality) — that refactor belongs to its own
branch.

## Changes

Capability gating (IPrinterAgent, OrcaPrinterAgent)

• Parse supported_features, capabilities.protocol.features, and
supported_commands from get_capabilities, including replies without a
flags block.
• Add supports_command, supports_feature, and uses_filament_mapping
queries with defaults that preserve existing agent behavior.
• Gate macro-backed AMS commands on fms plus the advertised command, and
slot metadata writes on filament_slots. Unknown capability means not
supported.

## AMS filament mapping

• Serialize ams_mapping2 into print.gcode_file.filament_mapping, keyed
by array position, dropping {255,255}; omit when empty so unmapped
prints stay byte-identical.
• Refuse a mapped print visibly until the correlation flag and
filament_mapping capability are satisfied, and refuse partially mapped
prints with an unmapped used filament. The GUI predicate shares the
serializer's definition.
• Resolve printer-set trays to the matching Generic preset by material
type and color; skip forced external-spool selection on printers without
an AMS.
• start_local_print_with_record returns non-success so PrintJob falls
back to start_print.

## AMS device UI on non-Bambu agents

• Resolve the printer model from the agent model id or selected profile
so the AMS Materials Setting dropdown and Confirm button work;
third-party trays keep editable temps.
• Render explicitly-empty trays as "Empty" via a wire-derived is_empty,
keeping the slot editable.
• Hide flow-dynamics K/N controls for non-Bambu agents, leaving Bambu
behavior unchanged.

## Storage tab (printer G-code files)

• New IPrinterAgent file APIs — list_printer_files,
get_printer_file_thumbnail, delete_printer_file,
get_printer_file_metadata — with ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED
defaults.
• NetworkAgent forwards to the bound agent; OrcaPrinterAgent implements
them against /server/files.
• New StoragePanel / OrcaFilesPanel and a Printer/ file-grid abstraction
(IFileGridModel, RemoteFileGridModel, BambuFileGridModel).

## Tests

Covers serializer keying/omission, GUI/agent gate agreement, payload
shape, capability parsing and per-command AMS gating, model resolution,
empty-tray classification, and AMS sync.
2026-10-05 18:52:45 +08:00
Lam Wei Lun 0a8fce0913 Add support for printing gcode files in the Storage tab 2026-10-05 18:22:29 +08:00
Lam Wei Lun 3942ba7005 OrcaPrinterAgent Storage Tab 2026-10-05 18:22:29 +08:00
Lam Wei Lun 0110572a77 Removal of specific ORCA_PRINTER_AGENT_ID usage in DeviceManager/SelectMachine/SendMultiMachinePage. Generalized the capabilities under IPrinterAgent 2026-10-05 18:22:29 +08:00
Lam Wei Lun 2c2869ab07 fix: make the AMS device UI work on non-Bambu printer agents
Resolve the filament list's printer model from the agent-reported model id or the selected printer profile, so the AMS Materials Setting dropdown and its Confirm button work on OrcaSonar. Third-party trays pre-fill and allow editing their temps, and the popup's initial selection no longer overwrites them.

Render explicitly-empty trays as "Empty" instead of "?" using a wire-derived DevAmsTray::is_empty, while keeping the slot editable.

Hide the flow-dynamics K/N controls for non-Bambu agents — tile, dialog, validation and sends — with Bambu behavior unchanged.

Tests cover model resolution, the K/N gate and empty-tray classification.
2026-10-05 18:22:29 +08:00
Lam Wei Lun bf63eb1226 feat: enable filament mapping and resolve printer-set AMS trays by material type
Drop the index-correlation gate so mapped prints are gated only on the filament_mapping capability
Resolve AMS trays set on the printer UI to the matching Generic preset using their material type and color
Skip the forced external-spool selection on printers without an AMS and add coverage for the mapping gates
2026-10-05 18:22:26 +08:00
Lam Wei Lun 38e57888a5 feat: add filament mapping and AMS capability gates for the orca printer agent
Parse connector-scope capabilities from get_capabilities: supported_features, capabilities.protocol.features and supported_commands are read for orca devices even when the reply has no flags block, reset before each parse, and left untouched for Bambu.

Gate macro-backed AMS commands on fms plus the advertised command (change filament, select tray, control, user settings, RFID, drying stop); gate slot metadata writes on filament_slots. Unknown capability is not support.

Serialize PrintParams::ams_mapping2 into print.gcode_file.filament_mapping with the array position as filament_index and {255,255} dropped. The field is omitted when empty, so an unmapped print stays byte-identical. A mapped print is refused visibly while ORCA_FILAMENT_MAPPING_CORRELATION_VERIFIED is false or the connector did not advertise filament_mapping; the GUI predicate shares the serializer's definition so both gates agree. Refuse a partially mapped print that leaves a used filament without a target.

Return a non-success result from start_local_print_with_record so PrintJob falls back to start_print instead of treating an unsent print as success.

Tests cover serializer keying and omission, GUI/agent gate agreement, payload shape, the with_record result, capability parsing, and the per-command AMS gate.
2026-10-05 18:22:23 +08:00
Ian Chua fc079b2e5b test: add OrcaSonar printer agent coverage 2026-10-05 16:40:22 +08:00
Ian Chua a88843c6f8 feat: initial implementation of OrcaSonar Printer Agent 2026-10-05 16:15:17 +08:00
265 changed files with 215583 additions and 41594 deletions
+1
View File
@@ -61,6 +61,7 @@ CheckOptions:
termios\.h;
[/\\](um|shared)[/\\].*;
sal\.h;
tchar\.h;
clipper[/\\]clipper\.hpp;
png(lib)?conf\.h;
mcut[/\\]platform\.h;
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
- name: Look for changed C++ files
id: changes
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"
else
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.
- 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.
- 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.
### Verifying
+7
View File
@@ -131,6 +131,13 @@
<true/>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSLocalNetworkUsageDescription</key>
<string>OrcaSlicer uses your local network to discover and connect to printers.</string>
<key>NSBonjourServices</key>
<array>
<string>_octoprint._tcp</string>
<string>_http._tcp</string>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Disable App Transport Security. Resolves https://github.com/OrcaSlicer/OrcaSlicer/issues/791 -->
@@ -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` |
| `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` |
| `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 |
| `Esc` | — | — | `escape()` ladder | — | one route whatever holds focus (`:4228-4231`) |
| `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 |
| `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 |
`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,
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
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
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
position, not the release. The sketch tool sees a right press only once the release has shown it
was a click: the press itself goes to the camera, which may pan or orbit with that button, and the
canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a
chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation
does not transition the state machine.
position, not the release. An armed sketch tool that already consumed the right
button (to terminate a chain, say) declines to also open a menu, through a read-and-clear flag.
Past either budget the event is navigation, and navigation does not transition the state
machine.
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
held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool
handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept
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.
for each button. The left button is shared with picking, so a whole body is swept with a
rectangle on plain left-drag only while no camera action is assigned to it, 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
`wxGLCanvas` is a native window and does not reliably stack over GL), and `N` to look normal to
the plane. The printer bed stays: there is no sketch grid, so the plate grid is the only ground
reference a sketch has. Code that changes either 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.
`wxGLCanvas` is a native window and does not reliably stack over GL), the printer bed muted so
a plate grid is never read as a sketch grid, and `N` to look normal to the plane. Code that
changes any of the three belongs with a change to this section.
## 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
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
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
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
empty rubber band all let go of it, whichever list or pick made it — except while a body Move is
open, which holds the selection until it ends (see the interaction contract).
empty rubber band all let go of it, whichever list or pick made it.
## 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
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,
Show/hide and Delete on a feature, Move, Show/hide and Delete on a body, and only Show/hide on
the Origin and Bed rows, a separate non-selectable list above the features — and the eye shows
Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows
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
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.
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
+62 -2
View File
@@ -52,8 +52,6 @@ src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.h
src/slic3r/GUI/DeviceTab/uiDeviceUpdateVersion.cpp
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleRack.h
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.cpp
src/slic3r/GUI/DeviceTab/wgtDeviceNozzleSelect.h
@@ -149,6 +147,7 @@ src/slic3r/GUI/HMSPanel.cpp
src/slic3r/GUI/MainFrame.cpp
src/slic3r/GUI/MediaPlayCtrl.cpp
src/slic3r/GUI/MediaFilePanel.cpp
src/slic3r/GUI/OrcaFilesPanel.cpp
src/slic3r/GUI/ImageGrid.cpp
src/slic3r/GUI/Widgets/ImageSwitchButton.cpp
src/slic3r/GUI/Printer/PrinterFileSystem.cpp
@@ -301,3 +300,64 @@ src/slic3r/GUI/Gizmos/GLGizmoPrimitive.cpp
src/slic3r/GUI/Gizmos/GLGizmoSketch.cpp
src/slic3r/GUI/KeyChord.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">
<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>
<?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>

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">
<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>
<?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>

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">
<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>
<?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>

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)"/>
<defs>
<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 offset="1" stop-color="#00AE42" stop-opacity="0"/>
<stop stop-color="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient>
</defs>
</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)"/>
<defs>
<linearGradient id="paint0_linear_46765_16184" x1="0.622561" y1="9.52006" x2="0" y2="0.52972" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/>
<stop stop-color="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient>
</defs>
</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)"/>
<defs>
<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 offset="1" stop-color="#00AE42" stop-opacity="0"/>
<stop stop-color="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient>
</defs>
</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)"/>
<defs>
<linearGradient id="paint0_linear_46765_16184" x1="5.37744" y1="0.47994" x2="6" y2="9.47028" gradientUnits="userSpaceOnUse">
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/>
<stop stop-color="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient>
</defs>
</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)"/>
<defs>
<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 offset="1" stop-color="#00AE42" stop-opacity="0"/>
<stop stop-color="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
</linearGradient>
</defs>
</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">
<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>
<?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>

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) {
return (query || "").trim() ?
T("sd_result_count", "Showing %s actions", shown) :
T("sd_result_count_all", "%s actions", total);
T("sd_result_count", "Matching actions: %s", shown) :
T("sd_result_count_all", "Actions: %s", total);
}
// 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;
if (fid && FAVS.indexOf(fid) !== -1) FAVS.splice(FAVS.indexOf(fid), 1);
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) {
pin.classList.toggle("on", on);
pin.innerHTML = pinSvg(on);
pin.title = on ? T("sd_unpin_fav", "Unpin from favourites (%s)", shortcutCtrl() + "B") :
T("sd_pin_fav", "Pin to favourites (%s)", shortcutCtrl() + "B");
pin.title = on ? T("sd_unpin_fav", "Unpin from favorites (%s)", shortcutCtrl() + "B") :
T("sd_pin_fav", "Pin to favorites (%s)", shortcutCtrl() + "B");
}
// ---- render ------------------------------------------------------------------
@@ -858,15 +858,15 @@ function renderFav() {
badge.textContent = slot;
// Slot "0" is the 10th favourite (Alt/Option+0).
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);
}
// Direct removal: a hover-revealed ✕ in the tile's corner. click() stops propagation so it
// unpins without activating the action.
var unpin = document.createElement("button");
unpin.className = "fav-unpin";
unpin.title = T("sd_remove_fav", "Remove from favourites");
unpin.setAttribute("aria-label", 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 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.onclick = function (ev) { ev.stopPropagation(); toggleFav(id); };
tile.appendChild(unpin);
@@ -1170,7 +1170,7 @@ function renderTabList() {
listEl.className = "dial-list";
if (countEl) {
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)); });
}
+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
# a gate.
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
# 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
@@ -481,14 +480,10 @@ def type_into_open_field(value, mark):
def enter_sketch(timeout=180):
"""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,
with nothing picked, brings them up to choose from, so the plane is clicked AFTER it. That
click opens the session on the plane and pops the tool offer, and a click outside a popup
menu only closes it, so the spot is clicked twice, apart: the second click closes the offer,
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.
THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. Shift+S enters sketch MODE and pops the plane
offer; the offer must be dismissed; and the plane itself is chosen by clicking it in the
viewport BEFORE Shift+S. check-gui-sketching.py has always done all four steps. This ladder
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
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
time.sleep(2.0)
dismiss_modals()
key("shift+s", 1.0) # nothing picked: the reference planes come up
click(*PLANE_PX, pause=0.8) # picks the plane IN THE VIEWPORT: session open, offer up...
click(*PLANE_PX) # ...and this closes the offer
key("p", 0.6) # arms Point in the session
click(*PLANE_PX) # pick the plane IN THE VIEWPORT — before Shift+S
key("shift+s", 1.0)
key("Escape", 0.5) # entering sketch mode pops the offer; dismiss it
key("p", 0.6) # any sketch tool starts the session on that plane
if try_call("sketch_describe") is not None:
# 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
@@ -516,15 +511,14 @@ def enter_sketch(timeout=180):
# reported all eleven checks failed with "nothing opened" — the tools were arming
# into an empty Feature-mode document.
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})")
# 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.
# Where the plane label sits in the viewport once Shift+S has brought the planes up. Same
# constant the gesture ladder uses; it is a label on the 3D view, not a widget, so it moves only
# if the camera does.
# Where the plane label sits in the viewport before a sketch is open. Same constant the gesture
# ladder uses; it is a label on the 3D view, not a widget, so it moves only if the camera does.
PLANE_PX = (913, 359)
# 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('{"'):
continue
# id, name, row, key, action, refusal, accepts, need_bodies, need_sketches, need_sheet,
# sketch_mode, family, ... — split on top-level commas, respecting quotes.
f, cur, q, esc = [], "", False, False
# sketch_mode, family, ... — split on top-level commas, respecting quotes and the
# L(...) / L_CONTEXT(...) translation markers.
f, cur, q, esc, depth = [], "", False, False, 0
for ch in line[1:]:
if esc:
cur += ch; esc = False; continue
@@ -87,16 +88,18 @@ def load_table():
cur += ch; esc = True; continue
if ch == '"':
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
if ch == "}" and not q:
if ch == "}" and not q and depth == 0:
break
cur += ch
f.append(cur.strip())
if len(f) < 12:
continue
# User-facing fields are wrapped in the L("...") gettext marker; strip it.
lit = lambda s: None if s == "nullptr" else re.sub(r'^L\((.*)\)$', r'\1', s).strip('"')
# User-facing fields are wrapped in L("...") or L_CONTEXT("...", ...); take the literal.
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]),
"action": lit(f[4]), "accepts": int(f[6].rstrip("u"), 0),
"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)
# The ribbon's document group lost its Bed checkbox and label when the bed toggle moved to the
# 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.
# Feature-tree rows, measured on the rig at 1920x1080: first row centre, then 23 px apart.
# 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.
#
@@ -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
# 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.
TREE_GAP = 12 + 1
TREE_ROW0 = (300, 215 + 2 * 23 + TREE_GAP + CHROME_DY)
TREE_ROW0 = (300, 215 + CHROME_DY)
def go_design():
@@ -280,21 +266,18 @@ def reset_document():
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.
leave_sketch()'s Escapes let go of every pick, so Shift+S finds nothing to sketch on and only
brings the reference planes up; the click on one opens the session on it and pops the tool
offer. A click outside a popup menu only closes it, so the same spot is clicked again, apart:
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.
Shift+S enters sketch MODE and pops the offer; Escape dismisses it; the tool letter then
starts the session on the plane the click selected. All four steps are real input — nothing
here goes through the socket.
"""
leave_sketch()
click(*plane_px)
key("shift+s", 0.8)
click(*plane_px, pause=0.8) # picks the plane: the session opens and the offer pops...
click(*plane_px) # ...and this closes the offer
key("Escape", 0.4) # entering sketch mode pops the offer; dismiss it
key("p", 0.6)
if try_call("sketch_describe") is None:
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
key(tool_key, 0.6)
@@ -754,7 +737,7 @@ def corner_pair(ents):
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):
@@ -883,7 +866,7 @@ def rung_extend():
# 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".
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",
"equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric",
"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
# 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.
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",
"equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric",
"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) + ")"
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):
"""Refuse an atlas the header cannot represent, naming every fault at once.
@@ -90,6 +95,9 @@ def main():
"#ifndef L",
"#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp",
"#endif",
"#ifndef L_CONTEXT",
"#define L_CONTEXT(s, context) s",
"#endif",
"",
"namespace Slic3r { namespace GUI {",
"",
@@ -113,7 +121,7 @@ def main():
"// nullptr -> kernel support exists, no GUI path yet (row shows disabled)",
"struct OfferVerb {",
" const char* id;",
" const char* name; // drawing-office word (L10); 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",
" const char* key; // shortcut shown in the row, or nullptr",
" const char* action;",
@@ -170,7 +178,7 @@ def main():
n = v.get("needs") or {}
lines.append(
" {%s, %s, %d, %s, %s, %s, 0x%08xu, %d, %d, %s, %s, %s, %s, %s}," % (
cstr(v["id"]), 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")),
mask, n.get("bodies", 0), n.get("sketches", 0),
"true" if n.get("sheet") else "false",
+2 -2
View File
@@ -229,11 +229,11 @@
"none"
],
"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,
"action": "key:S+S",
"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",
+15 -6
View File
@@ -36,6 +36,9 @@ EXCLUDED_DIRS = ("src/glad/", "tests/catch2/")
# Per file. A deleted include can leave hundreds of follow-on errors.
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+))? @@")
DIAGNOSTIC_RE = re.compile(r"^(.+?):(\d+):(\d+): (error|warning): (.*)$")
FIX_MESSAGE_RE = re.compile(r"^\s+Message:\s+(['\"])(.*)\1$")
@@ -72,7 +75,8 @@ def parse_diff(diff):
change = None
for line in diff.splitlines():
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
continue
if change is None:
@@ -98,8 +102,10 @@ def is_checked(path):
def changed_files(merge_base):
# 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],
check=True, capture_output=True, text=True).stdout
# core.quotePath=false keeps a non-ASCII path unquoted, so parse_diff sees its b/ prefix.
diff = subprocess.run(["git", "-c", "core.quotePath=false", "diff", "-U0", "--no-color", "--no-ext-diff",
"--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)}
@@ -117,8 +123,9 @@ def run_clang_tidy(clang_tidy, build_dir, path, lines, extra_args):
cmd = [clang_tidy, "-p", build_dir, "--quiet", "--export-fixes=" + fixes,
"--extra-arg=-Wno-unknown-warning-option", "--extra-arg=-ferror-limit=0", *extra_args, path]
if lines is not None:
cmd.insert(1, "--line-filter=" + json.dumps([{"name": path, "lines": lines}]))
result = subprocess.run(cmd, capture_output=True, text=True)
# clang-tidy matches the name against the end of the file's native path.
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)
output = result.stdout + result.stderr
return result.returncode, output, parse_diagnostics(output, suggestions)
@@ -178,7 +185,7 @@ def error_sites(errors, text):
def errors_alone_at(revision, clang_tidy, build_dir, path):
"""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:
return Counter()
# Beside the original, so its quoted includes resolve the same way.
@@ -234,6 +241,8 @@ def main():
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")
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,
capture_output=True, text=True).stdout.strip()
+31 -7
View File
@@ -54,6 +54,21 @@ function Has([string]$Command) {
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) {
Write-Host "Missing: $What"
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 = $null
if (Has "py") {
& py -3 --version *> $null
Invoke-Quiet { & py -3 --version }
if ($LASTEXITCODE -eq 0) { $Python = @("py", "-3") }
}
if (-not $Python -and (Has "python")) {
& python --version *> $null
Invoke-Quiet { & python --version }
if ($LASTEXITCODE -eq 0) { $Python = @("python") }
}
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.
$HostArch = if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { "arm64" } else { "x64" }
$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) {
$i = $line.IndexOf("=")
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"
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
$Log = Join-Path $BuildDir "configure.log"
& cmake @cmakeArgs *> $Log
Invoke-Quiet { & cmake @cmakeArgs } $Log
if ($LASTEXITCODE -ne 0) {
Get-Content $Log -Tail 20
Get-Content -LiteralPath $Log -Tail 20
throw "Configuring failed; the full log is in $Log."
}
& cmake --build $BuildDir --target git_commit_hash_header *> $null
if ($LASTEXITCODE -ne 0) { throw "Generating git_commit_hash.h failed." }
$HashLog = Join-Path $BuildDir "git_commit_hash.log"
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 ------------------------------------------------------------
+39
View File
@@ -5,10 +5,13 @@ external deps).
Run from the repo root: python -m unittest discover -s scripts/tests -v
"""
import json
import os
import subprocess
import sys
import tempfile
import unittest
from unittest import mock
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):
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):
def test_matches_however_the_include_was_spelled(self):
@@ -128,5 +135,37 @@ class TestParseSuggestedIncludes(unittest.TestCase):
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])
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")
if __name__ == "__main__":
unittest.main()
+2
View File
@@ -153,6 +153,8 @@ using namespace nlohmann;
#include "slic3r/GUI/GUI_ObjectList.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Jobs/SendJob.hpp"
#include <boost/nowide/convert.hpp>
#include <stdio.h>
namespace fs = boost::filesystem;
+1
View File
@@ -35,6 +35,7 @@ extern "C"
#include <boost/algorithm/string/classification.hpp>
#include <stdio.h>
#include <boost/algorithm/string/constants.hpp>
#ifdef SLIC3R_GUI
class OpenGLVersionCheck
+8
View File
@@ -11,6 +11,14 @@
#include "git_commit_hash.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::atomic<int> g_crash_log_count = 0;
+2
View File
@@ -3,6 +3,8 @@
#include <boost/nowide/fstream.hpp>
#include "StackWalker.h"
#include <eh.h>
#include <cstddef>
#include <string>
class CBaseException : public CStackWalker
{
+3
View File
@@ -2,6 +2,9 @@
#include <strsafe.h>
//#include <atlconv.h>
#include <dbghelp.h>
#include <cstdarg>
#include <cstddef>
#include <cstring>
#pragma comment(lib, "version.lib")
#pragma comment( lib, "dbghelp.lib" )
+1
View File
@@ -2,6 +2,7 @@
#include <Windows.h>
#include <tchar.h>
#include <vector>
#include <cstddef>
namespace textconv_helper
{
+7
View File
@@ -126,6 +126,13 @@
</array>
<key>LSMinimumSystemVersion</key>
<string>10.10</string>
<key>NSLocalNetworkUsageDescription</key>
<string>OrcaSlicer uses your local network to discover and connect to printers.</string>
<key>NSBonjourServices</key>
<array>
<string>_octoprint._tcp</string>
<string>_http._tcp</string>
</array>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
+3 -4
View File
@@ -42,6 +42,9 @@
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <cassert>
#include <iterator>
#include <string_view>
#ifdef WIN32
//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).
if (get("design_connector_face_glyph").empty())
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
//#ifdef SUPPORT_SHOW_HINTS
@@ -3,6 +3,9 @@
#include <cstdio>
#include <boost/filesystem/path.hpp>
#include <boost/nowide/convert.hpp>
#include <algorithm>
#include <string>
#include <vector>
#ifdef WIN32
#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/Point.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/I18N.hpp"
#include <BRepMesh_IncrementalMesh.hxx>
#include <BRep_Tool.hxx>
@@ -73,19 +74,19 @@ std::vector<TopoDS_Shape> GeometryEngine::read_step_solids(const std::string& pa
try {
STEPControl_Reader reader;
if (reader.ReadFile(path.c_str()) != IFSelect_RetDone) {
err = "cannot read STEP file";
err = _u8L("cannot read STEP file");
return out;
}
reader.TransferRoots();
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.
for (TopExp_Explorer ex(shape, TopAbs_SOLID); ex.More(); ex.Next())
out.push_back(ex.Current());
if (out.empty())
out.push_back(shape);
} 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();
}
return out;
@@ -115,7 +116,7 @@ TopoDS_Shape GeometryEngine::mesh_to_brep(const indexed_triangle_set& its,
if (tolerance <= 0.0)
throw std::runtime_error("mesh_to_brep: tolerance must be > 0");
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
// 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());
if (tris.empty())
throw std::runtime_error("mesh_to_brep: every triangle was rejected as degenerate "
"(try a smaller tolerance)");
throw std::runtime_error(_u8L("mesh_to_brep: every triangle was rejected as degenerate "
"(try a smaller tolerance)"));
// 3. One face per triangle, sharing vertices and edges through the caches.
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
// operation degenerate; OCCT leaves IsDone() false. Report it instead of
// 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();
}
@@ -377,7 +378,7 @@ TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double dis
chamfer.Add(distance, edge); // symmetric chamfer
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();
}
@@ -404,7 +405,7 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
}
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();
}
@@ -421,7 +422,7 @@ TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double dis
}
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();
}
@@ -547,12 +548,13 @@ GeometryEngine::MassProps GeometryEngine::mass_properties(const TopoDS_Shape& sh
std::string GeometryEngine::primitive_name(PrimitiveType type)
{
switch (type) {
case PrimitiveType::Box: return "Box";
case PrimitiveType::Cylinder: return "Cylinder";
case PrimitiveType::Sphere: return "Sphere";
case PrimitiveType::Cone: return "Cone";
case PrimitiveType::Torus: return "Torus";
default: return "Unknown";
// TRN Default name of an object created from the box primitive shape.
case PrimitiveType::Box: return _u8L("Box");
case PrimitiveType::Cylinder: return _u8L("Cylinder");
case PrimitiveType::Sphere: return _u8L("Sphere");
case PrimitiveType::Cone: return _u8L("Cone");
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/Point.hpp"
#include "libslic3r/TriangleMesh.hpp"
#include "libslic3r/I18N.hpp"
#include <Standard_Handle.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
{
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;
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();
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();
}
@@ -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)
{
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);
if (!prism.IsDone()) throw std::runtime_error("extrude failed");
if (!prism.IsDone()) throw std::runtime_error(_u8L("extrude failed"));
return prism.Shape();
}
@@ -169,7 +170,7 @@ static TopoDS_Shape extrude_face_internal(const TopoDS_Face& face, const gp_Dir&
if (symmetric) {
gp_Vec halfVec = gp_Vec(dir) * (length / 2.0);
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 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)
{
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);
}
@@ -194,7 +195,7 @@ TopoDS_Shape SketchEngine::make_extrude_two_sided(const TopoDS_Wire& wire, const
double up, double down)
{
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);
}
@@ -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 (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));
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();
}
@@ -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());
auto straight = [&]() -> TopoDS_Shape {
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);
};
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)
{
// 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 /
// 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
}
@@ -356,7 +357,7 @@ TopoDS_Shape SketchEngine::make_revolve(const TopoDS_Wire& wire, const gp_Ax1& a
{
BRepBuilderAPI_MakeFace faceMaker(wire);
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();
// 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; }
BRepPrimAPI_MakeRevol rev(face, axis, angle_rad);
if (!rev.IsDone())
throw std::runtime_error("Failed to revolve");
throw std::runtime_error(_u8L("Failed to revolve"));
if (!BRepCheck_Analyzer(rev.Shape()).IsValid())
throw std::runtime_error("the profile crosses the revolve axis — it must lie on one side of it");
return rev.Shape();
@@ -397,29 +398,29 @@ TopoDS_Shape SketchEngine::make_sweep(const TopoDS_Wire& profile, const TopoDS_W
{
BRepBuilderAPI_MakeFace faceMaker(profile);
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();
BRepOffsetAPI_MakePipe pipe(path, face);
pipe.Build();
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();
}
TopoDS_Shape SketchEngine::make_loft(const std::vector<TopoDS_Wire>& profiles, bool ruled)
{
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);
for (const TopoDS_Wire& w : profiles) {
if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
loft.AddWire(w);
}
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();
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;
}
@@ -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)
{
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);
for (const TopoDS_Wire& w : profiles) {
if (w.IsNull()) throw std::runtime_error("loft: null profile wire");
loft.AddWire(w);
}
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();
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;
}
@@ -444,11 +445,11 @@ TopoDS_Shape SketchEngine::make_pocket(const TopoDS_Wire& wire, const SketchPlan
const TopoDS_Shape& target, double depth)
{
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(),
gp_Dir(plane.normal.x(), plane.normal.y(), plane.normal.z()), depth + 1.0, false);
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();
}
@@ -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,
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
// 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) {
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());
}
@@ -956,7 +957,7 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
areas.reserve(wires.size());
for (const TopoDS_Wire& w : wires) {
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());
GProp_GProps props;
BRepGProp::SurfaceProperties(faces.back(), props);
@@ -982,10 +983,10 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
got = true;
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);
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.
// 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
@@ -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.
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
// already uses for imported glyphs, which is why holed TEXT extruded correctly all along
// 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/Line.hpp"
#include "libslic3r/BoundingBox.hpp"
#include <sstream>
// Experimentaly suggested ration of font ascent by multiple fonts
// to get approx center of normal text line
@@ -28,6 +28,7 @@
#include "FuzzySkin.hpp"
#include "libnoise/noise.h"
#include <functional>
// #define DEBUG_FUZZY
+8
View File
@@ -301,6 +301,9 @@ struct SurfaceFillParams
float skin_infill_depth = 0;
bool symmetric_infill_y_axis = false;
// Top fill for 3D honeycomb
bool infill_complete_top = false;
// Params for Lateral honeycomb
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_2);
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(skin_infill_depth);
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;
} else if (params.pattern == ipZigZag) {
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()) {
@@ -1430,6 +1436,8 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
} else if (surface_fill.params.pattern == ipZigZag) {
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)
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
// 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
// 3. Transition from upper "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
*/
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 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){
res.push_back(gridSize * (0. + 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);
}
// Generate an array of points that are in the same direction as the
// basic printing line (i.e. Y points for columns, X points for rows)
// Note: a negative offset only causes a change in the perpendicular
// direction
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)
// Add additional dense fill in line with the pattern direction to
// cover the top squares of the pattern
static Polylines addTops(coordf_t Zpos, coordf_t gridSize, coordf_t lengthX, coordf_t lengthY, coordf_t spacing,
size_t multiline_count, size_t topDistance)
{
std::vector<coordf_t> points;
points.push_back(baseLocation);
for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= (gridSize*2)) {
for(size_t pi = 0; pi < critPoints.size(); pi++){
points.push_back(baseLocation + cLoc + critPoints[pi]);
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
coordf_t zHalfCycle = fmod(zCycle, 0.5) * 2.;
bool printVert = zCycle < 0.5;
coordf_t offsetX = multiline_count;
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 points;
return lines;
}
// Generate an array of points for the dimension that is perpendicular to
// the basic printing line (i.e. X points for columns, Y points for rows)
static std::vector<coordf_t> perpendPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
size_t baseLocation, size_t gridLength,
size_t offsetBase, coordf_t perpDir)
// Generate a set of polylines that complete octahedron curves on the
// extremities of a pattern
static Polylines makeEndPoints(const coordf_t Zpos, coordf_t gridSize, std::vector<coordf_t> critPoints,
coordf_t lengthX, coordf_t lengthY, coordf_t spacing, size_t multiline_count)
{
std::vector<coordf_t> points;
points.push_back(offsetBase);
for (coordf_t cLoc = baseLocation; cLoc < gridLength; cLoc+= gridSize*2) {
for(size_t pi = 0; pi < critPoints.size(); pi++){
Polylines lines;
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
bool printVert = zCycle < 0.5;
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);
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);
return points;
}
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;
line.points.push_back((print_dir == 1) ? Point(dAdj, gridMax) : Point(gridMax, dAdj));
return line;
}
// Generate a set of curves (array of array of 2d points) that describe a
// 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);
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
bool printVert = zCycle < 0.5;
if (printVert) {
int perpDir = -1;
for (coordf_t x = 0; x <= (boundsX); x+= gridSize, perpDir *= -1) {
points.push_back(Pointfs());
Pointfs &newPoints = points.back();
newPoints = zip(
perpendPoints(Zpos, gridSize, critPoints, 0, boundsY, x, perpDir),
colinearPoints(Zpos, gridSize, critPoints, 0, boundsY));
BoundingBox extents;
int perpDir = -1;
int perpDirPattern = -1;
coordf_t gridMax = ceil(((printVert ? lengthX : lengthY) - EPSILON) / gridSize) * gridSize;
for (coordf_t pPos = 0; pPos < gridMax; pPos += gridSize, perpDirPattern *= -1) {
for (size_t li = 0; li < multiline_count; li++){
coordf_t oAdj = (li - ((multiline_count - 1) / 2.)) * spacing; // orthogonal line adjustment
Polyline newPoints;
newPoints = patternPoints(Zpos, gridSize, critPoints,
printVert ? lengthY : lengthX,
perpDirPattern, printVert ? 1 : -1, oAdj);
if (perpDir == 1)
std::reverse(newPoints.begin(), newPoints.end());
}
} else {
int perpDir = 1;
for (coordf_t y = gridSize; y <= (boundsY); y+= gridSize, 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());
std::reverse(newPoints.points.begin(), newPoints.points.end());
newPoints.translate(printVert ? Point(pPos + oAdj, 0.) : Point(0., pPos + oAdj));
extents.merge(newPoints.points);
lines.push_back(newPoints);
perpDir *= -1;
}
}
return points;
return lines;
}
// Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron with a specified
// grid square size.
// 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;
result.reserve(polylines.size());
for (std::vector<Pointfs>::const_iterator it_polylines = polylines.begin();
it_polylines != polylines.end(); ++ it_polylines) {
result.push_back(Polyline());
Polyline &polyline = result.back();
for (Pointfs::const_iterator it = it_polylines->begin(); it != it_polylines->end(); ++ it)
polyline.points.push_back(Point(coord_t((*it)(0)), coord_t((*it)(1))));
Polylines polyZag = makeZigZag(z, gridSize, lengthX, lengthY, spacing, multiline_count);
result.insert(result.end(), polyZag.begin(), polyZag.end());
// add end connectors
std::vector<coordf_t> critPoints = getCriticalPoints(z, gridSize);
Polylines endPoints = makeEndPoints(z, gridSize, critPoints, lengthX, lengthY, spacing, multiline_count);
result.insert(result.end(), endPoints.begin(), endPoints.end());
// 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;
}
// FillParams has the following useful information:
// density <0 .. 1> [proportion of space to fill]
// anchor_length [???]
// anchor_length_max [???]
// dont_connect() [avoid connect lines]
// dont_adjust [avoid filling space evenly]
// monotonic [fill strictly left to right]
// 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(
const FillParams &params,
@@ -208,78 +340,62 @@ void Fill3DHoneycomb::_fill_surface_single(
ExPolygon expolygon,
Polylines &polylines_out)
{
// no rotation is supported for this infill pattern
// Support infill angle
auto infill_angle = float(this->angle);
if (std::abs(infill_angle) >= EPSILON) expolygon.rotate(-infill_angle);
BoundingBox bb = expolygon.contour.bounding_box();
// Expand the bounding box to avoid artifacts at the edges
coord_t expand = 5 * (scale_(this->spacing));
bb.offset(expand);
// Increase the bounding box outwards to avoid edge clipping artefacts
coord_t expandSize = 5. * scale_(this->spacing);
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
// truncated octahedron; so Z is pre-adjusted first by scaling by sqrt(2)
coordf_t zScale = sqrt(2);
// adjustment to account for the additional distance of octagram curves
// note: this only strictly applies for a rectangular area where the total
// Z travel distance is a multiple of the spacing... but it should
// be at least better than the prevous estimate which assumed straight
// lines
// Density adjustment to account for the additional distance of
// octagram curves. [This only strictly applies for a rectangular
// area where the total Z travel distance is a multiple of the
// spacing]
// = 4 * integrate(func=4*x(sqrt(2) - 1) + 1, from=0, to=0.25)
// = (sqrt(2) + 1) / 2 [... I think]
// make a first guess at the preferred grid Size
coordf_t gridSize = (scale_(this->spacing) * ((zScale + 1.) / 2.) * params.multiline / params.density);
// This density calculation is incorrect for many values > 25%, possibly
// 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)
// 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 layerHeight = scale_(params.layer_height);
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;
// re-adjust the grid size for a partial octahedral path
// (scale of 1.1 guessed based on modeling)
gridSize = (scale_(this->spacing) * 1.1 * params.multiline / params.density);
// re-adjust zScale to make layering consistent
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
// (a module is 2*$gridSize since one $gridSize half-module is
// growing while the other $gridSize half-module is shrinking)
bb.merge(align_to_grid(bb.min, Point(gridSize*4, gridSize*4)));
// align bounding box to a multiple of the octahedron grid so that
// layers with different starting points have matching origins
bb.merge(align_to_grid(bb.min, Point(gridSize * 2., gridSize * 2.)));
// Z adjustment to start at the widest point for the lowest layer
coordf_t startOffset = gridSize / 2. + scale_(params.layer_height / 2.);
// generate pattern
Polylines polylines =
makeGrid(
scale_(this->z) * zScale,
gridSize,
bb.size()(0),
bb.size()(1),
!params.dont_adjust);
scale_(this->z) * zScale + startOffset,
scale_(this->z - (params.layer_height * params.multiline * layersPerSlice)) * zScale + startOffset,
gridSize, bb.size()(0), bb.size()(1),
params.infill_complete_top,
scale_(this->spacing),
params.multiline,
layersPerSlice);
// move pattern in place
for (Polyline &pl : polylines){
@@ -290,8 +406,8 @@ void Fill3DHoneycomb::_fill_surface_single(
smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution));
}
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
// Note: multiline fill adjustment is carried out in this code,
// rather than using the multiline_fill function
// clip pattern to boundaries, chain the clipped polylines
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
bool symmetric_infill_y_axis{false};
bool infill_complete_top{false};
coord_t symmetric_y_axis{0};
bool locked_zag{false};
float infill_lock_depth{0.0};
+1
View File
@@ -119,6 +119,7 @@
#endif
#include <Shiny/Shiny.h>
#include <stdio.h>
using namespace std::literals::string_view_literals;
+1
View File
@@ -12,6 +12,7 @@
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/nowide/fstream.hpp>
#include <ios>
#ifdef _WIN32
#include <boost/interprocess/sync/file_lock.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
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 enable_arc_fitting = print_config.enable_arc_fitting;
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
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 enable_arc_fitting = print_config.enable_arc_fitting;
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
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 enable_arc_fitting = print_config.enable_arc_fitting;
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)
{
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 enable_arc_fitting = print_config.enable_arc_fitting;
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)
{
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 enable_arc_fitting = print_config.enable_arc_fitting;
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)
{
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 enable_arc_fitting = print_config.enable_arc_fitting;
const auto scaled_resolution = scaled<double>(print_config.resolution.value);
+1
View File
@@ -12,6 +12,7 @@
#include <system_error>
#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.
#if defined(_LIBCPP_VERSION)
+1
View File
@@ -1209,6 +1209,7 @@ static std::vector<std::string> s_Preset_print_options{
"infill_lock_depth",
"skin_infill_depth",
"skin_infill_density",
"infill_complete_top",
"align_infill_direction_to_model",
"extra_solid_infills",
"center_of_surface_pattern",
+111 -36
View File
@@ -3813,7 +3813,11 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
auto ams_name = ams.opt_string("tray_name", 0u);
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
auto filament_multi_color = ams.opt<ConfigOptionStrings>("filament_multi_colour")->values;
if (filament_id.empty()) {
auto filament_type = ams.opt_string("filament_type", 0u);
auto is_placeholder = ams.has("filament_slot_placeholder") && ams.opt_bool("filament_slot_placeholder", 0u);
// A printer-set tray carries a material type but no preset id; resolve it to the
// matching Generic preset so it is not dropped and the combo array keeps aligning.
if (filament_id.empty() && (is_placeholder || filament_type.empty())) {
continue;
}
if (!filament_changed && this->filament_presets.size() > combox_info.ams_filament_presets.size()) {
@@ -3823,16 +3827,48 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
combox_info.ams_names.push_back(ams_name);
continue;
}
auto iter = std::find_if(filaments.begin(), filaments.end(),
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
auto iter = filaments.end();
if (!filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(),
[this, &filament_id](auto &f) { return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
}
if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
auto filament_type = ams.opt_string("filament_type", 0u);
if (!filament_type.empty()) {
auto original_type = filament_type;
filament_type = "Generic " + filament_type;
iter = std::find_if(filaments.begin(), filaments.end(),
[&filament_type](auto &f) { return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, filament_type); });
if (iter == filaments.end()) {
// Similarity fallback: find a generic preset whose filament_type
// appears as a whole word in the AMS type (e.g. "ASA" in "ASA Sparkle").
auto upper_type = boost::to_upper_copy(original_type);
auto contains_word = [](const std::string& haystack, const std::string& needle) {
auto pos = haystack.find(needle);
while (pos != std::string::npos) {
bool start_ok = (pos == 0 || !std::isalnum(static_cast<unsigned char>(haystack[pos - 1])));
bool end_ok = (pos + needle.size() >= haystack.size() ||
!std::isalnum(static_cast<unsigned char>(haystack[pos + needle.size()])));
if (start_ok && end_ok)
return true;
pos = haystack.find(needle, pos + 1);
}
return false;
};
// Find the longest-matching preset type to prefer e.g. "PA-CF" over "PA".
size_t best_len = 0;
for (auto it = filaments.begin(); it != filaments.end(); ++it) {
if (!it->is_compatible || !it->is_system || !boost::algorithm::starts_with(it->name, "Generic "))
continue;
auto preset_type = boost::to_upper_copy(it->config.opt_string("filament_type", 0u));
if (preset_type.size() > best_len && contains_word(upper_type, preset_type)) {
iter = it;
best_len = preset_type.size();
filament_type = "Generic " + it->config.opt_string("filament_type", 0u);
}
}
}
}
if (iter == filaments.end()) {
// Prefer old selection
@@ -3843,7 +3879,11 @@ void PresetBundle::get_ams_cobox_infos(AMSComboInfo& combox_info)
combox_info.ams_names.push_back(ams_name);
continue;
}
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { return f.is_compatible && f.is_system; });
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) {
return f.is_compatible && f.is_system && boost::algorithm::starts_with(f.name, "Generic ");
});
if (iter == filaments.end())
iter = std::find_if(filaments.begin(), filaments.end(), [](auto &f) { return f.is_compatible && f.is_system; });
if (iter == filaments.end())
continue;
}
@@ -3881,6 +3921,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
for (auto &entry : filament_ams_list) {
auto & ams = entry.second;
auto filament_id = ams.opt_string("filament_id", 0u);
const bool printer_set_tray = filament_id.empty();
auto filament_color = ams.opt_string("filament_colour", 0u);
auto filament_color_type = ams.opt_string("filament_colour_type", 0u);
auto filament_changed = !ams.has("filament_changed") || ams.opt_bool("filament_changed");
@@ -3893,42 +3934,58 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_array_maps.push_back(temp);
index++;
if (filament_id.empty()) {
if (use_map) {
for (int j = maps.size() - 1; j >= 0; j--) {
if (maps[j].slot_id == slot_id && maps[j].ams_id == ams_id) {
maps.erase(j);
// A tray written by the printer UI carries a material type but no
// OrcaSlicer preset id. Resolve it to the matching Generic preset with
// the tray's own color instead of dropping it (direct sync) or forcing
// "Generic PLA" (mapping sync). Placeholders and typeless trays keep
// the previous behavior.
const auto tray_type = ams.opt_string("filament_type", 0u);
if (is_placeholder || tray_type.empty()) {
if (use_map) {
for (int j = maps.size() - 1; j >= 0; j--) {
if (maps[j].slot_id == slot_id && maps[j].ams_id == ams_id) {
maps.erase(j);
}
}
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
auto default_unknown_color = "#CECECE";
ams_filament_colors.push_back(default_unknown_color);
ams_filament_color_types.push_back("1");
if (filament_multi_color.size() == 0) {
filament_multi_color.push_back(default_unknown_color);
}
ams_multi_color_filment.push_back(filament_multi_color);
} else if (is_placeholder) {
// Orca: push placeholders to keep index alignment with ams_infos
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
}
ams_filament_presets.push_back("Generic PLA");//for unknow matieral
auto default_unknown_color = "#CECECE";
ams_filament_colors.push_back(default_unknown_color);
ams_filament_color_types.push_back("1");
if (filament_multi_color.size() == 0) {
filament_multi_color.push_back(default_unknown_color);
}
ams_multi_color_filment.push_back(filament_multi_color);
} else if (is_placeholder) {
// Orca: push placeholders to keep index alignment with ams_infos
ams_filament_presets.push_back("");
ams_filament_colors.push_back("");
ams_filament_color_types.push_back("");
ams_multi_color_filment.push_back({});
continue;
}
continue;
}
if (!filament_changed && this->filament_presets.size() > ams_filament_presets.size()) {
ams_filament_presets.push_back(this->filament_presets[ams_filament_presets.size()]);
ams_filament_colors.push_back(filament_color);
ams_filament_color_types.push_back(filament_color_type);
ams_multi_color_filment.push_back(filament_multi_color);
ams_infos.back().valid = true;
continue;
}
bool has_type = false;
auto filament_type = ams.opt_string("filament_type", 0u);
auto iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
auto iter = filaments.end();
if (!filament_id.empty()) {
iter = std::find_if(filaments.begin(), filaments.end(), [this, &filament_id, &has_type, filament_type](auto &f) {
has_type |= f.config.opt_string("filament_type", 0u) == filament_type;
return f.is_compatible && filaments.get_preset_base(f) == &f && f.filament_id == filament_id; });
warn_ambiguous_filament_id_match(filaments, iter, filament_id);
} else {
// The material type is the only identity a printer-set tray carries.
has_type = std::any_of(filaments.begin(), filaments.end(), [&filament_type](auto &f) {
return f.is_compatible && f.config.opt_string("filament_type", 0u) == filament_type; });
}
if (iter == filaments.end()) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": filament_id %1% not found or system or compatible") % filament_id;
if (!filament_type.empty()) {
@@ -3968,6 +4025,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
}
}
}
const bool resolved_by_material_type = printer_set_tray && iter != filaments.end();
if (iter == filaments.end()) {
// Prefer old selection
if (ams_filament_presets.size() < this->filament_presets.size()) {
@@ -3975,6 +4033,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_filament_colors.push_back(filament_color);
ams_filament_color_types.push_back(filament_color_type);
ams_multi_color_filment.push_back(filament_multi_color);
ams_infos.back().valid = true;
unknowns.emplace_back(&ams, has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") :
L("The filament model is unknown. Still using the previous filament preset."));
continue;
@@ -3990,7 +4049,8 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
if (iter == filaments.end())
continue;
}
unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ?
if (!resolved_by_material_type)
unknowns.emplace_back(&ams, boost::algorithm::starts_with(iter->name, filament_type) ?
(has_type ? L("The filament may not be compatible with the current machine settings. Generic filament presets will be used.") :
L("The filament model is unknown. Generic filament presets will be used.")) :
(has_type ? L("The filament may not be compatible with the current machine settings. A random filament preset will be used.") :
@@ -4001,6 +4061,7 @@ unsigned int PresetBundle::sync_ams_list(std::vector<std::pair<DynamicPrintConfi
ams_filament_colors.push_back(filament_color);
ams_filament_color_types.push_back(filament_color_type);
ams_multi_color_filment.push_back(filament_multi_color);
ams_infos.back().valid = true;
}
if (ams_filament_presets.empty())
return 0;
@@ -4440,9 +4501,21 @@ std::vector<std::vector<DynamicPrintConfig>> PresetBundle::get_extruder_filament
return filament_infos;
}
// ORCA TODO: currently, this function assumes the printer name follows the pattern of "<printer_model> <nozzle_diameter>", e.g.
// printer_type: "Bambu Lab X2D", nozzle_diameter_str: "0.4 nozzle" => printer_name: "Bambu Lab X2D 0.4 nozzle". If the printer name does
// not follow this pattern, the function may not work correctly.
std::string PresetBundle::get_printer_model_display_name(const std::string &model_id) const
{
if (model_id.empty())
return {};
for (const auto &vendor_entry : vendors) {
for (const auto &model : vendor_entry.second.models) {
if (model.model_id == model_id)
return model.name;
}
}
return {};
}
// ORCA TODO: this assumes printer names follow "<printer_model> <nozzle_diameter>", e.g.
// "Bambu Lab X2D 0.4 nozzle". Other naming schemes may not resolve correctly.
std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only)
{
std::set<std::string> printer_names;
@@ -4464,7 +4537,9 @@ std::set<std::string> PresetBundle::get_printer_names_by_printer_type_and_nozzle
if (printer_it->name.find(nozzle_diameter_str) != std::string::npos) printer_names.insert(printer_it->name);
}
assert(printer_names.size() == 1);
// No match is normal for a connected machine the user has not installed; only an
// ambiguous match is a bug (the caller assumes one preset per model and nozzle).
assert(printer_names.size() <= 1);
for (auto& printer_name : printer_names) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " " << __LINE__ << " printer name: " << printer_name;
@@ -4477,8 +4552,8 @@ std::vector<Preset *> PresetBundle::get_filament_presets_for_machine(const std::
const std::string &nozzle_diameter_str,
bool include_user_presets)
{
// Printer model plus nozzle diameter is expected to resolve to a single system printer preset;
// get_printer_names_by_printer_type_and_nozzle asserts as much in debug builds.
// Printer model plus nozzle diameter normally resolves to a single system printer preset.
// Zero matches is normal for a connected machine the user never installed.
const std::set<std::string> printer_names = get_printer_names_by_printer_type_and_nozzle(printer_type, nozzle_diameter_str);
const Preset *printer = printer_names.empty() ? nullptr : printers.find_preset(*printer_names.begin());
if (printer == nullptr)
+3
View File
@@ -411,6 +411,9 @@ public:
std::vector<std::vector<DynamicPrintConfig>> get_extruder_filament_info() const;
// Resolve a vendor model id (an agent-reported machine identity) to the display name
// printer profiles use as printer_model. Empty when no loaded vendor declares it.
std::string get_printer_model_display_name(const std::string &model_id) const;
std::set<std::string> get_printer_names_by_printer_type_and_nozzle(const std::string &printer_type, std::string nozzle_diameter_str, bool system_only = true);
// Orca: the root filament presets a connected machine can use, resolved with the rule the rest
// of the app applies (is_compatible_with_printer): an empty compatible_printers means every
+13 -6
View File
@@ -4574,6 +4574,13 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
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
def = this->add("infill_combination_max_layer_height", coFloatOrPercent);
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 = 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->sidetext = L("Hz"); // Hertz, CIS languages need translation
def->min = 0;
@@ -5310,7 +5317,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0));
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->sidetext = L("Hz"); // Hertz, CIS languages need translation
def->min = 0;
@@ -5319,7 +5326,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0));
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->min = 0;
def->max = 1;
@@ -5327,7 +5334,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0.1));
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->min = 0;
def->max = 1;
@@ -8530,14 +8537,14 @@ void PrintConfigDef::init_sla_params()
def = this->add("display_pixels_x", coInt);
//def->full_label = L("");
def->label = ("X");
def->label = L_CONTEXT("X", "Axis");
//def->tooltip = L("");
def->min = 100;
def->set_default_value(new ConfigOptionInt(2560));
def = this->add("display_pixels_y", coInt);
//def->full_label = L("");
def->label = ("Y");
def->label = L_CONTEXT("Y", "Axis");
//def->tooltip = L("");
def->min = 100;
def->set_default_value(new ConfigOptionInt(1440));
+1
View File
@@ -1358,6 +1358,7 @@ PRINT_CONFIG_CLASS_DEFINE(
((ConfigOptionFloat, bottom_layer_direction))
((ConfigOptionString, solid_infill_rotate_template))
((ConfigOptionBool, symmetric_infill_y_axis))
((ConfigOptionBool, infill_complete_top))
((ConfigOptionFloat, infill_shift_step))
((ConfigOptionString, sparse_infill_rotate_template))
((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 ||
region_config.sparse_infill_pattern == ipOctagramSpiral;
const Flow &flow = candidate.region->bridging_flow(frSolidInfill, true);
Polygons area_to_be_bridge = expand(candidate.new_polys, flow.scaled_spacing());
area_to_be_bridge = intersection(area_to_be_bridge, deep_infill_area);
area_to_be_bridge.erase(std::remove_if(area_to_be_bridge.begin(), area_to_be_bridge.end(),
[internal_unsupported_area](const Polygon &p) {
return intersection({p}, internal_unsupported_area).empty();
ExPolygons bridge_components = intersection_ex(expand(candidate.new_polys, flow.scaled_spacing()), 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(),
[&internal_unsupported_area](const ExPolygon &component) {
return intersection_ex(component, 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);
+2 -2
View File
@@ -1944,7 +1944,7 @@ Polygons TreeSupport::get_trim_support_regions(
static const double no_overlap_xy_gap = 0.2f;
double gap_xy_scaled = scale_(gap_xy);
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);
@@ -1991,7 +1991,7 @@ Polygons TreeSupport::get_trim_support_regions(
const Layer& object_layer = *object.layers()[i];
bool some_region_overlaps = false;
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)
break;
some_region_overlaps = true;
+1
View File
@@ -22,6 +22,7 @@
#include <tbb/task_arena.h>
#include "Thread.hpp"
#include <cstring>
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.
// 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 <string.h>
namespace boost::posix_time { class ptime; }
#if ! defined(TBB_VERSION_MAJOR)
+14 -1
View File
@@ -368,6 +368,10 @@ set(SLIC3R_GUI_SOURCES
GUI/MarkdownTip.hpp
GUI/MediaFilePanel.cpp
GUI/MediaFilePanel.h
GUI/OrcaFilesPanel.cpp
GUI/OrcaFilesPanel.h
GUI/StoragePanel.cpp
GUI/StoragePanel.h
GUI/MediaPlayCtrl.cpp
GUI/MediaPlayCtrl.h
GUI/WebRtcMediaController.cpp
@@ -457,6 +461,11 @@ set(SLIC3R_GUI_SOURCES
GUI/PresetHints.hpp
GUI/PrinterCloudAuthDialog.cpp
GUI/PrinterCloudAuthDialog.hpp
GUI/Printer/BambuFileGridModel.cpp
GUI/Printer/BambuFileGridModel.h
GUI/Printer/IFileGridModel.h
GUI/Printer/RemoteFileGridModel.cpp
GUI/Printer/RemoteFileGridModel.h
GUI/Printer/PrinterFileSystem.cpp
GUI/Printer/PrinterFileSystem.h
GUI/PrinterWebView.cpp
@@ -772,8 +781,12 @@ set(SLIC3R_GUI_SOURCES
Utils/ICameraSignalingChannel.hpp
Utils/OrcaCloudServiceAgent.cpp
Utils/OrcaCloudServiceAgent.hpp
Utils/OrcaMqttConnection.cpp
Utils/OrcaMqttConnection.hpp
Utils/OrcaPrinterAgent.cpp
Utils/OrcaPrinterAgent.hpp
Utils/OrcaCloudSignalingChannel.cpp
Utils/OrcaCloudSignalingChannel.hpp
Utils/QidiPrinterAgent.cpp
Utils/QidiPrinterAgent.hpp
Utils/SnapmakerPrinterAgent.cpp
@@ -917,7 +930,7 @@ target_include_directories(libslic3r_gui PRIVATE Utils ${CMAKE_CURRENT_BINARY_DI
if (WIN32)
target_include_directories(libslic3r_gui SYSTEM PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../deps/WebView2/include)
target_link_libraries(libslic3r_gui Advapi32)
target_link_libraries(libslic3r_gui Advapi32 Crypt32)
endif()
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SLIC3R_GUI_SOURCES})
-7
View File
@@ -310,7 +310,6 @@ GLVolume::GLVolume(float r, float g, float b, float a)
, force_native_color(false)
, force_neutral_color(false)
, force_sinking_contours(false)
, depth_bias(false)
, picking(false)
, tverts_range(0, size_t(-1))
{
@@ -1333,17 +1332,11 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type,
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();
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
if (volume.first->selected && shader_can_outline && GUI::wxGetApp().show_outline())
volume.first->render_with_outline(cnv_size);
else
volume.first->render();
if (volume.first->depth_bias)
glsafe(::glDisable(GL_POLYGON_OFFSET_FILL));
#if ENABLE_ENVIRONMENT_MAP
if (use_environment_texture)
-4
View File
@@ -232,10 +232,6 @@ public:
bool force_neutral_color : 1;
// Whether or not to force rendering of sinking contours
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
bool picking : 1;
// slice error
+13 -4
View File
@@ -7,6 +7,7 @@
#include "slic3r/GUI/DeviceCore/DevExtruderSystem.h"
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceManager.hpp"
#include "slic3r/GUI/MsgDialog.hpp"
@@ -1228,11 +1229,11 @@ void AMSDryCtrWin::update_normal_description(DevAms* dev_ams)
for (const auto& lim : ams_limits) {
if (dev_ams->GetAmsType() == lim.type) {
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";
can_enable_button = false;
} 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";
can_enable_button = false;
}
@@ -1260,7 +1261,7 @@ void AMSDryCtrWin::update_normal_description(DevAms* dev_ams)
auto limit_temperature = preset.value().filament_dev_ams_drying_heat_distortion_temperature;
if (temp_val > limit_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;
}
}
@@ -1633,8 +1634,16 @@ int AMSDryCtrWin::update_filament_list(DevAms* dev_ams, MachineObject* obj)
stream << std::fixed << std::setprecision(1) << obj->GetExtderSystem()->GetNozzleDiameter(extruder_id);
std::string nozzle_diameter_str = stream.str();
// The connected device's model may not resolve (OrcaSonar's is optional); the
// helper falls back to the selected profile so the list is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (filament_printer_model.empty()) {
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " cannot resolve a printer model for the filament list";
return false;
}
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second)
continue;
const std::string filament_alias = filaments.get_preset_alias(*filament_it, true);
+98 -43
View File
@@ -4,10 +4,12 @@
#include "GUI_App.hpp"
#include "GUI.hpp"
#include "slic3r/Utils/NetworkAgent.hpp"
#include "slic3r/Utils/NetworkAgentFactory.hpp"
#include "libslic3r/Preset.hpp"
#include "I18N.hpp"
#include <algorithm>
#include <boost/log/trivial.hpp>
#include <slic3r/GUI/DeviceManager.hpp>
#include <string>
#include <sstream>
#include <ios>
@@ -472,6 +474,13 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
m_button_confirm->Show(true);
}
// A third-party tray owns its temp range; BBL RFID trays keep the read-only preset values.
// Only the OrcaSonar agent accepts the temp fields, so other agents stay read-only.
const bool is_orca_agent = obj && obj->printer_agent_id == ORCA_PRINTER_AGENT_ID;
const bool can_edit = !is_printing || obj->is_support_filament_setting_inprinting;
m_input_nozzle_min->Enable(m_is_third && can_edit && is_orca_agent);
m_input_nozzle_max->Enable(m_is_third && can_edit && is_orca_agent);
if (!m_is_third) {
m_tip_readonly->SetLabelText(wxEmptyString);
m_tip_readonly->Hide();
@@ -494,6 +503,8 @@ void AMSMaterialsSetting::update_filament_editing(bool is_printing)
if (m_view_only) { // Orca: view-only (2D laser/cut) — lock every edit control and hide apply/reset
m_comboBox_filament->Enable(false);
m_comboBox_cali_result->Enable(false);
m_input_nozzle_min->Enable(false);
m_input_nozzle_max->Enable(false);
m_input_k_val->Enable(false);
m_input_n_val->Enable(false);
m_button_confirm->Hide();
@@ -543,7 +554,7 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
}
// set k / n value
if (obj->cali_version <= -1 && obj->get_printer_series() == PrinterSeries::SERIES_P1P) {
if (obj->supports_extrusion_cali() && obj->cali_version <= -1 && obj->get_printer_series() == PrinterSeries::SERIES_P1P) {
// set extrusion cali ratio
int cali_tray_id = ams_id * 4 + slot_id;
@@ -564,7 +575,7 @@ void AMSMaterialsSetting::on_select_reset(wxCommandEvent& event) {
}
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
}
else {
else if (obj->supports_extrusion_cali()) {
PACalibIndexInfo select_index_info;
int tray_id = ams_id * 4 + slot_id;
if (is_virtual_tray()) {
@@ -723,6 +734,21 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
return;
}
if (nozzle_temp_min_int < FILAMENT_MIN_TEMP || nozzle_temp_min_int > FILAMENT_MAX_TEMP ||
nozzle_temp_max_int < FILAMENT_MIN_TEMP || nozzle_temp_max_int > FILAMENT_MAX_TEMP) {
MessageDialog msg_dlg(nullptr,
wxString::Format(_L("The input value should be greater than %1% and less than %2%"), FILAMENT_MIN_TEMP, FILAMENT_MAX_TEMP),
wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
if (nozzle_temp_min_int > nozzle_temp_max_int) {
MessageDialog msg_dlg(nullptr, _L("The minimum temperature cannot be greater than the maximum temperature."),
wxEmptyString, wxICON_WARNING | wxOK);
msg_dlg.ShowModal();
return;
}
// Orca: log the tray payload this dialog hands the printer, so the filament_id resolved from the
// dropdown selection can be checked against the tray_info_idx the AMS actually receives. A
@@ -746,7 +772,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
wxString k_text = m_input_k_val->GetTextCtrl()->GetValue();
wxString n_text = m_input_n_val->GetTextCtrl()->GetValue();
if (obj->cali_version <= -1 && (obj->get_printer_series() != PrinterSeries::SERIES_X1) && !ExtrusionCalibration::check_k_validation(k_text)) {
if (obj->supports_extrusion_cali() && obj->cali_version <= -1 && (obj->get_printer_series() != PrinterSeries::SERIES_X1) && !ExtrusionCalibration::check_k_validation(k_text)) {
wxString k_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE);
wxString kn_tips = wxString::Format(_L("Please input a valid value (K in %.1f~%.1f, N in %.1f~%.1f)"), MIN_PA_K_VALUE, MAX_PA_K_VALUE, 0.6, 2.0);
MessageDialog msg_dlg(nullptr, k_tips, wxEmptyString, wxICON_WARNING | wxOK);
@@ -776,7 +802,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
vt_tray = VIRTUAL_TRAY_DEPUTY_ID;
}
if (obj->cali_version >= 0) {
if (obj->supports_extrusion_cali() && obj->cali_version >= 0) {
PACalibIndexInfo select_index_info;
select_index_info.tray_id = vt_tray;
select_index_info.ams_id = ams_id;
@@ -795,7 +821,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
CalibUtils::select_PA_calib_result(select_index_info);
}
else {
else if (obj->supports_extrusion_cali()) {
obj->command_extrusion_cali_set(vt_tray, "", "", k, n);
}
}
@@ -817,7 +843,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
;
}
if (obj->cali_version >= 0) {
if (obj->supports_extrusion_cali() && obj->cali_version >= 0) {
PACalibIndexInfo select_index_info;
select_index_info.tray_id = cali_tray_id;
select_index_info.ams_id = ams_id;
@@ -836,7 +862,7 @@ void AMSMaterialsSetting::on_select_ok(wxCommandEvent &event)
CalibUtils::select_PA_calib_result(select_index_info);
}
else {
else if (obj->supports_extrusion_cali()) {
obj->command_extrusion_cali_set(cali_tray_id, "", "", k, n);
}
}
@@ -932,7 +958,12 @@ bool AMSMaterialsSetting::is_virtual_tray()
void AMSMaterialsSetting::update_widgets()
{
if (obj && obj->get_printer_series() == PrinterSeries::SERIES_X1 && obj->cali_version <= -1) {
if (obj && !obj->supports_extrusion_cali()) {
// No printer-side K/N records: do not offer them.
m_panel_normal->Show();
m_panel_kn->Hide();
}
else if (obj && obj->get_printer_series() == PrinterSeries::SERIES_X1 && obj->cali_version <= -1) {
// Low version firmware does not display k value
m_panel_kn->Hide();
}
@@ -993,6 +1024,9 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_input_k_val->GetTextCtrl()->SetValue(k);
m_input_n_val->GetTextCtrl()->SetValue(n);
// Tray values are re-supplied by every caller; clear so a previous popup cannot leak in.
m_input_nozzle_min->GetTextCtrl()->SetValue(wxEmptyString);
m_input_nozzle_max->GetTextCtrl()->SetValue(wxEmptyString);
wxArrayString filament_items;
wxString bambu_filament_name;
@@ -1016,11 +1050,14 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
}
stream << std::fixed << std::setprecision(1) << machine_diameter;
std::string nozzle_diameter_str = stream.str();
// OrcaSonar's model id is optional, so the device model alone can resolve to nothing;
// the helper falls back to the selected profile so the dropdown is never empty.
const std::string filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
if (preset_bundle) {
if (preset_bundle && !filament_printer_model.empty()) {
BOOST_LOG_TRIVIAL(trace) << "system_preset_bundle filament number=" << preset_bundle->filaments.size();
for (Preset *filament_it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (!filament_id_set.insert(filament_it->filament_id).second)
continue;
const std::string alias = preset_bundle->filaments.get_preset_alias(*filament_it, true);
@@ -1087,6 +1124,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
else {
m_comboBox_filament->Show();
m_readonly_filament->Hide();
// A printer-set tray carries its own temps; the preset selection only fills
// them when the tray has none.
if (!temp_min.IsEmpty() && temp_min != "0")
m_input_nozzle_min->GetTextCtrl()->SetValue(temp_min);
if (!temp_max.IsEmpty() && temp_max != "0")
m_input_nozzle_max->GetTextCtrl()->SetValue(temp_max);
}
if (obj->cali_version >= 0) {
@@ -1223,7 +1266,12 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
}
}
if (filament_items.IsEmpty())
BOOST_LOG_TRIVIAL(warning) << "ams_materials_setting: no filament presets for printer model \""
<< filament_printer_model << "\" at nozzle " << nozzle_diameter_str;
m_comboBox_filament->Set(filament_items);
m_comboBox_from_printer = true;
m_comboBox_filament->SetSelection(selection_idx);
post_select_event(selection_idx);
@@ -1231,9 +1279,6 @@ void AMSMaterialsSetting::Popup(wxString filament, wxString sn, wxString temp_mi
m_comboBox_filament->SetValue(wxEmptyString);
}
// Set the flag whether to open the filament setting dialog from the device page
m_comboBox_filament->SetClientData(new int(1));
update();
Layout();
Fit();
@@ -1277,50 +1322,53 @@ int AMSMaterialsSetting::get_filament_variant_index(const Preset &filament, cons
void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
{
// Get the flag whether to open the filament setting dialog from the device page
int* from_printer = static_cast<int*>(m_comboBox_filament->GetClientData());
// True for the popup's own initial selection: the dialog pre-filled the tray's
// temps, so the matching preset must not overwrite them.
const bool initial_printer_selection = m_comboBox_from_printer;
m_filament_type = "";
PresetBundle* preset_bundle = wxGetApp().preset_bundle;
std::string filament_printer_model;
if (preset_bundle) {
std::ostringstream stream;
if (obj) {
// Defensive: this dialog is opened only from StatusPanel (BBL-only) today, so the fallback fires
// only during the brief BBL startup window before firmware reports nozzle info. Without this,
// the "0.0" lookup string returns an empty set and filament lookup yields no results.
// Use the selected profile's nozzle diameter until the connected device reports one.
float machine_diameter = obj->GetExtderSystem()->GetNozzleDiameter(0);
if (machine_diameter == 0.0f) {
const ConfigOption *opt = preset_bundle->printers.get_selected_preset().config.option("nozzle_diameter");
if (opt) machine_diameter = static_cast<const ConfigOptionFloats *>(opt)->values[0];
}
stream << std::fixed << std::setprecision(1) << machine_diameter;
filament_printer_model = resolve_filament_printer_model(obj->printer_type, preset_bundle);
}
std::string nozzle_diameter_str = stream.str();
// Resolve the selection against the same list Popup() built the dropdown from, so the two
// halves of the dialog cannot disagree about which filaments this machine can use.
const std::string selected = m_comboBox_filament->GetValue().ToStdString();
if (!selected.empty()) {
if (!selected.empty() && !filament_printer_model.empty()) {
const std::string filament_id = map_filament_items[selected].filament_id;
for (Preset *it : preset_bundle->get_filament_presets_for_machine(
DevPrinterConfigUtil::get_printer_display_name(obj->printer_type), nozzle_diameter_str, obj->is_support_user_preset)) {
filament_printer_model, nozzle_diameter_str, obj->is_support_user_preset)) {
if (it->filament_id != filament_id)
continue;
// ) if nozzle_temperature_range is found
const int variant_index = get_filament_variant_index(*it, nozzle_diameter_str);
ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
if (opt_min) {
ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_min);
if (opt_min_ints) {
wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(variant_index));
m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min);
if (!initial_printer_selection) {
// ) if nozzle_temperature_range is found
const int variant_index = get_filament_variant_index(*it, nozzle_diameter_str);
ConfigOption* opt_min = it->config.option("nozzle_temperature_range_low");
if (opt_min) {
ConfigOptionInts* opt_min_ints = dynamic_cast<ConfigOptionInts*>(opt_min);
if (opt_min_ints) {
wxString text_nozzle_temp_min = wxString::Format("%d", opt_min_ints->get_at(variant_index));
m_input_nozzle_min->GetTextCtrl()->SetValue(text_nozzle_temp_min);
}
}
}
ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high");
if (opt_max) {
ConfigOptionInts* opt_max_ints = dynamic_cast<ConfigOptionInts*>(opt_max);
if (opt_max_ints) {
wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(variant_index));
m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max);
ConfigOption* opt_max = it->config.option("nozzle_temperature_range_high");
if (opt_max) {
ConfigOptionInts* opt_max_ints = dynamic_cast<ConfigOptionInts*>(opt_max);
if (opt_max_ints) {
wxString text_nozzle_temp_max = wxString::Format("%d", opt_max_ints->get_at(variant_index));
m_input_nozzle_max->GetTextCtrl()->SetValue(text_nozzle_temp_max);
}
}
}
ConfigOption* opt_type = it->config.option("filament_type");
@@ -1359,13 +1407,16 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
m_button_confirm->Disable(); // ORCA No need to change style
m_comboBox_cali_result->Clear();
m_comboBox_cali_result->SetValue(wxEmptyString);
m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString);
m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString);
m_comboBox_filament->SetClientData(new int(0));
if (!initial_printer_selection) {
m_input_k_val->GetTextCtrl()->SetValue(wxEmptyString);
m_input_n_val->GetTextCtrl()->SetValue(wxEmptyString);
}
m_comboBox_from_printer = false;
return;
}
else {
m_button_confirm->Enable(true); // ORCA No need to change style
if (!m_view_only)
m_button_confirm->Enable(true); // ORCA No need to change style
}
//filament id
@@ -1389,6 +1440,9 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
}
}
if (!ams_filament_id.empty())
m_clr_picker->is_empty(false);
wxArrayString items;
m_pa_profile_items.clear();
m_comboBox_cali_result->SetValue(wxEmptyString);
@@ -1444,7 +1498,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
m_comboBox_cali_result->Set(items);
if (ams_id == VIRTUAL_TRAY_MAIN_ID || ams_id == VIRTUAL_TRAY_DEPUTY_ID) {
if (from_printer && (*from_printer == 1)) {
if (initial_printer_selection) {
for (auto slot : obj->vt_slot) {
if (slot.id == std::to_string(ams_id))
cali_select_idx = CalibUtils::get_selected_calib_idx(m_pa_profile_items, slot.cali_idx);
@@ -1461,10 +1515,11 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
}
}
else {
if (from_printer && (*from_printer == 1)) {
if (initial_printer_selection) {
DevAmsTray* selected_tray = this->obj->GetFilaSystem()->GetAmsTray(std::to_string(ams_id), std::to_string(slot_id));
if (!selected_tray)
{
m_comboBox_from_printer = false;
return;
}
@@ -1495,7 +1550,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
else {
if (!ams_filament_id.empty()) {
//m_input_k_val->GetTextCtrl()->SetValue("0.00");
m_input_k_val->Enable(true);
m_input_k_val->Enable(!m_view_only);
}
else {
//m_input_k_val->GetTextCtrl()->SetValue("0.00");
@@ -1503,7 +1558,7 @@ void AMSMaterialsSetting::on_select_filament(wxCommandEvent &evt)
}
}
m_comboBox_filament->SetClientData(new int(0));
m_comboBox_from_printer = false;
}
void AMSMaterialsSetting::on_dpi_changed(const wxRect &suggested_rect)
+4 -1
View File
@@ -138,7 +138,7 @@ public:
std::string ams_filament_id;
std::string ams_setting_id;
bool m_is_third;
bool m_is_third = false;
// Orca: view-only mode (laser/cut). When set, the dialog is inspectable but every
// editing control is disabled and no command is sent.
bool m_view_only = false;
@@ -200,6 +200,9 @@ protected:
wxStaticText* m_n_param;
TextInput* m_input_n_val;
int m_filament_selection;
// True while the popup's initial, printer-driven selection is being handled; it
// keeps that selection from overwriting the tray's own temps with preset temps.
bool m_comboBox_from_printer = false;
int m_pa_cali_select_id = 0;
+7 -4
View File
@@ -530,7 +530,8 @@ void AMSSetting::on_insert_material_read(wxCommandEvent &event)
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
UpdateByObj(m_obj);
m_sizer_Insert_material_tip_inline->Layout();
Layout();
@@ -556,7 +557,8 @@ void AMSSetting::on_starting_read(wxCommandEvent &event)
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
UpdateByObj(m_obj);
m_sizer_starting_tip_inline->Layout();
Layout();
@@ -570,7 +572,8 @@ void AMSSetting::on_remain(wxCommandEvent& event)
bool start_read_opt = m_checkbox_starting_auto_read->GetValue();
bool tray_read_opt = m_checkbox_Insert_material_auto_read->GetValue();
bool remain_opt = m_checkbox_remain->GetValue();
m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt);
if (m_obj->command_ams_user_settings(start_read_opt, tray_read_opt, remain_opt) != 0)
UpdateByObj(m_obj);
event.Skip();
}
@@ -806,4 +809,4 @@ void AMSSettingArrangeAMSOrder::OnBtnRearrangeClicked(wxCommandEvent& event)
}
#endif
}} // namespace Slic3r::GUI
}} // namespace Slic3r::GUI
+3 -2
View File
@@ -264,7 +264,8 @@ AboutDialog::AboutDialog()
vesizer->Add(0, 0, 1, wxEXPAND, FromDIP(5));
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* 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);
wxFont version_font = GetFont();
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));
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));
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) {
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);
ams_mapping_item_container->SetName(m_right_marea_panel->GetName());
ams_mapping_item_container->SetSizer(sizer_mapping_list);
@@ -506,7 +507,7 @@ void AmsMapingPopup::update_ams_tips(MachineObject* obj)
}
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", "");
}
@@ -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).
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)) {
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 {
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);
if (!can_pick_the_item) {
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);
if (!can_pick_the_item) {
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) {
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
+3
View File
@@ -65,6 +65,8 @@
#include "Widgets/Label.hpp"
#include "slic3r/GUI/I18N.hpp"
#include "slic3r/GUI/Plater.hpp"
#include <wx/dcgraph.h>
#include <wx/dcmemory.h>
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_input_description = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
wxSize(FromDIP(450), FromDIP(300)), wxTE_MULTILINE | wxTE_PROCESS_ENTER);
m_input_description->SetBackgroundColour(*wxWHITE);
m_input_description->SetFont(::Label::Body_14);
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)
: DPIDialog(parent, wxID_ANY, _L("Auxiliaryies"), wxDefaultPosition,
: DPIDialog(parent, wxID_ANY, _L("Auxiliaries"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
m_aux_list = new AuxiliaryList(this);
@@ -60,6 +60,9 @@
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#ifdef _WIN32
#include <excpt.h>
#endif
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/GUI_App.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/GLToolbar.hpp"
#include "slic3r/GUI/Event.hpp"
@@ -92,7 +91,7 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
m_canvas->enable_plate_chrome(false);
m_canvas->enable_labels(false);
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_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) {
@@ -191,11 +190,8 @@ DesignCanvas::DesignCanvas(wxWindow* parent)
refresh_bed();
// 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
// Design bed, which stays at the printer bed's home whichever plate is current.
// is the view the user is looking at right now.
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
// 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);
}
} else if (obj_idx == 1) {
// The ghost is the whole resulting model, normally drawn as a faint blue overlay on
// the visible bodies. Every face the feature leaves alone is in both, at the same
// depth, so the ghost is drawn with a depth bias: the bodies win on those faces instead
// 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.
// The ghost is normally a faint blue overlay on the visible body. 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->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; }
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();
rebuild_bodies();
reload(!m_first_frame);
@@ -424,8 +415,6 @@ void DesignCanvas::clear_mesh()
{
m_body_meshes = nullptr;
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()) {
m_model.delete_object((size_t)0);
reload(true);
@@ -481,6 +470,13 @@ void DesignCanvas::begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode
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,
const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane)
@@ -554,7 +550,6 @@ void DesignCanvas::refresh_bed()
double printable_height = 100.0;
const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height");
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
}
@@ -736,6 +731,8 @@ void DesignCanvas::clear_move_gizmo()
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)
{
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);
}
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)
{
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;
m_ctx_bound = true;
// Bound AFTER GLCanvas3D's own handlers, so this runs first and can consume the event.
// It only consumes when it actually opens the offer.
// Right-drag may pan or orbit (Preferences > Control). Without remembering where the press
// landed, every such drag ended by popping the offer over wherever the camera stopped — the
// menu appearing as the reward for moving the view. A right-click is the release of a
// STATIONARY press (kCadRightClickDriftPx); only that reaches the sketch tool or the offer.
// It only consumes when it actually opens the offer; every other right-click still falls
// through to the polyline-chain end and the move gizmo, which were there first.
// Right-drag pans. Without remembering where the press landed, every pan ended by popping
// the offer over wherever the camera stopped — the menu appearing as the reward for moving
// the view. The offer is the release of a STATIONARY right-click (kCadRightClickDriftPx).
m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
m_ctx_press = e.GetPosition();
m_ctx_travelled = false;
// Drop any press the tool still keeps (only a click's release takes it, so a pan's stays)
// 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
e.Skip(); // the canvas still needs the press to seed the orbit
});
m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) {
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) {
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;
// Ending a chain or abandoning an anchor uses the click up.
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) {
if (m_on_context_menu && !terminated && !inline_busy() && is_click) {
// 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
// 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)
}
// 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)
{
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();
};
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.
const float left = m_canvas->get_canvas_toolbar_right() + margin;
const float left = kStatusHudLeftInset * em;
const ImVec4 col = m_status_hud_colour.IsOk()
? ImVec4(m_status_hud_colour.Red() / 255.f, m_status_hud_colour.Green() / 255.f,
m_status_hud_colour.Blue() / 255.f, 1.f)
+4 -7
View File
@@ -3,7 +3,6 @@
#include <vector>
#include "libslic3r/Point.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/Color.hpp"
#include <utility>
#include <wx/colour.h>
@@ -49,11 +48,10 @@ public:
// 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
// by address (a stable panel member) and read again whenever the selection changes. The
// bodies' edge lines are refreshed with them (DesignSketchTool::refresh_body_edges).
// by address (a stable panel member) and read again whenever the selection changes.
void set_bodies(const std::vector<TriangleMesh>* body_meshes,
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 clear_preview();
@@ -68,6 +66,7 @@ public:
const std::vector<SketchEntityConstraintDef>& constraints,
const SketchPlane& plane);
void set_sketch_tool(DesignSketchTool::Mode mode);
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when a reference plane is clicked in 3D
void set_sketch_construction(bool c);
// Flip the sketch selection between construction and real geometry; returns the
// number of entities changed (0 = nothing selected, caller falls back to the mode).
@@ -84,8 +83,6 @@ public:
void finish_sketch();
bool is_sketching() const;
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
// 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
@@ -160,6 +157,7 @@ public:
void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform,
double body_radius);
void clear_move_gizmo();
bool moving_body() const;
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
// 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
void clear_base_pick();
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_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
+96 -93
View File
@@ -13,6 +13,9 @@
#ifndef L
#define L(s) s // gettext marker, as in slic3r/GUI/I18N.hpp
#endif
#ifndef L_CONTEXT
#define L_CONTEXT(s, context) s
#endif
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)
struct OfferVerb {
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
const char* key; // shortcut shown in the row, or nullptr
const char* action;
@@ -99,107 +102,107 @@ static const bool kOfferRowFlat[] = {
};
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")},
{"extrude", L("Extrude"), 1, "Shift+E", "key:S+E", L("Create a sketch, or pick a solid face, first"), 0x00004002u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch profile, or push/pull a picked face")},
{"revolve", L("Revolve"), 1, "Shift+R", "key:S+R", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a profile about an axis")},
{"sweep", L("Sweep"), 1, "Shift+W", "key:S+W", L("Create a profile sketch to sweep first"), 0x00004000u, 0, 2, false, false, nullptr, "design_sweep", L("Sweep a profile along a path")},
{"loft", L("Loft"), 1, "Shift+L", "key:S+L", L("Create at least two profile sketches to loft"), 0x00004000u, 0, 2, false, false, nullptr, "design_loft", L("Loft (skin) between two or more profiles")},
{"thicken", L("Thicken"), 1, nullptr, "fly:material#4", L("Thicken needs a solid body — add or import one first"), 0x0000000au, 1, 0, false, false, nullptr, "design_thicken", L("Offset a solid face into a thin plate (new body)")},
{"rib", L("Rib"), 1, "R", "fly:material#5", L("Rib needs a solid body — add or import one first"), 0x00010000u, 1, 0, false, false, nullptr, "design_rib", L("Grow a thin wall from an open sketch line, fused to a body")},
{"boolean", L("Join"), 1, "Shift+B", "btn:bool#0", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Fuse the tool body into the target — one solid, no seam")},
{"bool_subtract", L("Subtract"), 1, nullptr, "btn:bool#1", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Cut the tool body out of the target")},
{"bool_intersect", L("Intersect"), 1, nullptr, "btn:bool#2", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Keep only where the two bodies overlap")},
{"surf_extrude", L("Surface Extrude"), 1, "Shift+G", "key:S+G", L("Create a sketch first"), 0x00004000u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch into a sheet body (no end caps)")},
{"surf_revolve", L("Surface Revolve"), 1, nullptr, "fly:surface#1", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a sketch profile into a sheet body")},
{"surf_loft", L("Surface Loft"), 1, nullptr, "fly:surface#2", L("Create at least two profile sketches to loft"), 0x00004000u, 0, 2, false, false, nullptr, "design_loft", L("Loft (skin) between 2+ profiles, open (no end caps)")},
{"surf_fill", L("Surface Fill"), 1, nullptr, "fly:surface#3", L("Create a closed sketch first"), 0x00004000u, 0, 0, false, false, nullptr, "design_surface", L("Fill a sketch boundary with a smooth face")},
{"thicken_surf", L("Thicken Surface"), 1, nullptr, "fly:surface#5", L("Thicken Surface needs a surface body — make one with a Surface tool first"), 0x00000100u, 0, 0, true, false, nullptr, "design_thicken", L("Thicken a sheet body into a solid")},
{"hole", L("Hole"), 2, "Shift+H", "key:S+H", L("Pick a face or a plane to drill into"), 0x00000402u, 1, 0, false, false, nullptr, "design_hole", L("Drill a hole, centerd on a picked face or placed on a plane")},
{"thread", L("Thread"), 2, "Shift+T", "key:S+T", L("Pick a cylindrical surface (bore / outer) or a circular edge for a thread"), 0x00000024u, 1, 0, false, false, nullptr, "design_thread", L("Thread a cylindrical surface (inner bore / outer) or a circular edge")},
{"shell", L("Shell"), 2, "Shift+K", "key:S+K", L("Shell needs a solid body — add or import one first"), 0x00000082u, 1, 0, false, false, nullptr, "design_shell", L("Hollow the body to a wall thickness, opening a picked face")},
{"cut", L("Cut"), 2, "Shift+X", "key:S+X", L("Cut needs a solid body — add or import one first"), 0x000004feu, 1, 0, false, false, nullptr, "design_cut", L("Trim the body with a plane — drag the offset arrow; keep one half or both")},
{"split", L("Split"), 2, nullptr, nullptr, L("Split needs a solid body — add or import one first"), 0x000000feu, 1, 0, false, false, nullptr, nullptr, L("Split the body along a picked face into two solids")},
{"fillet", L("Fillet"), 3, "Shift+F", "btn:dress#0", L("Pick an edge to round"), 0x000000b2u, 1, 0, false, false, nullptr, "design_filletedge", L("Pick an edge, then drag the radius arrow or type it")},
{"chamfer", L("Chamfer"), 3, nullptr, "btn:dress#1", L("Pick an edge to bevel"), 0x000000b2u, 1, 0, false, false, nullptr, "design_chamfer", L("Pick an edge, then drag the distance arrow or type it")},
{"draft", L("Draft"), 3, "Shift+D", "key:S+D", L("Pick a face to taper"), 0x0000000au, 1, 0, false, false, nullptr, "design_draft", L("Tilt a picked face by a draft angle")},
{"surf_offset", L("Surface Offset"), 3, nullptr, "fly:surface#4", L("Surface Offset needs a surface body — make one with a Surface tool first"), 0x00000100u, 0, 0, true, false, nullptr, "design_offset", L("Offset a sheet body's shell by a signed distance")},
{"pattern", L("Linear pattern"), 4, "Shift+N", "btn:pat#0", L("Pattern needs a solid body — add or import one first"), 0x00006082u, 1, 0, false, false, nullptr, "design_array", L("Repeat the body along a direction — drag the spacing, set the count")},
{"pattern_circular", L("Circular pattern"), 4, nullptr, "btn:pat#1", L("Pattern needs a solid body — add or import one first"), 0x00006082u, 1, 0, false, false, nullptr, "design_polararray", L("Repeat the body around an axis — set the count and sweep")},
{"mirror", L("Mirror"), 4, "Shift+Z", "key:S+Z", L("Mirror needs a body — add or import one first"), 0x000004feu, 1, 0, false, false, nullptr, "design_mirror", L("Reflect a body about a plane")},
{"pat_curve", L("Pattern on Curve"), 4, nullptr, nullptr, L("Pattern on Curve needs a body and a curve"), 0x00000090u, 1, 0, false, false, nullptr, nullptr, L("Repeat the body along a picked curve")},
{"transform", L("Move"), 5, "Shift+Y", "key:S+Y", L("Transform needs a body — add or import one first"), 0x000021feu, 1, 0, false, false, nullptr, "design_move", L("Move and/or rotate an existing body")},
{"mate", L("Mate"), 5, nullptr, "fly:placement#2", L("Mate needs two coordinate systems — create them first"), 0x00001202u, 2, 0, false, false, nullptr, "design_c_coincident", L("Assembly: align two coordinate systems (fastened, planar, revolute, slider, cylindrical)")},
{"align", L("Align to"), 5, nullptr, nullptr, L("Align needs a body — add or import one first"), 0x00000002u, 1, 0, false, false, nullptr, nullptr, L("Align the body to a picked face or plane")},
{"plane", L("Plane"), 6, "Shift+P", "key:S+P", nullptr, 0x00000453u, 0, 0, false, false, nullptr, "design_plane", L("Reference plane (offset / tilt / midplane / tangent / two edges / coincident)")},
{"axis", L("Axis"), 6, "Shift+A", "key:S+A", nullptr, 0x00000057u, 0, 0, false, false, nullptr, "design_line", L("Datum axis (two points, face normal, cylinder centerline, two planes, along edge)")},
{"coordsys_v", L("Coordinate system"), 6, "Shift+C", "key:S+C", nullptr, 0x00000043u, 0, 0, false, false, nullptr, "design_point", L("Datum coordinate system (world point, or face + direction edge)")},
{"helix", L("Helix"), 6, nullptr, "fly:plane#3", nullptr, 0x00000405u, 0, 0, false, false, nullptr, "design_thread", L("Helical curve (spring path) — use as a sweep path for coils / springs / augers")},
{"project", L("Project"), 6, nullptr, "fly:plane#4", L("Project needs a body — add or import one first"), 0x00000482u, 1, 0, false, false, nullptr, "design_sketch", L("Project body edges onto a plane as sketch entities")},
{"measure", L("Measure"), 6, nullptr, nullptr, nullptr, 0x000b03feu, 0, 0, false, false, nullptr, nullptr, L("Measure between the picked points, edges or faces")},
{"mass_props", L("Volume and area"), 6, nullptr, "btn:mass", nullptr, 0x000000feu, 1, 0, false, false, nullptr, "info", L("Report the volume and surface area of the selected body")},
{"interference", L("Interference"), 6, nullptr, "btn:interference", L("Interference needs at least two bodies"), 0x00000280u, 2, 0, false, false, nullptr, nullptr, L("Check whether two bodies overlap — reports, changes nothing")},
{"edit_feature", L("Edit"), 7, nullptr, "btn:edit", nullptr, 0x00007d8eu, 0, 0, false, false, nullptr, "design_edit", L("Reopen the selected feature to change what it was made from")},
{"rename", L("Rename…"), 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")},
{"colour", L("Color"), 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_body", L("Delete Body"), 7, nullptr, "btn:delete_body", nullptr, 0x000001feu, 1, 0, false, false, nullptr, "design_delete", L("Delete this whole body — removes the feature it was made from")},
{"sk_line_t", L("Line"), 0, "L", "key:L", nullptr, 0x000f8000u, 0, 0, false, true, L("Line"), "design_line", L("Line — click start, then end")},
{"sk_polyline", L("Polyline"), 0, nullptr, "fly:design_line#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Line"), "design_polyline", L("Click points; click the first point to close the loop, right-click to end it open")},
{"sk_rect", L("Corner rectangle"), 0, "R", "key:R", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_rect", L("Rectangle — click two opposite corners")},
{"sk_rect_center", L("Center rectangle"), 0, nullptr, "fly:design_rect#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_crect", L("Click center, then a corner")},
{"sk_rect_oblique", L("Oblique rectangle"), 0, nullptr, "fly:design_rect#2", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_rect_oblique", L("Click two corners of one edge, then a point for the width")},
{"sk_rect_rounded", L("Rounded rectangle"), 0, nullptr, "fly:design_rect#3", nullptr, 0x000f8000u, 0, 0, false, true, L("Rectangle"), "design_rect_rounded", L("Click two opposite corners, then a point for the corner radius")},
{"sk_circle", L("Center circle"), 0, "C", "key:C", nullptr, 0x000f8000u, 0, 0, false, true, L("Circle"), "design_circle", L("Circle — click center, then radius")},
{"sk_circle_2pt", L("2-point circle"), 0, nullptr, "fly:design_circle#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Circle"), "design_circle2pt", L("Click two ends of the diameter")},
{"sk_circle_3pt", L("3-point circle"), 0, nullptr, "fly:design_circle#2", nullptr, 0x000f8000u, 0, 0, false, true, L("Circle"), "design_circle3pt", L("Click three points on the circle")},
{"sk_arc_t", L("3-point arc"), 0, "A", "key:A", nullptr, 0x000f8000u, 0, 0, false, true, L("Arc"), "design_arc3pt", L("Arc — click start, end, then a point")},
{"sk_arc_tangent", L("Tangent arc"), 0, nullptr, "fly:design_arc3pt#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Arc"), "design_tangentarc", L("Click start (on the last entity) then end")},
{"sk_arc_center", L("Center-point arc"), 0, nullptr, "fly:design_arc3pt#2", nullptr, 0x000f8000u, 0, 0, false, true, L("Arc"), "design_arc_center", L("Click center, then start, then a point for the end angle")},
{"sk_slot", L("Slot"), 0, "S", "key:S", nullptr, 0x000f8000u, 0, 0, false, true, L("Slot"), "design_slot", L("Slot — two centerline ends, then the width")},
{"sk_slot_arc", L("Arc slot"), 0, nullptr, "fly:design_slot#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Slot"), "design_slot_arc", L("Click center, start, end, then a point for the width")},
{"sk_ellipse", L("Ellipse"), 0, "E", "key:E", nullptr, 0x000f8000u, 0, 0, false, true, L("Ellipse"), "design_ellipse", L("Ellipse — center, major end, minor point")},
{"sk_ellipse_arc", L("Elliptical arc"), 0, nullptr, "fly:design_ellipse#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Ellipse"), "design_ellipse_arc", L("Click center, major-axis end, minor point, then arc start and end")},
{"sk_spline", L("Spline"), 0, "B", "key:B", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_bspline", L("Spline — click control points")},
{"sk_poly_3", L("Triangle"), 0, nullptr, "btn:poly#3", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Triangle — click center, then a vertex")},
{"sk_poly_4", L("Square"), 0, nullptr, "btn:poly#4", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Square — click center, then a vertex")},
{"sk_poly_5", L("Pentagon"), 0, nullptr, "btn:poly#5", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Pentagon — click center, then a vertex")},
{"sk_polygon", L("Hexagon"), 0, "G", "btn:poly#6", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Hexagon — click center, then a vertex")},
{"sk_poly_8", L("Octagon"), 0, nullptr, "btn:poly#8", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Octagon — click center, then a vertex")},
{"sk_poly_12", L("Dodecagon"), 0, nullptr, "btn:poly#12", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Dodecagon — click center, then a vertex")},
{"sk_poly_inscribed", L("Inscribed"), 0, nullptr, "btn:polyfit#0", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Measure the polygon to its corners (inscribed)")},
{"sk_poly_circumscribed", L("Circumscribed"), 0, nullptr, "btn:polyfit#1", nullptr, 0x000f8000u, 0, 0, false, true, L("Polygon"), "design_polygon", L("Measure the polygon to its flats (circumscribed)")},
{"sk_point_t", L("Point"), 0, "P", "key:P", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_point", L("Point — click to place")},
{"sk_text", L("Text"), 0, nullptr, "btn:text", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_text", L("Type text; 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_offset", L("Offset"), 1, "O", "key:O", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_offset", L("Offset — pick an entity, drag the distance")},
{"sk_trim", L("Trim"), 2, "T", "key:T", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_trim", L("Trim — click a segment to trim it")},
{"sk_fillet", L("Fillet"), 3, "F", "key:F", nullptr, 0x00090000u, 0, 0, false, true, nullptr, "design_filletedge", L("Fillet — pick two lines, set the radius")},
{"sk_chamfer", L("Chamfer"), 3, "H", "key:H", nullptr, 0x00090000u, 0, 0, false, true, nullptr, "design_chamfer", L("Chamfer — pick two lines, set the distance")},
{"sk_array", L("Linear array"), 4, nullptr, "fly:design_array#0", nullptr, 0x000b0000u, 0, 0, false, true, L("Array"), "design_array", L("Pick entities, drag the spacing handle, click the count; click empty to apply")},
{"sk_array_polar", L("Polar array"), 4, nullptr, "fly:design_array#1", nullptr, 0x000b0000u, 0, 0, false, true, L("Array"), "design_polararray", L("Pick entities, drag the sweep handle, click the count; click empty to apply")},
{"sk_mirror", L("Mirror"), 4, "M", "key:M", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_mirror", L("Mirror — pick axis, then entities")},
{"sk_move", L("Move"), 5, nullptr, "fly:design_move#0", nullptr, 0x000f0000u, 0, 0, false, true, L("Move"), "design_move", L("Pick entities, then drag the handle or click the distance; click empty to apply")},
{"sk_rotate", L("Rotate"), 5, nullptr, "fly:design_move#1", nullptr, 0x000f0000u, 0, 0, false, true, L("Move"), "design_rotate", L("Pick entities, then drag around the pivot or click the angle; click empty to apply")},
{"sk_scale", L("Scale"), 5, nullptr, "fly:design_move#2", nullptr, 0x000f0000u, 0, 0, false, true, L("Move"), "design_scale", L("Pick entities, then drag the handle or click the factor; click empty to apply")},
{"sk_dimension", L("Dimension"), 6, "D", "key:D", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_dimension", L("Dimension — click 2 points or an entity")},
{"sk_constrain", L("Constrain"), 6, "K", "key:K", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_constrain", L("Constrain the selected sketch entities to each other")},
{"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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_CONTEXT("Delete", "Design"), 7, "Del", "btn:delete", nullptr, 0x000f7c00u, 0, 0, false, false, nullptr, "design_delete", L("Delete what is selected")},
{"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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_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_CONTEXT("Point", "Design"), 0, "P", "key:P", nullptr, 0x000f8000u, 0, 0, false, true, nullptr, "design_point", L("Point — click to place")},
{"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_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_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_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_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_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_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_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_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_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_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_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_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_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
// 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.
// It sits in the Reference row, the address the sketch-mode Constrain has: one verb, one row.
{"constrain", L("Constrain sketch"), 6, nullptr, "btn:constrain", L("Select a sketch to constrain it"), 0x00004000u, 0, 1, false, false, nullptr, "design_constrain", L("Add dimensions and relations (coincident, tangent, parallel...) to the selected sketch")},
{"sk_construct", L("Construction"), 6, "Q", "key:Q", nullptr, 0x000b8000u, 0, 0, false, true, nullptr, nullptr, L("Toggle construction: geometry that guides but is never built")},
{"sk_extend", L("Extend"), 7, "X", "key:X", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_extend", L("Extend — click a line/arc to extend it")},
{"sk_delete", L("Delete"), 7, "Del", "btn:sk_delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_delete", L("Delete the selected sketch entities")},
{"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_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_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_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
// each names the quantity in the drawing-office word for THAT element; all three land on
// the same handler, because dimension_kind() already resolves the quantity from the
// selection. Without these, an element's own numbers were reachable only by arming the
// Dimension tool and re-picking geometry that was already selected.
{"sk_length", L("Length…"), 7, "V", "key:V", nullptr, 0x00010000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the length of this line")},
{"sk_radius", L("Radius / diameter…"), 7, "V", "key:V", nullptr, 0x00020000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the radius of this arc, or the diameter of this circle")},
{"sk_angdist", L("Angle / distance…"), 7, "V", "key:V", nullptr, 0x00080000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the angle between two lines, or the distance between the two picks")},
{"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_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_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;

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