Compare commits

..
Author SHA1 Message Date
SoftFever ec21c84738 Keep the Design tab's Bodies right-click menu open on Linux 2026-10-06 22:46:00 +08:00
SoftFever ce7d4815e6 Set the Design tab's Origin and Bed rows apart from the feature list
They now sit above the features' framed list as fixed view switches:
a click no longer selects them, they stay put while the features scroll,
their eyes line up with the features' eyes, and their labels dim when
hidden. Ctrl+Shift+O toggles the Origin from the keyboard, as
Ctrl+Shift+B does the Bed. The test script's position for the first
feature row is calculated, not measured, and needs re-measuring on the
test setup.
2026-10-06 20:46:09 +08:00
SoftFever 6d8a13c33b Move the Design tab's Bed toggle into the Feature tree, under the Origin row
The bed's show/hide state is now remembered across sessions like the Origin
row's, shown by default. The GUI ladder's ribbon x-coordinates are shifted by
the removed checkbox's derived width and still need re-measuring on the rig.
2026-10-06 20:44:43 +08:00
SoftFever 4d4e52722f Open the Design tab's sketches only once their plane is picked
Sketch on a picked flat face or reference plane opens the sketch on it at once. With nothing
picked it no longer enters sketch mode: the reference planes and axes come up, and the plane or
flat face clicked next opens the sketch and puts them away. Esc or Cancel leaves without one.
A picked plane is used up by the sketch on it and dropped by Esc or a click on nothing, the plane
prompt is no longer replaced by a stale tool hint, and clicking the face a sketch was just
cancelled on picks that face again rather than the whole body.
2026-10-06 20:17:47 +08:00
SoftFever 406ef0dd03 Stop the Design tab's import buttons from staying highlighted after a click 2026-10-06 19:25:22 +08:00
SoftFever 206a7461ea Give the Design tab's import icons an arrow and size its document icons consistently 2026-10-06 19:03:30 +08:00
SoftFever 78a22fca93 Hide the Design tab's reference planes until a sketch needs them, with an Origin row to pin them 2026-10-06 18:38:57 +08:00
SoftFever 064eee8b84 Stop the Design tab's body Move from getting stuck after Esc or a click off the gizmo
Esc and Cancel now put the body back, and Confirm keeps the new position. A click off the
gizmo only moves the camera and no longer leaves the Move card and its buttons dead. Starting
another edit or a rebuild keeps the position, and undo is refused until the Move is confirmed
or cancelled.
2026-10-06 16:57:08 +08:00
SoftFever 250fed8808 Show the Design tab's reference planes as separate labelled squares around the origin
The XY, XZ and YZ planes no longer cross through the bed. Each is a small square in its axis
colour, set off from the axes into the corner that faces the default front view, with its name
written in the plane. Dash-dot axes run between the squares and replace the bed's axis triad
while they show. Hovering a plane greys it and selecting one makes it solid, and picking a
solid face now clears a previously picked plane.
2026-10-06 16:32:36 +08:00
SoftFever 0c2e26fa3d Stop the Design tab's move arrows from jumping the body on the first drag
Grabbing an arrow anywhere along its length snapped the body's centre to that point as soon
as the mouse moved. The body now moves by how far the cursor travels from where the arrow
was grabbed, including when it is grabbed while looking straight down the axis.
2026-10-06 14:48:48 +08:00
SoftFever e328b60992 Make the Design tab's reference planes readable where they cross
The XY/XZ/YZ planes are cut along each other and drawn back to front, with
lines along every crossing, and their fills are strong enough for the order
to show. Before, they blended into one grey smear and were too pale to work with.
2026-10-06 14:19:46 +08:00
SoftFever afa9252b59 Make the Fit camera button frame the Design tab's selection and sketches
In the Design tab the button always swung the camera to the plate view and framed the whole
bed, whatever was selected: the tab's picks and sketches are neither the canvas's selection
nor its volumes. It now frames the selected faces, body, edges, vertex, sketch region or
sketch entities, and with nothing selected everything on show — the visible bodies, the
feature preview and the sketches — keeping the current view direction. An empty tab still
frames the bed as before.
2026-10-06 12:28:52 +08:00
SoftFever 56ebde968a Keep the Design tab's status line clear of the view buttons at any display scale
The line started at a fixed inset scaled like the ImGui style, while the navigator and the
round canvas buttons scale with the monitor's DPI on Windows, so at 150% the buttons covered
its first words. It now starts past the edge the canvas reports for that corner.
2026-10-06 11:55:41 +08:00
SoftFever 840d44b611 Keep the Design tab's edge lines in step with the bodies after undo, hide and delete 2026-10-06 11:21:18 +08:00
SoftFever 34696f5651 Name the Design canvas flag after its role, not the axes it once moved 2026-10-06 03:44:15 +08:00
SoftFever c300fa1e8d Keep the Design tab's bed, grid and reference planes aligned on every plate 2026-10-06 03:41:33 +08:00
SoftFever cd30b196b2 Let the camera pan and orbit freely while sketching in the Design tab
Panning or orbiting with a right-drag no longer ends the polyline chain or drops
the point already placed. Only a right-click that doesn't move does. In the
Touchpad camera style, Alt+move and Shift+move now orbit and pan even while a
draw tool is armed.
2026-10-05 23:24:50 +08:00
SoftFever b67269ccc4 Fix z-fighting between feature previews and the bodies in the Design tab
The bodies now always draw over the preview on every face a feature leaves unchanged, for
all features. Since a hole's cut sits inside the body, the Hole preview now hides the
bodies and shows the result alone, as Fillet/Chamfer and Draft do.
2026-10-05 19:38:46 +08:00
232 changed files with 43651 additions and 209284 deletions
-1
View File
@@ -61,7 +61,6 @@ 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 -c core.quotePath=false diff --name-only HEAD^1 -- src tests | grep -qE '\.(cpp|cc|cxx|hpp|h|hxx)$'; then
if git 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,7 +90,6 @@ 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
@@ -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** | — | no longer orbits in this canvas | middle-drag orbits, right-drag pans (`:10013-10016`) |
| `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 |
| `LeftUp` | — | commit pick **or** resolve band | — | — | `:10035-10041` |
| `MiddleDrag` / `RightDrag` | — | must not see it | — | orbit / pan | camera gestures never reach the FSM |
| `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 |
| `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` |
+74 -14
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, an armed sketch tool, a constrain session | exits it; drawn entities survive |
| `Tool` | a feature card, Sketch waiting for its plane, 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,26 +149,66 @@ 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. 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.
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.
Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control
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.
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.
Entering a sketch changes three things at once so the mode is legible: a banner above the
Entering a sketch changes two 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), 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.
`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.
## Rendering the bodies
@@ -189,6 +229,15 @@ 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
@@ -214,7 +263,8 @@ 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.
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).
## Following the app
@@ -232,8 +282,18 @@ 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 the eye shows
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
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
+2 -61
View File
@@ -52,6 +52,8 @@ 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
@@ -299,64 +301,3 @@ 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
+4 -1
View File
@@ -1 +1,4 @@
<?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>
<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>

Before

Width:  |  Height:  |  Size: 398 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+1
View File
@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 301 B

+1
View File
@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 481 B

+1
View File
@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 794 B

+1
View File
@@ -0,0 +1 @@
<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>

After

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

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

+3 -1
View File
@@ -1 +1,3 @@
<?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>
<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>

Before

Width:  |  Height:  |  Size: 696 B

After

Width:  |  Height:  |  Size: 891 B

+4 -1
View File
@@ -1 +1,4 @@
<?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>
<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>

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.5 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="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" 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="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" 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="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" 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="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" 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="#009688"/>
<stop offset="1" stop-color="#009688" stop-opacity="0"/>
<stop stop-color="#00AE42"/>
<stop offset="1" stop-color="#00AE42" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 775 B

After

Width:  |  Height:  |  Size: 775 B

+4 -1
View File
@@ -1 +1,4 @@
<?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>
<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>

Before

Width:  |  Height:  |  Size: 438 B

After

Width:  |  Height:  |  Size: 1023 B

+9 -9
View File
@@ -416,8 +416,8 @@ function favDigitFromEvent(e) {
function resultCountText(total, shown, query) {
return (query || "").trim() ?
T("sd_result_count", "Matching actions: %s", shown) :
T("sd_result_count_all", "Actions: %s", total);
T("sd_result_count", "Showing %s actions", shown) :
T("sd_result_count_all", "%s actions", 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", "Favorites are full (%s max)", (payload.limit || K_FAV_LIMIT)));
flashHint(T("sd_favs_full", "Favourites 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 favorites (%s)", shortcutCtrl() + "B") :
T("sd_pin_fav", "Pin to favorites (%s)", shortcutCtrl() + "B");
pin.title = on ? T("sd_unpin_fav", "Unpin from favourites (%s)", shortcutCtrl() + "B") :
T("sd_pin_fav", "Pin to favourites (%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", "Favorite %s (%s)", slot_num, shortcutAlt() + slot);
badge.title = T("sd_fav_slot", "Favourite %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 favorites");
unpin.setAttribute("aria-label", T("sd_remove_fav", "Remove from favorites"));
unpin.title = T("sd_remove_fav", "Remove from favourites");
unpin.setAttribute("aria-label", T("sd_remove_fav", "Remove from favourites"));
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", "Matching tabs: %s", list.length) : T("sd_tab_count", "Tabs: %s", list.length);
countEl.textContent = q ? T("sd_tab_match_count", "%s matches", list.length) : T("sd_tab_count", "%s tabs", list.length);
}
list.forEach(function (t, i) { listEl.appendChild(renderTabRow(t, i)); });
}
+17 -11
View File
@@ -147,6 +147,7 @@ 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
@@ -480,10 +481,14 @@ 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. 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.
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.
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
@@ -499,10 +504,10 @@ def enter_sketch(timeout=180):
click(132, 53) # Design tab
time.sleep(2.0)
dismiss_modals()
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
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
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
@@ -511,14 +516,15 @@ 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 plane click + Shift+S within "
die("no sketch opened after Shift+S + plane click 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 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.
# 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.
PLANE_PX = (913, 359)
# Every coordinate below stays inside 1000..1400 x 500..760 — the box check-gui-sketching.py's
+6 -9
View File
@@ -78,9 +78,8 @@ 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 and the
# L(...) / L_CONTEXT(...) translation markers.
f, cur, q, esc, depth = [], "", False, False, 0
# sketch_mode, family, ... — split on top-level commas, respecting quotes.
f, cur, q, esc = [], "", False, False
for ch in line[1:]:
if esc:
cur += ch; esc = False; continue
@@ -88,18 +87,16 @@ def load_table():
cur += ch; esc = True; continue
if ch == '"':
q = not q
if not q:
depth += (ch == "(") - (ch == ")")
if ch == "," and not q and depth == 0:
if ch == "," and not q:
f.append(cur.strip()); cur = ""; continue
if ch == "}" and not q and depth == 0:
if ch == "}" and not q:
break
cur += ch
f.append(cur.strip())
if len(f) < 12:
continue
# 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)
# User-facing fields are wrapped in the L("...") gettext marker; strip it.
lit = lambda s: None if s == "nullptr" else re.sub(r'^L\((.*)\)$', r'\1', s).strip('"')
out.append({"id": lit(f[0]), "name": lit(f[1]), "row": int(f[2]), "key": lit(f[3]),
"action": lit(f[4]), "accepts": int(f[6].rstrip("u"), 0),
"need_bodies": int(f[7]), "need_sketches": int(f[8]),
+28 -11
View File
@@ -229,7 +229,20 @@ CHROME_DY = int(os.environ.get("ORCA_CAD_CHROME_DY", "26"))
DESIGN_TAB = (128, 29 + CHROME_DY)
# Feature-tree rows, measured on the rig at 1920x1080: first row centre, then 23 px apart.
# 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.
# 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.
#
@@ -237,7 +250,8 @@ DESIGN_TAB = (128, 29 + CHROME_DY)
# 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_ROW0 = (300, 215 + CHROME_DY)
TREE_GAP = 12 + 1
TREE_ROW0 = (300, 215 + 2 * 23 + TREE_GAP + CHROME_DY)
def go_design():
@@ -266,18 +280,21 @@ 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.
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()'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.
"""
leave_sketch()
click(*plane_px)
key("shift+s", 0.8)
key("Escape", 0.4) # entering sketch mode pops the offer; dismiss it
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("p", 0.6)
if try_call("sketch_describe") is None:
shot("/shots/gl-enter-failed.png")
die("no sketch opened after plane click + Shift+S (see /shots/gl-enter-failed.png)")
die("no sketch opened after Shift+S + plane click (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)
@@ -737,7 +754,7 @@ def corner_pair(ents):
die("no adjacent pair in what should be a rectangle")
CONSTRUCTION_CHECKBOX = (419, 75 + CHROME_DY)
CONSTRUCTION_CHECKBOX = (419 + RIBBON_DX, 75 + CHROME_DY)
def draw_line(x0, y0, x1, y1, length, angle):
@@ -866,7 +883,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 + 42 * i, CON_BTN_Y) for i, n in enumerate(
CON_BTN = {n: (449 + RIBBON_DX + 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"])}
@@ -880,7 +897,7 @@ CON_BTN = {n: (449 + 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 + 42 * i, CON_BTN_Y) for i, n in enumerate(
CON_BTN_SKETCH = {n: (677 + RIBBON_DX + 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"])}
+2 -10
View File
@@ -43,11 +43,6 @@ 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.
@@ -95,9 +90,6 @@ 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 {",
"",
@@ -121,7 +113,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); msgctxt \"Design\", translated at use",
" const char* name; // drawing-office word (L10); marked L(); 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;",
@@ -178,7 +170,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_design(v["name"]), slots.index(v["slot"]),
cstr(v["id"]), tstr(v["name"]), slots.index(v["slot"]),
cstr(v.get("key")), cstr(v.get("action")), tstr(v.get("refusal")),
mask, n.get("bodies", 0), n.get("sketches", 0),
"true" if n.get("sheet") else "false",
+2 -2
View File
@@ -229,11 +229,11 @@
"none"
],
"needs": {},
"refusal": "Click a face or a reference plane in the viewport, then a sketch tool",
"refusal": "Select a flat face or a reference plane to sketch on",
"gui": true,
"action": "key:S+S",
"icon": "design_sketch",
"hint": "Click a face or a reference plane, then pick a drawing tool"
"hint": "Sketch on the selected flat face or plane, or click one next"
},
{
"id": "extrude",
+18 -58
View File
@@ -8,9 +8,6 @@ include) fails wherever the error is. Headers are compiled on their own, and
fail only on errors in their changed lines. Deleting an #include also fails
on every use, changed or not, that now lacks the header it provided.
With -- --fix, clang-tidy adds the missing includes, on those lines only, and
each file it changed is checked again so that only what remains is reported.
The checks come from .clang-tidy at the repository root. The compile database
must come from a configure with SLIC3R_PCH=OFF, or the precompiled header hides
missing includes.
@@ -39,9 +36,6 @@ 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$")
@@ -78,8 +72,7 @@ def parse_diff(diff):
change = None
for line in diff.splitlines():
if line.startswith("+++ "):
# git appends a tab to the header of a path that contains a space.
target = line[4:].removesuffix("\t")
target = line[4:]
change = changes.setdefault(target[2:], FileChange()) if target.startswith("b/") else None
continue
if change is None:
@@ -105,11 +98,8 @@ def is_checked(path):
def changed_files(merge_base):
# Against the working tree, so a local run covers uncommitted edits too.
# core.quotePath=false keeps a non-ASCII path unquoted, and the explicit prefixes
# override diff.noprefix and diff.mnemonicPrefix, so parse_diff sees its b/ prefix.
diff = subprocess.run(["git", "-c", "core.quotePath=false", "diff", "-U0", "--no-color", "--no-ext-diff",
"--src-prefix=a/", "--dst-prefix=b/", "--diff-filter=AMR", merge_base],
check=True, capture_output=True, **UTF8).stdout
diff = subprocess.run(["git", "diff", "-U0", "--no-color", "--no-ext-diff", "--diff-filter=AMR", merge_base],
check=True, capture_output=True, text=True).stdout
return {path: change for path, change in parse_diff(diff).items() if is_checked(path)}
@@ -127,9 +117,8 @@ 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:
# 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)
cmd.insert(1, "--line-filter=" + json.dumps([{"name": path, "lines": lines}]))
result = subprocess.run(cmd, capture_output=True, text=True)
suggestions = parse_suggested_includes(fixes)
output = result.stdout + result.stderr
return result.returncode, output, parse_diagnostics(output, suggestions)
@@ -189,7 +178,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, **UTF8)
shown = subprocess.run(["git", "show", f"{revision}:{path}"], capture_output=True, text=True)
if shown.returncode != 0:
return Counter()
# Beside the original, so its quoted includes resolve the same way.
@@ -205,19 +194,13 @@ def errors_alone_at(revision, clang_tidy, build_dir, path):
def check_file(clang_tidy, build_dir, merge_base, path, change, extra_args):
"""Run clang-tidy on one file and return (failed, output, failing diagnostics, fixed)."""
fixing = any(arg.startswith("--fix") for arg in extra_args)
if fixing:
with open(path, "rb") as f:
before = f.read()
"""Run clang-tidy on one file and return (failed, output, failing diagnostics)."""
# A deleted include can orphan uses on unchanged lines, so such a file is
# checked whole and the findings narrowed here. --fix keeps a line filter so
# it never rewrites unrelated code, which for such a file means a second,
# fixing run limited to the lines the first one found wanting.
whole = bool(change.removed_includes)
# checked whole and the findings narrowed here. --fix keeps the line filter
# so it never rewrites unrelated code.
whole = bool(change.removed_includes) and not extra_args
returncode, output, diagnostics = run_clang_tidy(clang_tidy, build_dir, path,
None if whole else change.lines,
[] if whole else extra_args)
None if whole else change.lines, extra_args)
real = os.path.realpath(path)
def introduced(d):
@@ -233,25 +216,14 @@ def check_file(clang_tidy, build_dir, merge_base, path, change, extra_args):
if errors and change.removed_includes:
with open(path, encoding="utf-8") as f:
text = f.read()
before_sites = errors_alone_at(merge_base, clang_tidy, build_dir, path)
before = errors_alone_at(merge_base, clang_tidy, build_dir, path)
failing += [d for d in errors if d not in failing
and (error_sites([d], text) - before_sites)]
failed = bool(failing)
elif whole:
and (error_sites([d], text) - before)]
return bool(failing), output, failing
if whole:
failing = [d for d in diagnostics if d.is_compile_error or introduced(d)]
failed = bool(failing)
else:
failing, failed = diagnostics, returncode != 0
if not fixing:
return failed, output, failing, False
if whole and failing:
lines = change.lines + [[d.line, d.line] for d in failing if os.path.realpath(d.file) == real]
run_clang_tidy(clang_tidy, build_dir, path, lines, extra_args)
with open(path, "rb") as f:
if f.read() == before:
return failed, output, failing, False
# Checked again, so what is reported is what the fixes left.
return check_file(clang_tidy, build_dir, merge_base, path, change, [])[:3] + (True,)
return bool(failing), output, failing
return returncode != 0, output, diagnostics
def main():
@@ -262,8 +234,6 @@ 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()
@@ -284,14 +254,11 @@ def main():
annotate = os.environ.get("GITHUB_ACTIONS") == "true"
root = os.getcwd() + os.sep
failed = []
fixed = []
with ThreadPoolExecutor(max_workers=args.jobs) as pool:
jobs = {path: pool.submit(check_file, args.clang_tidy, args.build_dir, merge_base, path, change, args.extra_args)
for path, change in todo}
for path, job in jobs.items():
file_failed, output, diagnostics, file_fixed = job.result()
if file_fixed:
fixed.append(path)
file_failed, output, diagnostics = job.result()
if not file_failed:
continue
failed.append(path)
@@ -306,13 +273,6 @@ def main():
if len(diagnostics) > MAX_REPORTED:
print(f"... and {len(diagnostics) - MAX_REPORTED} more")
if fixed:
print(f"\nAdded includes to {len(fixed)} file(s):")
for path in fixed:
print(f" {path}")
if failed and fixed:
print(f"\nclang-tidy still fails on {len(failed)} file(s); the findings above are what --fix could not add.")
return 1
if failed:
print(f"\nclang-tidy failed on {len(failed)} file(s). Add the includes it names, or apply its "
"suggestions locally with scripts/run_clang_tidy.sh --fix (scripts\\run_clang_tidy.ps1 -Fix on Windows).")
+7 -31
View File
@@ -54,21 +54,6 @@ 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 ?") {
@@ -93,11 +78,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") {
Invoke-Quiet { & py -3 --version }
& py -3 --version *> $null
if ($LASTEXITCODE -eq 0) { $Python = @("py", "-3") }
}
if (-not $Python -and (Has "python")) {
Invoke-Quiet { & python --version }
& python --version *> $null
if ($LASTEXITCODE -eq 0) { $Python = @("python") }
}
if (-not $Python) { Request-Install "Python 3" "winget install -e --id Python.Python.3.12" }
@@ -115,12 +100,7 @@ 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"
# 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."
}
$envLines = & cmd /c "`"$VsDevCmd`" -arch=$Arch -host_arch=$HostArch -no_logo >nul && set"
foreach ($line in $envLines) {
$i = $line.IndexOf("=")
if ($i -gt 0) { Set-Item -Path ("env:" + $line.Substring(0, $i)) -Value $line.Substring($i + 1) }
@@ -210,17 +190,13 @@ $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"
Invoke-Quiet { & cmake @cmakeArgs } $Log
& cmake @cmakeArgs *> $Log
if ($LASTEXITCODE -ne 0) {
Get-Content -LiteralPath $Log -Tail 20
Get-Content $Log -Tail 20
throw "Configuring failed; the full log is in $Log."
}
$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."
}
& cmake --build $BuildDir --target git_commit_hash_header *> $null
if ($LASTEXITCODE -ne 0) { throw "Generating git_commit_hash.h failed." }
# --- Base revision ------------------------------------------------------------
+10 -40
View File
@@ -6,9 +6,8 @@
# scripts/run_clang_tidy.sh --fix also add the missing includes it names
#
# It configures a separate build directory (build-tidy) without the precompiled
# header, uses the clang-tidy on your system or installs the pinned one into a
# virtual environment inside it, and runs scripts/clang_tidy_diff.py the way CI
# does. Uncommitted changes are checked too.
# header, installs the pinned clang-tidy into a virtual environment inside it, and
# runs scripts/clang_tidy_diff.py the way CI does. Uncommitted changes are checked too.
set -euo pipefail
@@ -24,8 +23,7 @@ Usage: scripts/run_clang_tidy.sh [options]
deps/build/<arch> on macOS)
-j, --jobs N parallel clang-tidy runs (default: all cores)
--fix apply clang-tidy's fixes (adds the missing includes)
-y, --yes install missing tools without asking; another clang-tidy
version found on the system is then not offered
-y, --yes install missing tools without asking
-h, --help show this help
EOF
}
@@ -138,38 +136,12 @@ REQUIREMENTS="$ROOT/scripts/clang_tidy_requirements.txt"
PINNED=$(sed -n 's/^clang-tidy==//p' "$REQUIREMENTS")
VENV="$BUILD_DIR/clang-tidy-venv"
is_pinned() {
[ -x "$1" ] && "$1" --version 2>/dev/null | grep -q "version $PINNED"
}
CLANG_TIDY="${CLANG_TIDY:-}"
if [ -n "$CLANG_TIDY" ]; then
if [ -n "${CLANG_TIDY:-}" ]; then
# Set by the caller: use it as is.
is_pinned "$CLANG_TIDY" || echo "Warning: $CLANG_TIDY is not clang-tidy $PINNED, so results may differ from CI." >&2
:
else
# One already on the system comes first: the pinned version outright, another
# version if the user accepts the difference. The pinned version is installed
# into a virtual environment otherwise.
INSTALLED=""
for candidate in $(command -v clang-tidy "clang-tidy-${PINNED%%.*}" || true) \
"/usr/lib/llvm-${PINNED%%.*}/bin/clang-tidy" \
"$(brew --prefix llvm 2>/dev/null || true)/bin/clang-tidy"; do
if is_pinned "$candidate"; then
CLANG_TIDY="$candidate"
break
fi
[ -z "$INSTALLED" ] && [ -x "$candidate" ] && INSTALLED="$candidate"
done
if [ -z "$CLANG_TIDY" ] && [ -n "$INSTALLED" ] && [ "$YES" = 0 ] && ! is_pinned "$VENV/bin/clang-tidy"; then
echo "Found $INSTALLED, which is $("$INSTALLED" --version | sed -n 's/.*version \([0-9.]*\).*/\1/p' | head -n 1), not the $PINNED CI uses, so results may differ slightly."
if ask "Use it anyway?"; then
CLANG_TIDY="$INSTALLED"
fi
fi
fi
if [ -z "$CLANG_TIDY" ]; then
CLANG_TIDY="$VENV/bin/clang-tidy"
if ! is_pinned "$CLANG_TIDY"; then
if [ ! -x "$CLANG_TIDY" ] || ! "$CLANG_TIDY" --version | grep -q "version $PINNED"; then
if ask "clang-tidy $PINNED (the version CI uses) is not installed. Install it into $VENV?"; then
mkdir -p "$BUILD_DIR"
if ! python3 -m venv "$VENV"; then
@@ -186,6 +158,9 @@ if [ -z "$CLANG_TIDY" ]; then
fi
fi
fi
if ! "$CLANG_TIDY" --version | grep -q "version $PINNED"; then
echo "Warning: $CLANG_TIDY is not clang-tidy $PINNED, so results may differ from CI." >&2
fi
# --- Dependencies -------------------------------------------------------------
@@ -242,12 +217,7 @@ cmake --build "$BUILD_DIR" --target git_commit_hash_header >/dev/null
if [ -z "$BASE" ]; then
REMOTE=$(git remote -v | awk '/github\.com[:\/]OrcaSlicer\/OrcaSlicer(\.git)? \(fetch\)/ { print $1; exit }')
if [ -z "$REMOTE" ]; then
# Against a fork's main that already has the commits, nothing is checked.
echo "Warning: no remote points at github.com/OrcaSlicer/OrcaSlicer, so this compares against origin/main." >&2
echo "If origin is your fork, add the upstream remote (git remote add upstream https://github.com/OrcaSlicer/OrcaSlicer.git) or pass --base." >&2
REMOTE=origin
fi
REMOTE="${REMOTE:-origin}"
if [ "$FETCH" = 1 ]; then
git fetch --quiet "$REMOTE" main
fi
-96
View File
@@ -5,13 +5,10 @@ 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__), "..")))
@@ -65,10 +62,6 @@ 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):
@@ -135,94 +128,5 @@ 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])
# Whatever diff.noprefix or diff.mnemonicPrefix a user has set.
self.assertIn("--dst-prefix=b/", call.args[0])
self.assertEqual(call.kwargs["encoding"], "utf-8")
self.assertEqual(files["src/libslic3r/Über.cpp"].lines, [[2, 2]])
def test_clang_tidy_and_git_show_output_is_decoded_as_utf8(self):
_, call, _ = self.run_patched(clang_tidy_diff.run_clang_tidy, "clang-tidy", "build",
"src/libslic3r/Color.cpp", None, [])
self.assertEqual(call.kwargs["encoding"], "utf-8")
_, call, _ = self.run_patched(clang_tidy_diff.errors_alone_at, "base", "clang-tidy", "build",
"src/libslic3r/Color.hpp", returncode=128)
self.assertEqual(call.kwargs["encoding"], "utf-8")
class TestCheckFileFix(unittest.TestCase):
"""check_file with -- --fix: what clang-tidy is run on, and what is reported afterwards."""
def setUp(self):
self.dir = tempfile.TemporaryDirectory()
self.addCleanup(self.dir.cleanup)
self.path = os.path.join(self.dir.name, "Color.cpp")
with open(self.path, "w") as f:
f.write("int x;\n")
def finding(self, line, include=""):
return clang_tidy_diff.Diagnostic(self.path, line, 1, "error",
'no header providing "x" is directly included [misc-include-cleaner]', include)
def check(self, change, results, fix_writes=None):
"""Run check_file with run_clang_tidy answering from `results` in turn; the --fix run
rewrites the file with `fix_writes` when given. Returns (result, calls)."""
calls = []
def run(clang_tidy, build_dir, path, lines, extra_args):
calls.append((lines, extra_args))
if "--fix" in extra_args and fix_writes is not None:
with open(path, "w") as f:
f.write(fix_writes)
return results[len(calls) - 1]
with mock.patch.object(clang_tidy_diff, "run_clang_tidy", side_effect=run):
result = clang_tidy_diff.check_file("clang-tidy", "build", "base", self.path, change, ["--fix"])
return result, calls
def test_a_deleted_include_is_fixed_on_the_lines_it_orphaned_only(self):
change = clang_tidy_diff.FileChange(lines=[[4, 4]], removed_includes={"libslic3r/Point.hpp"})
orphaned = self.finding(50, "<libslic3r/Point.hpp>")
unrelated = self.finding(60, "<vector>")
(failed, _, failing, fixed), calls = self.check(
change, [(1, "", [orphaned, unrelated]), (1, "", [orphaned]), (0, "", [])], fix_writes="#include <libslic3r/Point.hpp>\n")
self.assertEqual(calls, [(None, []), ([[4, 4], [50, 50]], ["--fix"]), (None, [])])
self.assertEqual((failed, failing, fixed), (False, [], True))
def test_a_file_the_fix_did_not_change_keeps_its_findings(self):
change = clang_tidy_diff.FileChange(lines=[[4, 4]])
error = clang_tidy_diff.Diagnostic(self.path, 4, 1, "error", "unknown type name 'Foo' [clang-diagnostic-error]")
(failed, _, failing, fixed), calls = self.check(change, [(1, "", [error])])
self.assertEqual(calls, [([[4, 4]], ["--fix"])])
self.assertEqual((failed, failing, fixed), (True, [error], False))
def test_a_changed_file_is_checked_again_and_reports_what_is_left(self):
change = clang_tidy_diff.FileChange(lines=[[4, 4]])
error = clang_tidy_diff.Diagnostic(self.path, 4, 1, "error", "unknown type name 'Foo' [clang-diagnostic-error]")
(failed, _, failing, fixed), calls = self.check(
change, [(1, "", [self.finding(4, "<vector>"), error]), (1, "", [error])], fix_writes="#include <vector>\n")
self.assertEqual(calls, [([[4, 4]], ["--fix"]), ([[4, 4]], [])])
self.assertEqual((failed, failing, fixed), (True, [error], True))
if __name__ == "__main__":
unittest.main()
-2
View File
@@ -153,8 +153,6 @@ 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,7 +35,6 @@ 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,14 +11,6 @@
#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,8 +3,6 @@
#include <boost/nowide/fstream.hpp>
#include "StackWalker.h"
#include <eh.h>
#include <cstddef>
#include <string>
class CBaseException : public CStackWalker
{
-3
View File
@@ -2,9 +2,6 @@
#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,7 +2,6 @@
#include <Windows.h>
#include <tchar.h>
#include <vector>
#include <cstddef>
namespace textconv_helper
{
+4 -3
View File
@@ -42,9 +42,6 @@
#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.
@@ -370,6 +367,10 @@ 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,9 +3,6 @@
#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
+16 -18
View File
@@ -1,7 +1,6 @@
#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>
@@ -74,19 +73,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 = _u8L("cannot read STEP file");
err = "cannot read STEP file";
return out;
}
reader.TransferRoots();
const TopoDS_Shape shape = reader.OneShape();
if (shape.IsNull()) { err = _u8L("STEP file has no geometry"); return out; }
if (shape.IsNull()) { err = "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() : _u8L("OCCT failed to read STEP");
err = *e.what() ? e.what() : "OCCT failed to read STEP";
out.clear();
}
return out;
@@ -116,7 +115,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(_u8L("mesh_to_brep: mesh has no triangles"));
throw std::runtime_error("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.
@@ -151,8 +150,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(_u8L("mesh_to_brep: every triangle was rejected as degenerate "
"(try a smaller tolerance)"));
throw std::runtime_error("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());
@@ -362,7 +361,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(_u8L("fillet radius too large for this geometry"));
if (!fillet.IsDone()) throw std::runtime_error("fillet radius too large for this geometry");
return fillet.Shape();
}
@@ -378,7 +377,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(_u8L("chamfer distance too large for this geometry"));
if (!chamfer.IsDone()) throw std::runtime_error("chamfer distance too large for this geometry");
return chamfer.Shape();
}
@@ -405,7 +404,7 @@ TopoDS_Shape GeometryEngine::apply_fillet(const TopoDS_Shape& solid, double radi
}
mk.Build();
if (!mk.IsDone()) throw std::runtime_error(_u8L("apply_fillet: OCCT fillet failed"));
if (!mk.IsDone()) throw std::runtime_error("apply_fillet: OCCT fillet failed");
return mk.Shape();
}
@@ -422,7 +421,7 @@ TopoDS_Shape GeometryEngine::apply_chamfer(const TopoDS_Shape& solid, double dis
}
mk.Build();
if (!mk.IsDone()) throw std::runtime_error(_u8L("apply_chamfer: OCCT chamfer failed"));
if (!mk.IsDone()) throw std::runtime_error("apply_chamfer: OCCT chamfer failed");
return mk.Shape();
}
@@ -548,13 +547,12 @@ GeometryEngine::MassProps GeometryEngine::mass_properties(const TopoDS_Shape& sh
std::string GeometryEngine::primitive_name(PrimitiveType type)
{
switch (type) {
// 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");
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";
}
}
+29 -30
View File
@@ -1,7 +1,6 @@
#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>
@@ -139,7 +138,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(_u8L("Profile has fewer than 2 points"));
throw std::runtime_error("Profile has fewer than 2 points");
BRepBuilderAPI_MakeWire builder;
for (size_t i = 0; i < points.size(); ++i) {
@@ -151,7 +150,7 @@ TopoDS_Wire SketchProfile::to_occt_wire(const SketchPlane& plane) const
}
builder.Build();
if (!builder.IsDone())
throw std::runtime_error(_u8L("Failed to build wire from profile"));
throw std::runtime_error("Failed to build wire from profile");
return builder.Wire();
}
@@ -159,9 +158,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(_u8L("extrude depth is zero"));
if (vec.Magnitude() < 1e-9) throw std::runtime_error("extrude depth is zero");
BRepPrimAPI_MakePrism prism(base, vec);
if (!prism.IsDone()) throw std::runtime_error(_u8L("extrude failed"));
if (!prism.IsDone()) throw std::runtime_error("extrude failed");
return prism.Shape();
}
@@ -170,7 +169,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(_u8L("Fuse failed"));
if (!fuse.IsDone()) throw std::runtime_error("Fuse failed");
return fuse.Shape();
}
return SketchEngine::make_prism(face, gp_Vec(dir) * length);
@@ -180,7 +179,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(_u8L("Failed to make face from wire"));
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire");
return make_extrude(fm.Face(), plane, length, symmetric, taper_deg);
}
@@ -195,7 +194,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(_u8L("Failed to make face from wire"));
if (!fm.IsDone()) throw std::runtime_error("Failed to make face from wire");
return make_extrude_two_sided(fm.Face(), plane, up, down);
}
@@ -207,7 +206,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(_u8L("two-sided extrude fuse failed"));
if (!fuse.IsDone()) throw std::runtime_error("two-sided extrude fuse failed");
return fuse.Shape();
}
@@ -217,7 +216,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(_u8L("Failed to make face from wire"));
if (!fm.IsDone()) throw std::runtime_error("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();
@@ -263,7 +262,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(_u8L("extrude depth is zero"));
if (std::abs(length) < 1e-9) throw std::runtime_error("extrude depth is zero");
// Drop consecutive coincident points and the closing duplicate. FreeType /
// SVG flattening routinely emits repeated points which would build a
@@ -349,7 +348,7 @@ TopoDS_Shape SketchEngine::make_extrude_regions(
}
}
if (count == 0) throw std::runtime_error(_u8L("imported regions produced no extrudable geometry"));
if (count == 0) throw std::runtime_error("imported regions produced no extrudable geometry");
return count == 1 ? last : TopoDS_Shape(comp); // avoid a compound-of-one
}
@@ -357,7 +356,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(_u8L("Failed to make face from wire"));
throw std::runtime_error("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
@@ -388,7 +387,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(_u8L("Failed to revolve"));
throw std::runtime_error("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();
@@ -398,29 +397,29 @@ TopoDS_Shape SketchEngine::make_sweep(const TopoDS_Wire& profile, const TopoDS_W
{
BRepBuilderAPI_MakeFace faceMaker(profile);
if (!faceMaker.IsDone())
throw std::runtime_error(_u8L("Failed to make face from sweep profile"));
throw std::runtime_error("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(_u8L("Failed to sweep profile along path"));
throw std::runtime_error("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(_u8L("loft needs at least 2 profiles"));
throw std::runtime_error("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(_u8L("loft failed"));
if (!loft.IsDone()) throw std::runtime_error("loft failed");
TopoDS_Shape s = loft.Shape();
if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no solid"));
if (s.IsNull()) throw std::runtime_error("loft produced no solid");
return s;
}
@@ -428,16 +427,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(_u8L("loft needs at least 2 profiles"));
throw std::runtime_error("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(_u8L("loft failed"));
if (!loft.IsDone()) throw std::runtime_error("loft failed");
TopoDS_Shape s = loft.Shape();
if (s.IsNull()) throw std::runtime_error(_u8L("loft produced no shape"));
if (s.IsNull()) throw std::runtime_error("loft produced no shape");
return s;
}
@@ -445,11 +444,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(_u8L("Pocket face failed"));
if (!fm.IsDone()) throw std::runtime_error("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(_u8L("Pocket cut failed"));
if (!cut.IsDone()) throw std::runtime_error("Pocket cut failed");
return cut.Shape();
}
@@ -930,7 +929,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(_u8L("sketch has no closed loop"));
if (wires.empty()) throw std::runtime_error("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,
@@ -945,7 +944,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(_u8L("sketch loop does not bound a face"));
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face");
return checked_profile_face(fm.Face());
}
@@ -957,7 +956,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(_u8L("sketch loop does not bound a face"));
if (!fm.IsDone()) throw std::runtime_error("sketch loop does not bound a face");
faces.push_back(fm.Face());
GProp_GProps props;
BRepGProp::SurfaceProperties(faces.back(), props);
@@ -983,10 +982,10 @@ TopoDS_Face SketchEngine::wires_to_face(const std::vector<TopoDS_Wire>& wires,
got = true;
break;
}
if (!got) throw std::runtime_error(_u8L("sketch loop does not bound a face"));
if (!got) throw std::runtime_error("sketch loop does not bound a face");
BRepClass_FaceClassifier fc(faces[outer], p, 1e-7);
if (fc.State() != TopAbs_IN)
throw std::runtime_error(_u8L("sketch has two disjoint regions; put each in its own sketch"));
throw std::runtime_error("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
@@ -996,7 +995,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(_u8L("sketch loop does not bound a face"));
if (!fm.IsDone()) throw std::runtime_error("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,7 +43,6 @@
#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,7 +28,6 @@
#include "FuzzySkin.hpp"
#include "libnoise/noise.h"
#include <functional>
// #define DEBUG_FUZZY
-8
View File
@@ -301,9 +301,6 @@ 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;
@@ -347,7 +344,6 @@ 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);
@@ -935,8 +931,6 @@ 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()) {
@@ -1436,8 +1430,6 @@ 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;
+127 -243
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; not needed if the pattern base starts here)
// 1. Start of wave (always 0.0)
// 2. Transition to upper "horizontal" part
// 3. Transition from upper "horizontal" part
// 4. Transition to lower "horizontal" part
@@ -73,11 +73,17 @@ 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;
std::vector<coordf_t> res = {0.};
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));
@@ -87,251 +93,113 @@ static coordf_t troctWave(coordf_t pos, coordf_t gridSize, coordf_t Zpos)
return(res);
}
// 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)
// 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)
{
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);
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]);
}
}
return lines;
points.push_back(gridLength);
return points;
}
// 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)
// 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)
{
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++){
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++){
coordf_t offset = troctWave(critPoints[pi], gridSize, Zpos);
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 + (offset * perpDir));
}
}
line.points.push_back((print_dir == 1) ? Point(dAdj, gridMax) : Point(gridMax, dAdj));
return line;
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;
}
// Generate a set of curves (array of array of 2d points) that describe a
// horizontal slice of a truncated regular octahedron.
static Polylines makeZigZag(coordf_t Zpos, coordf_t gridSize, coordf_t lengthX, coordf_t lengthY,
coordf_t spacing, size_t multiline_count)
static std::vector<Pointfs> makeActualGrid(coordf_t Zpos, coordf_t gridSize, size_t boundsX, size_t boundsY)
{
Polylines lines;
std::vector<Pointfs> points;
std::vector<coordf_t> critPoints = getCriticalPoints(Zpos, gridSize);
coordf_t zCycle = fmod(Zpos + gridSize/2, gridSize * 2.) / (gridSize * 2.);
bool printVert = zCycle < 0.5;
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 (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));
if (perpDir == 1)
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;
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());
}
}
return lines;
return points;
}
// 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
// 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)
static Polylines makeGrid(coordf_t z, coordf_t gridSize, coordf_t boundWidth, coordf_t boundHeight, bool fillEvenly)
{
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;
std::vector<Pointfs> polylines = makeActualGrid(z, gridSize, boundWidth, boundHeight);
Polylines result;
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());
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))));
}
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,
@@ -340,62 +208,78 @@ 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();
// 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;
}
// Expand the bounding box to avoid artifacts at the edges
coord_t expand = 5 * (scale_(this->spacing));
bb.offset(expand);
// 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);
// 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]
// 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
// = 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 (in unscaled units)
coordf_t gridSize = (scale_(this->spacing) *
((zScale + 1.) / 2.) * params.multiline / params.density);
coordf_t layerHeight = scale_(params.layer_height);
// 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)
coordf_t layersPerModule = floor((gridSize * 2) / (zScale * layerHeight) + 0.05);
// If a density over 42% is requested, set an exact layer pattern
if((params.density > 0.42) || (layersPerModule < 2)){
if(params.density > 0.42){ // exact layer pattern for >42% density
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 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.)));
// 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)));
// 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 + 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);
scale_(this->z) * zScale,
gridSize,
bb.size()(0),
bb.size()(1),
!params.dont_adjust);
// move pattern in place
for (Polyline &pl : polylines){
@@ -406,8 +290,8 @@ void Fill3DHoneycomb::_fill_surface_single(
smooth_polyline_corners(pl, params.smooth_factor, scaled<double>(params.resolution));
}
// Note: multiline fill adjustment is carried out in this code,
// rather than using the multiline_fill function
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
// clip pattern to boundaries, chain the clipped polylines
polylines = intersection_pl(std::move(polylines), to_polygons(expolygon));
-1
View File
@@ -118,7 +118,6 @@ 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,7 +119,6 @@
#endif
#include <Shiny/Shiny.h>
#include <stdio.h>
using namespace std::literals::string_view_literals;
+105 -83
View File
@@ -751,50 +751,6 @@ template<typename T>
}
}
namespace {
// Writes G-code to a file in blocks and records in lines_ends the file offset after every '\n'
class GCodeFileWriter
{
public:
GCodeFileWriter(FilePtr& out, const std::string& out_path, std::vector<size_t>& lines_ends, const char* error_message)
: m_out(out), m_out_path(out_path), m_lines_ends(lines_ends), m_error_message(error_message)
{}
~GCodeFileWriter() { assert(m_buffer.empty() || std::uncaught_exceptions() > 0); }
void append(std::string_view text)
{
const size_t text_pos = m_file_pos + m_buffer.size();
for (size_t i = text.find('\n'); i != std::string_view::npos; i = text.find('\n', i + 1))
m_lines_ends.emplace_back(text_pos + i + 1);
m_buffer += text;
if (m_buffer.size() >= GCodeProcessor::Output_Block_Size)
flush();
}
void flush()
{
if (m_buffer.empty())
return;
fwrite(m_buffer.data(), 1, m_buffer.size(), m_out.f);
if (ferror(m_out.f)) {
m_out.close();
boost::nowide::remove(m_out_path.c_str());
throw Slic3r::RuntimeError(m_error_message);
}
m_file_pos += m_buffer.size();
m_buffer.clear();
}
private:
FilePtr& m_out;
const std::string& m_out_path;
std::vector<size_t>& m_lines_ends;
const char* m_error_message;
std::string m_buffer;
size_t m_file_pos{0};
};
} // namespace
// Helper class to modify and export gcode to file
class ExportLines
{
@@ -809,6 +765,16 @@ public:
enum class EWriteType { BySize, ByTime };
private:
static void update_lines_ends_and_out_file_pos(const std::string& out_string, std::vector<size_t>& lines_ends, size_t* out_file_pos)
{
for (size_t i = 0; i < out_string.size(); ++i) {
if (out_string[i] == '\n')
lines_ends.emplace_back((out_file_pos != nullptr) ? *out_file_pos + i + 1 : i + 1);
}
if (out_file_pos != nullptr)
*out_file_pos += out_string.size();
}
struct LineData
{
std::string line;
@@ -848,14 +814,12 @@ private:
EWriteType m_write_type{EWriteType::BySize};
// Time machines containing g1 times cache
const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& m_machines;
// Output file writer
GCodeFileWriter& m_writer;
// Current time
std::array<float, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)> m_times{0.0f, 0.0f};
// Current size of the cache in bytes
// Current size in bytes
size_t m_size{0};
// gcode lines cache, used only when writing by time
// gcode lines cache
std::deque<LineData> m_lines;
size_t m_added_lines_counter{0};
// map of gcode line ids from original to final
@@ -863,16 +827,16 @@ private:
std::vector<std::pair<size_t, size_t>> m_gcode_lines_map;
size_t m_times_cache_id{0};
size_t m_out_file_pos{0};
public:
ExportLines(EWriteType type, const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines, GCodeFileWriter& writer)
ExportLines(EWriteType type, const std::array<GCodeProcessor::TimeMachine, static_cast<size_t>(PrintEstimatedStatistics::ETimeMode::Count)>& machines)
#ifndef NDEBUG
: m_statistics(*this)
, m_write_type(type)
, m_machines(machines)
, m_writer(writer){}
, m_machines(machines){}
#else
: m_write_type(type), m_machines(machines), m_writer(writer)
: m_write_type(type), m_machines(machines)
{}
#endif // NDEBUG
@@ -926,14 +890,11 @@ public:
if (line.empty())
return;
if (m_write_type == EWriteType::ByTime) {
m_lines.push_back({line, m_times});
m_lines.push_back({line, m_times});
#ifndef NDEBUG
m_statistics.add_line(line.length());
m_statistics.add_line(line.length());
#endif // NDEBUG
m_size += line.length();
} else
m_writer.append(line);
m_size += line.length();
++m_added_lines_counter;
if (!ignore_from_move) {
assert(!m_gcode_lines_map.empty());
@@ -999,35 +960,65 @@ public:
}
}
// when writing by time, pass the cached lines older than m_times[Normal] - backtrace_time to the writer
void write(float backtrace_time)
// write to file:
// m_write_type == EWriteType::ByTime - all lines older than m_time - backtrace_time
// m_write_type == EWriteType::BySize - all lines if current size is greater than 65535 bytes
void write(FilePtr& out, float backtrace_time, GCodeProcessorResult& result, const std::string& out_path)
{
if (m_write_type != EWriteType::ByTime)
if (m_lines.empty())
return;
while (!m_lines.empty() && m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) {
const LineData& data = m_lines.front();
m_writer.append(data.line);
m_size -= data.line.length();
m_lines.pop_front();
// collect lines to write into a single string
std::string out_string;
if (!m_lines.empty()) {
if (m_write_type == EWriteType::ByTime) {
while (m_lines.front().times[Normal] < m_times[Normal] - backtrace_time) {
const LineData& data = m_lines.front();
out_string += data.line;
m_size -= data.line.length();
m_lines.pop_front();
#ifndef NDEBUG
m_statistics.remove_line();
m_statistics.remove_line();
#endif // NDEBUG
}
} else {
if (m_size > 65535) {
while (!m_lines.empty()) {
out_string += m_lines.front().line;
m_lines.pop_front();
}
m_size = 0;
#ifndef NDEBUG
m_statistics.remove_all_lines();
#endif // NDEBUG
}
}
}
{
write_to_file(out, out_string, result, out_path);
update_lines_ends_and_out_file_pos(out_string, result.lines_ends, &m_out_file_pos);
}
}
// flush the current content of the cache and the writer to file
void flush()
// flush the current content of the cache to file
void flush(FilePtr& out, GCodeProcessorResult& result, const std::string& out_path)
{
// collect lines to flush into a single string
std::string out_string;
while (!m_lines.empty()) {
m_writer.append(m_lines.front().line);
out_string += m_lines.front().line;
m_lines.pop_front();
}
m_size = 0;
#ifndef NDEBUG
m_statistics.remove_all_lines();
#endif // NDEBUG
m_writer.flush();
{
write_to_file(out, out_string, result, out_path);
update_lines_ends_and_out_file_pos(out_string, result.lines_ends, &m_out_file_pos);
}
}
void synchronize_moves(GCodeProcessorResult& result) const
@@ -1060,7 +1051,20 @@ public:
size_t get_size() const { return m_size; }
void reserve(size_t lines_count) { m_gcode_lines_map.reserve(lines_count); }
private:
void write_to_file(FilePtr& out, const std::string& out_string, GCodeProcessorResult& result, const std::string& out_path)
{
if (!out_string.empty()) {
if (true) {
fwrite((const void*) out_string.c_str(), 1, out_string.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError("GCode processor post process export failed.\nIs the disk full?");
}
}
}
}
};
void GCodeProcessor::run_post_process()
@@ -1156,13 +1160,8 @@ void GCodeProcessor::run_post_process()
last_exported_stop[i] = time_in_minutes(m_time_processor.machines[i].time);
}
m_result.lines_ends.clear();
// m_result.lines_ends.emplace_back(std::vector<size_t>());
GCodeFileWriter writer(out, out_path, m_result.lines_ends, "GCode processor post process export failed.\nIs the disk full?");
ExportLines export_line(m_result.backtrace_enabled ? ExportLines::EWriteType::ByTime : ExportLines::EWriteType::BySize,
m_time_processor.machines, writer);
// The line map holds an entry for each line of the file, and the first pass counted them
export_line.reserve(m_line_id);
m_time_processor.machines);
// replace placeholder lines with the proper final value
// gcode_line is in/out parameter, to reduce expensive memory allocation
@@ -1536,6 +1535,9 @@ void GCodeProcessor::run_post_process()
}
};
m_result.lines_ends.clear();
// m_result.lines_ends.emplace_back(std::vector<size_t>());
// Orca: freshly collect SKIPPABLE ranges each post-process pass. The ranges are stored on the
// member (rather than a local) so the injection pass can consume them, hence the clear here to
// avoid stale ranges on re-invocation.
@@ -1800,7 +1802,7 @@ void GCodeProcessor::run_post_process()
if (!gcode_line.empty())
export_line.append_line(gcode_line);
export_line.write(1.1f * max_backtrace_time);
export_line.write(out, 1.1f * max_backtrace_time, m_result, out_path);
gcode_line.clear();
}
}
@@ -1842,7 +1844,7 @@ void GCodeProcessor::run_post_process()
}
}
export_line.flush();
export_line.flush(out, m_result, out_path);
out.close();
in.close();
@@ -1990,13 +1992,31 @@ void GCodeProcessor::run_second_pass_injection()
// The rewrite may shift byte positions (once the injector inserts lines), so rebuild lines_ends from scratch.
// With an empty map the scanned '\n' offsets reproduce the current lines_ends exactly.
m_result.lines_ends.clear();
GCodeFileWriter writer(out, out_path, m_result.lines_ends, "GCode processor pre-heat injection pass failed.\nIs the disk full?\n");
size_t out_file_pos = 0;
auto write_out = [&out, &out_path, this, &out_file_pos](std::string& str) {
if (str.empty())
return;
fwrite((const void*) str.c_str(), 1, str.length(), out.f);
if (ferror(out.f)) {
out.close();
boost::nowide::remove(out_path.c_str());
throw Slic3r::RuntimeError(std::string("GCode processor pre-heat injection pass failed.\nIs the disk full?\n"));
}
for (size_t i = 0; i < str.size(); ++i) {
if (str[i] == '\n')
m_result.lines_ends.emplace_back(out_file_pos + i + 1);
}
out_file_pos += str.size();
str.clear();
};
// Orca: read/split lines with EOL-preserving semantics (keep the original \r and \n bytes, and
// synthesize no trailing newline). This is required for the empty-map identity: normalizing every
// line ending to "\n" would not be byte-identical if the finished file used \r\n or lacked a
// final newline.
std::string gcode_line;
std::string export_buffer;
unsigned int line_id = 0;
auto op_it = inserted_operation_lines.begin();
std::vector<char> buffer(65536 * 10, 0);
@@ -2038,14 +2058,16 @@ void GCodeProcessor::run_second_pass_injection()
}
++op_it;
}
writer.append(gcode_line);
export_buffer += gcode_line;
gcode_line.clear();
if (export_buffer.length() >= 65536)
write_out(export_buffer);
}
}
if (eof)
break;
}
writer.flush();
write_out(export_buffer);
out.close();
in.close();
-3
View File
@@ -491,9 +491,6 @@ class Print;
static const std::string VFlush_End_Tag;
static const std::string External_Purge_Tag;
public:
// Size of the blocks the post-processing passes write the G-code in
static constexpr size_t Output_Block_Size = 65536;
// Orca: SKIPPABLE region tags, stored as static strings (the FLUSH idiom above) rather than
// a CustomETags/CustomTags array. Public so the emission sites (WipeTower / change_filament
// path) can reference them single-sourced.
-1
View File
@@ -12,7 +12,6 @@
#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,7 +12,6 @@
#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,7 +1209,6 @@ 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",
+6 -13
View File
@@ -4574,13 +4574,6 @@ 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");
@@ -5308,7 +5301,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_CONTEXT("X", "Axis");
def->label = L("X");
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;
@@ -5317,7 +5310,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("input_shaping_freq_y", coFloat);
def->label = L_CONTEXT("Y", "Axis");
def->label = L("Y");
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;
@@ -5326,7 +5319,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("input_shaping_damp_x", coFloat);
def->label = L_CONTEXT("X", "Axis");
def->label = L("X");
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;
@@ -5334,7 +5327,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_CONTEXT("Y", "Axis");
def->label = L("Y");
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;
@@ -8537,14 +8530,14 @@ void PrintConfigDef::init_sla_params()
def = this->add("display_pixels_x", coInt);
//def->full_label = L("");
def->label = L_CONTEXT("X", "Axis");
def->label = ("X");
//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 = L_CONTEXT("Y", "Axis");
def->label = ("Y");
//def->tooltip = L("");
def->min = 100;
def->set_default_value(new ConfigOptionInt(1440));
-1
View File
@@ -1358,7 +1358,6 @@ 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);
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();
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();
}),
bridge_components.end());
Polygons area_to_be_bridge = to_polygons(std::move(bridge_components));
area_to_be_bridge.end());
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;
const PrintConfig& print_config = object.print()->config();
auto m_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(print_config);
coordf_t bridging_height = region->region().bridging_height_avg(m_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,7 +22,6 @@
#include <tbb/task_arena.h>
#include "Thread.hpp"
#include <cstring>
namespace Slic3r {
-1
View File
@@ -119,7 +119,6 @@
// 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)
+7
View File
@@ -310,6 +310,7 @@ 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))
{
@@ -1332,11 +1333,17 @@ 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,6 +232,10 @@ 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
+3 -3
View File
@@ -1228,11 +1228,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\u2103." /* °C */), wxString(lim.name), lim.max_temp);
wxString msg = wxString::Format(_L("%s maximum drying temperature is %d°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\u2103." /* °C */), wxString(lim.name), lim.min_temp);
wxString msg = wxString::Format(_L("%s minimum drying temperature is %d°C."), wxString(lim.name), lim.min_temp);
warning_text += msg + "\n";
can_enable_button = false;
}
@@ -1260,7 +1260,7 @@ void AMSDryCtrWin::update_normal_description(DevAms* dev_ams)
auto limit_temperature = preset.value().filament_dev_ams_drying_heat_distortion_temperature;
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(u8"\u2103" /* °C */) + ")\n";
wxString::Format(wxT("%d"), static_cast<int>(limit_temperature)) + wxString::FromUTF8("°C)\n");
can_enable_button = false;
}
}
+2 -3
View File
@@ -264,8 +264,7 @@ 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);
// 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);
wxStaticText* credits_string = new wxStaticText(this, wxID_ANY, wxString::Format("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
@@ -331,7 +330,7 @@ AboutDialog::AboutDialog()
copyright_hor_sizer->Add(copyright_ver_sizer, 0, wxLEFT, FromDIP(20));
wxStaticText *html_text = new wxStaticText(this, wxID_ANY, _L("Copyright(C) 2026 OrcaSlicer Pte Ltd All Rights Reserved"), wxDefaultPosition, wxDefaultSize);
wxStaticText *html_text = new wxStaticText(this, wxID_ANY, "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);
+6 -7
View File
@@ -329,8 +329,7 @@ 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);
// 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");
const auto& shown_name = td_opt->ams_id == VIRTUAL_TRAY_MAIN_ID ? "Ext-R" : "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);
@@ -507,7 +506,7 @@ void AmsMapingPopup::update_ams_tips(MachineObject* obj)
}
if (obj && obj->GetFilaSwitch()->IsInstalled()) {
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.");
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.");
m_ams_tips_msg_panel->AddMessage(msg, "#FF6F00", "");
}
@@ -589,7 +588,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 ? _L("Ext-R") : _L("Ext-L"));
m_mapping_item->set_tray_index(tray_data[i].id == VIRTUAL_TRAY_MAIN_ID ? wxString("Ext-R") : wxString("Ext-L"));
} else {
m_mapping_item->set_tray_index(wxGetApp().transition_tridid(tray_data[i].id));
}
@@ -608,7 +607,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 are not supported since Filament Track Switch has been installed. If you want to use an external spool, please uninstall it.");
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
}
}
@@ -625,7 +624,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 are not supported since Filament Track Switch has been installed. If you want to use an external spool, please uninstall it.");
"External spools is not supported since Filament Track Switch has been installed. If you want to use external spool, please uninstall it.");
}
} else if (m_show_type != ShowType::RIGHT && m_show_type != ShowType::LEFT_AND_RIGHT) {
can_pick_the_item = false;
@@ -719,7 +718,7 @@ void AmsMapingPopup::add_ext_ams_mapping(TrayData tray_data, MappingItem* item)
});
}
item->set_tray_index(_L("Ext"));
item->set_tray_index("Ext");
}
} // namespace Slic3r::GUI
-3
View File
@@ -65,8 +65,6 @@
#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;
@@ -1154,7 +1152,6 @@ 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("Auxiliaries"), wxDefaultPosition,
: DPIDialog(parent, wxID_ANY, _L("Auxiliaryies"), wxDefaultPosition,
wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
{
m_aux_list = new AuxiliaryList(this);
@@ -60,9 +60,6 @@
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/PartPlate.hpp"
#include "slic3r/Utils/PrintHost.hpp"
#ifdef _WIN32
#include <excpt.h>
#endif
namespace Slic3r {
+41 -29
View File
@@ -8,6 +8,7 @@
#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"
@@ -91,7 +92,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_axes_at_bed_center(true); // triad at bed centre = modeling origin
m_canvas->set_design_canvas(true); // home-position bed, triad and CAD grid at the modeling origin
m_canvas->set_design_sketch_tool(&m_sketch_tool);
m_sketch_tool.on_commit = [this](const SketchProfile& prof, const SketchPlane& pl) {
@@ -190,8 +191,11 @@ 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.
// 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.
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 —
@@ -378,10 +382,14 @@ void DesignCanvas::reload(bool keep_view)
v->set_color(c);
}
} else if (obj_idx == 1) {
// 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.
// 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.
v->set_color(m_body_hidden ? ColorRGBA(0.40f, 0.82f, 1.0f, 1.0f) : ghost);
v->depth_bias = true;
}
}
@@ -406,6 +414,7 @@ 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);
@@ -415,6 +424,8 @@ 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);
@@ -470,13 +481,6 @@ 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)
@@ -550,6 +554,7 @@ 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
}
@@ -731,8 +736,6 @@ 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);
@@ -1025,6 +1028,11 @@ 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);
@@ -1047,15 +1055,18 @@ 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; 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).
// 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.
m_canvas_widget->Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
m_ctx_press = e.GetPosition();
m_ctx_travelled = false;
e.Skip(); // the canvas still needs the press to seed the orbit
// 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
});
m_canvas_widget->Bind(wxEVT_MOTION, [this](wxMouseEvent& e) {
if (e.RightIsDown()) {
@@ -1066,12 +1077,13 @@ 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;
// 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.
// Click, or navigation? A press that travelled panned or 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;
if (m_on_context_menu && !terminated && !inline_busy() && is_click) {
// 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) {
// 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
@@ -1167,9 +1179,6 @@ 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;
@@ -1204,8 +1213,11 @@ 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 = kStatusHudLeftInset * em;
const float left = m_canvas->get_canvas_toolbar_right() + margin;
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)
+7 -4
View File
@@ -3,6 +3,7 @@
#include <vector>
#include "libslic3r/Point.hpp"
#include "libslic3r/BuildVolume.hpp"
#include "libslic3r/Color.hpp"
#include <utility>
#include <wx/colour.h>
@@ -48,10 +49,11 @@ 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.
// 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).
void set_bodies(const std::vector<TriangleMesh>* body_meshes,
const std::vector<bool>& visible = {});
void clear_mesh();
void clear_mesh(); // no bodies: drops their volumes and the tool's edge lines, pick and selection
void set_preview_mesh(const TriangleMesh& mesh);
void clear_preview();
@@ -66,7 +68,6 @@ 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).
@@ -83,6 +84,8 @@ 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
@@ -157,7 +160,6 @@ 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.
@@ -234,6 +236,7 @@ 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
+93 -96
View File
@@ -13,9 +13,6 @@
#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 {
@@ -55,7 +52,7 @@ inline uint32_t offer_bit(OfferSel s) { return 1u << int(s); }
// nullptr -> kernel support exists, no GUI path yet (row shows disabled)
struct OfferVerb {
const char* id;
const char* name; // drawing-office word (L10); msgctxt "Design", translated at use
const char* name; // drawing-office word (L10); marked L(); 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;
@@ -102,107 +99,107 @@ static const bool kOfferRowFlat[] = {
};
static const OfferVerb kOfferVerbs[] = {
{"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")},
{"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")},
// 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_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")},
{"constrain", L("Constrain sketch"), 6, nullptr, "btn:constrain", L("Select a sketch to constrain it"), 0x00004000u, 0, 1, false, false, nullptr, "design_constrain", L("Add dimensions and relations (coincident, tangent, parallel...) to the selected sketch")},
{"sk_construct", L("Construction"), 6, "Q", "key:Q", nullptr, 0x000b8000u, 0, 0, false, true, nullptr, nullptr, L("Toggle construction: geometry that guides but is never built")},
{"sk_extend", L("Extend"), 7, "X", "key:X", nullptr, 0x000b0000u, 0, 0, false, true, nullptr, "design_extend", L("Extend — click a line/arc to extend it")},
{"sk_delete", L("Delete"), 7, "Del", "btn:sk_delete", nullptr, 0x000f0000u, 0, 0, false, true, nullptr, "design_delete", L("Delete the selected sketch entities")},
// Typing the defining number of the element you pointed at. Three rows rather than one so
// each names the quantity in the drawing-office word for THAT element; all three land on
// the same handler, because dimension_kind() already resolves the quantity from the
// selection. Without these, an element's own numbers were reachable only by arming the
// Dimension tool and re-picking geometry that was already selected.
{"sk_length", 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")},
{"sk_length", L("Length…"), 7, "V", "key:V", nullptr, 0x00010000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the length of this line")},
{"sk_radius", L("Radius / diameter…"), 7, "V", "key:V", nullptr, 0x00020000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the radius of this arc, or the diameter of this circle")},
{"sk_angdist", L("Angle / distance…"), 7, "V", "key:V", nullptr, 0x00080000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the angle between two lines, or the distance between the two picks")},
};
static const int kOfferVerbCount = 92;
File diff suppressed because it is too large Load Diff
+31 -6
View File
@@ -383,7 +383,16 @@ private:
void update_rib_gizmo(); // in-plane slab footprint + thickness handles (Rib card)
void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport
void refresh_mate_connectors(); // push connector frames so verse + polarity are visible
void update_reference_planes(); // persistent XY/XZ/YZ reference planes (fallback when no object)
void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, plane choice, Plane tool
void toggle_origin(); // the Origin row's eye (and Ctrl+Shift+O): keep the reference planes up
void toggle_bed(); // the Bed row's eye (and Ctrl+Shift+B): draw the printer bed, or not
void refresh_pinned(); // rebuild the Origin and Bed rows (their eyes and label colours)
// Sketch: open a session on the picked face or plane, or with none picked, put the planes up
// and wait for one (m_choosing_sketch_plane).
void start_sketch();
void start_sketch_on_target(bool offer_tools); // a session on sketch_plane_from_selection()
void end_sketch_plane_choice(); // leave the plane choice without a sketch; no-op outside it
bool sketch_plane_pick_live() const; // may a pick made now still open the sketch?
CadDocument m_doc;
@@ -413,7 +422,6 @@ private:
ScalableButton* m_commit_btn{nullptr}; // main face: runs the current commit mode
DropDown* m_commit_drop{nullptr}; // commit-mode choices, owned via m_flyout_keepalive
wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place)
CheckBox* m_show_bed{nullptr}; // view option: draw the printer bed + plate grid, or not
wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle)
wxSizer* m_box_sketch{nullptr};
wxSizer* m_box_extrude{nullptr};
@@ -571,8 +579,20 @@ private:
// deliberately no dropdown for it. e1p.
int m_ref_plane{0};
// m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY"
// from "nobody has chosen anything yet". This does.
// from "nobody has chosen anything yet". This does. A selection like a picked face: the sketch
// opened on it uses it up, and Esc or a click on nothing lets go of it.
bool m_plane_picked{false};
// Sketch was pressed with nothing to sketch on: the reference planes are up and the next
// reference plane or flat face clicked opens the sketch on it. Still Feature mode — sketch
// mode is entered only with the session (start_sketch_on_target).
bool m_choosing_sketch_plane{false};
// The Feature tree's Origin row: keeps the reference planes and their axes up
// (update_reference_planes). A view preference (AppConfig "design_show_origin"), not part of
// the recipe.
bool m_show_origin{false};
// The Feature tree's Bed row: draws the printer bed and its plate grid. A view preference
// (AppConfig "design_show_bed"), not part of the recipe.
bool m_show_bed{true};
ComboBox* m_shape{nullptr};
ComboBox* m_mode{nullptr};
wxSpinCtrlDouble* m_width{nullptr};
@@ -913,9 +933,11 @@ private:
std::function<void(double)> m_value_cont; // deferred apply, run on Confirm
std::function<void()> m_value_cancel; // optional action when the card is cancelled
// Feature tree: one row per feature, in feature order, with a per-type icon and the row's
// own Edit / Show-hide / Delete icons. Callers use row indices via
// tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows.
// Feature tree: the Origin and Bed rows, a fixed block of view switches that never scrolls
// and selects nothing, then, in a frame of its own below them, one row per feature, in feature
// order, with a per-type icon and the row's own Edit / Show-hide / Delete icons. Callers use
// row indices via tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows.
DesignRowList* m_pinned{nullptr};
DesignRowList* m_tree{nullptr};
// The faces the selected feature row made (CadDocument::faces_made_by), drawn as selected.
// Finding them replays the history, so they are kept per row and topology generation.
@@ -928,6 +950,7 @@ private:
void request_feature_highlight();
void update_feature_highlight();
bool deselect_rows(); // Esc / a click on nothing: drop the tree and Bodies rows
bool drop_plane_pick(); // ...and a picked reference plane; true if one was picked
// Bodies list under the feature tree: one row per body (parallel to m_doc.bodies). Selecting
// one highlights that body and makes it the target for the next op.
DesignRowList* m_parts{nullptr};
@@ -964,6 +987,8 @@ private:
void rebuild_disp_meshes(); // recompute m_disp_* from m_doc + m_body_xform
void feed_bodies(); // push m_disp_* + visibility/xform to the viewport
void on_move_body(); // start the move gizmo on the selected body
bool body_move_pending() const { return m_move_body >= 0; } // the Move button's session is open
void end_body_move(bool keep); // leave it: keep the dragged pose, or put the body back
void arm_transform_gizmo(); // arm the move gizmo on the Transform card's body (add mode only)
void on_set_body_color(); // Color tool: pick a per-body display colour override
void on_boolean_tool(); // Boolean (combine bodies): needs two solids, then opens the tool
+22 -12
View File
@@ -29,11 +29,19 @@ static constexpr int kCellDip = 20; // square action cell, hover chip included
static constexpr int kPadDip = 4; // row edges, and the gap before the action cells
static constexpr int kGapDip = 6; // type icon to label
DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
// wxVListBox defaults to wxBORDER_THEME; the sidebar's lists take a simple frame.
: wxVListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE)
DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable, long style)
// wxVListBox defaults to wxBORDER_THEME; `style` defaults to the simple frame the sidebar's
// lists take, and a list meant to sit unframed on its card passes wxBORDER_NONE.
: wxVListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style)
, m_max_visible(std::max(max_visible, 1))
, m_selectable(selectable)
{
// A non-selectable list takes no focus: from the mouse because it consumes the presses that
// would focus it, below, and from the keyboard through AcceptsFocus(). wxGTK reads that once,
// while the base class is being built, before the override exists, so it is told here.
if (!m_selectable)
SetCanFocus(false);
Bind(wxEVT_LISTBOX, [this](wxCommandEvent&) { if (on_select) on_select(); });
Bind(wxEVT_LISTBOX_DCLICK, [this](wxCommandEvent&) { if (on_activate) on_activate(); });
@@ -46,18 +54,20 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
m_pressed = Hit{};
e.Skip();
});
// A press on an action cell is remembered and skipped, so the list still selects the row.
// A press on an action cell is remembered and skipped, so the list still selects the row. A
// non-selectable list keeps every press from wxVListBox, which would select the row and take
// the focus.
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) {
const Hit h = hit_test(e.GetPosition());
m_pressed = h.cell >= 0 ? h : Hit{};
e.Skip();
if (m_selectable) e.Skip();
});
// The second press of a double-click on a cell is a press too, and is not skipped: the list
// would otherwise turn it into a row double-click, so a quick double toggle of the eye would
// also open the feature for editing.
Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& e) {
const Hit h = hit_test(e.GetPosition());
if (h.cell < 0) { m_pressed = Hit{}; e.Skip(); return; }
if (h.cell < 0) { m_pressed = Hit{}; if (m_selectable) e.Skip(); return; }
m_pressed = h;
});
Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
@@ -69,9 +79,9 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
const int id = m_rows[pressed.row].actions[pressed.cell].id;
// After the click has finished dispatching: the action may rebuild these rows. The row
// must still exist and still be the selected one, or the click is dropped rather than
// applied to whatever row took its place.
// applied to whatever row took its place. A non-selectable list has no selected row.
CallAfter([this, row = pressed.row, id] {
if (row < int(GetItemCount()) && row == GetSelection() && on_action)
if (row < int(GetItemCount()) && (!m_selectable || row == GetSelection()) && on_action)
on_action(row, id);
});
});
@@ -79,7 +89,7 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
// press suppresses the wxEVT_CONTEXT_MENU that follows it.
Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
const Hit h = hit_test(e.GetPosition());
if (h.row != wxNOT_FOUND) select(h.row);
if (m_selectable && h.row != wxNOT_FOUND) select(h.row);
e.Skip();
});
Bind(wxEVT_CONTEXT_MENU, [this](wxContextMenuEvent& e) {
@@ -95,7 +105,7 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
return;
// On MSW the menu comes with the button's release, and the pointer may have moved to
// another row since the press selected one: the menu is for the row it opens over.
select(row);
if (m_selectable) select(row);
if (on_menu) on_menu(row, screen);
});
// wxVListBox takes every wheel event, even with nothing to scroll, so a short list would stop
@@ -254,7 +264,7 @@ void DesignRowList::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) con
wxColour bg;
if (IsSelected(n))
bg = StateColor::darkModeColorFor(wxColour("#BFE1DE"));
else if (int(n) == m_hover.row)
else if (m_selectable && int(n) == m_hover.row) // a non-selectable row lights only its icons
bg = StateColor::darkModeColorFor(wxColour("#E5F0EE"));
if (!bg.IsOk())
return; // the list already cleared to its background colour
@@ -314,7 +324,7 @@ DesignRowList::Hit DesignRowList::hit_test(const wxPoint& pt) const
const wxRect rect = GetItemRect(h.row);
const auto& acts = m_rows[h.row].actions;
for (size_t i = 0; i < acts.size(); ++i)
if (cell_rect(rect, acts.size(), i).Contains(pt)) {
if (!acts[i].icon.empty() && cell_rect(rect, acts.size(), i).Contains(pt)) {
h.cell = int(i);
break;
}
+16 -5
View File
@@ -29,12 +29,16 @@ namespace Slic3r { namespace GUI {
// Selection follows wxTreeCtrl's contract, which DesignPanel's mutual exclusion between the two
// lists relies on: on_select runs on every change, whether the user or select() made it, and
// selecting the row that is already selected changes nothing and notifies nobody.
//
// A list built non-selectable is a row of switches, not of objects: a click never selects a row,
// the pointer highlights only the action icon under it, and the list never takes the focus. Its
// action icons and its context menu are the only things that answer.
class DesignRowList : public wxVListBox
{
public:
struct Action {
int id; // the owner's code for it, handed back to on_action
std::string icon; // icon name, e.g. "design_eye"
std::string icon; // icon name, e.g. "design_eye"; empty for a blank cell that only holds a column
wxString tip;
};
struct Row {
@@ -50,7 +54,8 @@ public:
static constexpr const char* hover_chip = "#D4D4D4";
// The list is as tall as its rows, at least one and at most `max_visible`; past that it scrolls.
DesignRowList(wxWindow* parent, int max_visible);
// `style` is its frame: wxBORDER_NONE for a list that sits unframed on its card.
DesignRowList(wxWindow* parent, int max_visible, bool selectable = true, long style = wxBORDER_SIMPLE);
// Replace every row. Clears the selection without notifying and cancels a rename in progress;
// the owner re-selects the row it keeps, which notifies.
@@ -67,15 +72,20 @@ public:
std::function<void()> on_select; // the selection changed
std::function<void()> on_activate; // the selected row was double-clicked
// A row's action icon was clicked. Runs after the click has finished dispatching, and only
// while that row is still the selected one; the click itself selected it.
// A row's action icon was clicked. Runs after the click has finished dispatching, while the
// row still exists and — in a selectable list, where the click itself selected it — is still
// the selected one.
std::function<void(int row, int id)> on_action;
// Context menu on a row, at a screen position; the row under the pointer is selected first.
// Context menu on a row, at a screen position; the row under the pointer is selected first,
// unless the list is non-selectable. Pop the menu from inside the call: wxGTK sends it on the
// right press, and a menu deferred past that event closes on the button's release.
std::function<void(int row, const wxPoint& screen)> on_menu;
// The editor committed `name` for `row`, trimmed and never empty (an empty commit cancels).
// Runs after the editor's own events have finished, so the owner may rebuild the rows here.
std::function<void(int row, const wxString& name)> on_rename;
bool AcceptsFocus() const override { return m_selectable && wxVListBox::AcceptsFocus(); }
protected:
void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const override;
void OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const override;
@@ -103,6 +113,7 @@ private:
std::vector<Row> m_rows;
std::map<std::string, ScalableBitmap> m_icons;
int m_max_visible;
bool m_selectable;
int m_row_h{0}; // every row's height, from measure_row()
Hit m_hover;
Hit m_pressed; // action cell under the last left press
File diff suppressed because it is too large Load Diff

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