Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c2e26fa3d | ||
|
|
e328b60992 | ||
|
|
afa9252b59 | ||
|
|
56ebde968a | ||
|
|
840d44b611 | ||
|
|
34696f5651 | ||
|
|
c300fa1e8d | ||
|
|
cd30b196b2 | ||
|
|
b67269ccc4 |
@@ -61,7 +61,6 @@ CheckOptions:
|
||||
termios\.h;
|
||||
[/\\](um|shared)[/\\].*;
|
||||
sal\.h;
|
||||
tchar\.h;
|
||||
clipper[/\\]clipper\.hpp;
|
||||
png(lib)?conf\.h;
|
||||
mcut[/\\]platform\.h;
|
||||
|
||||
@@ -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/."
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -154,15 +154,18 @@ rather than to its end points: a press that wandered past the budget at any mome
|
||||
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
|
||||
canvas (a sibling of the canvas, not a child over it — on GTK a child window over a
|
||||
@@ -189,6 +192,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
|
||||
@@ -234,6 +246,15 @@ The tab is a page of Orca's main window and answers to the same settings as Prep
|
||||
`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
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 +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 |
@@ -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,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,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,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,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,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 |
@@ -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 |
@@ -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)); });
|
||||
}
|
||||
|
||||
@@ -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]),
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -36,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$")
|
||||
@@ -75,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:
|
||||
@@ -102,10 +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, so parse_diff sees its b/ prefix.
|
||||
diff = subprocess.run(["git", "-c", "core.quotePath=false", "diff", "-U0", "--no-color", "--no-ext-diff",
|
||||
"--diff-filter=AMR", merge_base],
|
||||
check=True, capture_output=True, **UTF8).stdout
|
||||
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)}
|
||||
|
||||
|
||||
@@ -123,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)
|
||||
@@ -185,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.
|
||||
@@ -241,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()
|
||||
|
||||
@@ -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 ------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -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,37 +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])
|
||||
self.assertEqual(call.kwargs["encoding"], "utf-8")
|
||||
self.assertEqual(files["src/libslic3r/Über.cpp"].lines, [[2, 2]])
|
||||
|
||||
def test_clang_tidy_and_git_show_output_is_decoded_as_utf8(self):
|
||||
_, call, _ = self.run_patched(clang_tidy_diff.run_clang_tidy, "clang-tidy", "build",
|
||||
"src/libslic3r/Color.cpp", None, [])
|
||||
self.assertEqual(call.kwargs["encoding"], "utf-8")
|
||||
_, call, _ = self.run_patched(clang_tidy_diff.errors_alone_at, "base", "clang-tidy", "build",
|
||||
"src/libslic3r/Color.hpp", returncode=128)
|
||||
self.assertEqual(call.kwargs["encoding"], "utf-8")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include "StackWalker.h"
|
||||
#include <eh.h>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
class CBaseException : public CStackWalker
|
||||
{
|
||||
|
||||
@@ -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" )
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include <Windows.h>
|
||||
#include <tchar.h>
|
||||
#include <vector>
|
||||
#include <cstddef>
|
||||
|
||||
namespace textconv_helper
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ¶ms,
|
||||
@@ -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));
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -119,7 +119,6 @@
|
||||
#endif
|
||||
|
||||
#include <Shiny/Shiny.h>
|
||||
#include <stdio.h>
|
||||
|
||||
|
||||
using namespace std::literals::string_view_literals;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include <tbb/task_arena.h>
|
||||
|
||||
#include "Thread.hpp"
|
||||
#include <cstring>
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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);
|
||||
@@ -550,6 +561,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
|
||||
}
|
||||
|
||||
@@ -1047,15 +1059,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 +1081,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 +1183,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 +1217,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)
|
||||
|
||||
@@ -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();
|
||||
@@ -83,6 +85,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
|
||||
|
||||
@@ -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("Click a face or a reference plane in the viewport, then a sketch tool"), 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", L("Click a face or a reference plane, then pick a drawing tool")},
|
||||
{"extrude", L("Extrude"), 1, "Shift+E", "key:S+E", L("Create a sketch, or pick a solid face, first"), 0x00004002u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch profile, or push/pull a picked face")},
|
||||
{"revolve", L("Revolve"), 1, "Shift+R", "key:S+R", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a profile about an axis")},
|
||||
{"sweep", L("Sweep"), 1, "Shift+W", "key:S+W", L("Create a profile sketch to sweep first"), 0x00004000u, 0, 2, false, false, nullptr, "design_sweep", L("Sweep a profile along a path")},
|
||||
{"loft", L("Loft"), 1, "Shift+L", "key:S+L", L("Create at least two profile sketches to loft"), 0x00004000u, 0, 2, false, false, nullptr, "design_loft", L("Loft (skin) between two or more profiles")},
|
||||
{"thicken", L("Thicken"), 1, nullptr, "fly:material#4", L("Thicken needs a solid body — add or import one first"), 0x0000000au, 1, 0, false, false, nullptr, "design_thicken", L("Offset a solid face into a thin plate (new body)")},
|
||||
{"rib", L("Rib"), 1, "R", "fly:material#5", L("Rib needs a solid body — add or import one first"), 0x00010000u, 1, 0, false, false, nullptr, "design_rib", L("Grow a thin wall from an open sketch line, fused to a body")},
|
||||
{"boolean", L("Join"), 1, "Shift+B", "btn:bool#0", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Fuse the tool body into the target — one solid, no seam")},
|
||||
{"bool_subtract", L("Subtract"), 1, nullptr, "btn:bool#1", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Cut the tool body out of the target")},
|
||||
{"bool_intersect", L("Intersect"), 1, nullptr, "btn:bool#2", L("Boolean needs two bodies — add or import a second one"), 0x00000200u, 2, 0, false, false, nullptr, "design_boolean", L("Keep only where the two bodies overlap")},
|
||||
{"surf_extrude", L("Surface Extrude"), 1, "Shift+G", "key:S+G", L("Create a sketch first"), 0x00004000u, 0, 0, false, false, nullptr, "design_extrude", L("Extrude a sketch into a sheet body (no end caps)")},
|
||||
{"surf_revolve", L("Surface Revolve"), 1, nullptr, "fly:surface#1", L("Create a sketch profile to revolve first"), 0x00004000u, 0, 0, false, false, nullptr, "design_revolve", L("Revolve a sketch profile into a sheet body")},
|
||||
{"surf_loft", L("Surface Loft"), 1, nullptr, "fly:surface#2", L("Create at least two profile sketches to loft"), 0x00004000u, 0, 2, false, false, nullptr, "design_loft", L("Loft (skin) between 2+ profiles, open (no end caps)")},
|
||||
{"surf_fill", L("Surface Fill"), 1, nullptr, "fly:surface#3", L("Create a closed sketch first"), 0x00004000u, 0, 0, false, false, nullptr, "design_surface", L("Fill a sketch boundary with a smooth face")},
|
||||
{"thicken_surf", L("Thicken Surface"), 1, nullptr, "fly:surface#5", L("Thicken Surface needs a surface body — make one with a Surface tool first"), 0x00000100u, 0, 0, true, false, nullptr, "design_thicken", L("Thicken a sheet body into a solid")},
|
||||
{"hole", L("Hole"), 2, "Shift+H", "key:S+H", L("Pick a face or a plane to drill into"), 0x00000402u, 1, 0, false, false, nullptr, "design_hole", L("Drill a hole, centerd on a picked face or placed on a plane")},
|
||||
{"thread", L("Thread"), 2, "Shift+T", "key:S+T", L("Pick a cylindrical surface (bore / outer) or a circular edge for a thread"), 0x00000024u, 1, 0, false, false, nullptr, "design_thread", L("Thread a cylindrical surface (inner bore / outer) or a circular edge")},
|
||||
{"shell", L("Shell"), 2, "Shift+K", "key:S+K", L("Shell needs a solid body — add or import one first"), 0x00000082u, 1, 0, false, false, nullptr, "design_shell", L("Hollow the body to a wall thickness, opening a picked face")},
|
||||
{"cut", L("Cut"), 2, "Shift+X", "key:S+X", L("Cut needs a solid body — add or import one first"), 0x000004feu, 1, 0, false, false, nullptr, "design_cut", L("Trim the body with a plane — drag the offset arrow; keep one half or both")},
|
||||
{"split", L("Split"), 2, nullptr, nullptr, L("Split needs a solid body — add or import one first"), 0x000000feu, 1, 0, false, false, nullptr, nullptr, L("Split the body along a picked face into two solids")},
|
||||
{"fillet", L("Fillet"), 3, "Shift+F", "btn:dress#0", L("Pick an edge to round"), 0x000000b2u, 1, 0, false, false, nullptr, "design_filletedge", L("Pick an edge, then drag the radius arrow or type it")},
|
||||
{"chamfer", L("Chamfer"), 3, nullptr, "btn:dress#1", L("Pick an edge to bevel"), 0x000000b2u, 1, 0, false, false, nullptr, "design_chamfer", L("Pick an edge, then drag the distance arrow or type it")},
|
||||
{"draft", L("Draft"), 3, "Shift+D", "key:S+D", L("Pick a face to taper"), 0x0000000au, 1, 0, false, false, nullptr, "design_draft", L("Tilt a picked face by a draft angle")},
|
||||
{"surf_offset", L("Surface Offset"), 3, nullptr, "fly:surface#4", L("Surface Offset needs a surface body — make one with a Surface tool first"), 0x00000100u, 0, 0, true, false, nullptr, "design_offset", L("Offset a sheet body's shell by a signed distance")},
|
||||
{"pattern", L("Linear pattern"), 4, "Shift+N", "btn:pat#0", L("Pattern needs a solid body — add or import one first"), 0x00006082u, 1, 0, false, false, nullptr, "design_array", L("Repeat the body along a direction — drag the spacing, set the count")},
|
||||
{"pattern_circular", L("Circular pattern"), 4, nullptr, "btn:pat#1", L("Pattern needs a solid body — add or import one first"), 0x00006082u, 1, 0, false, false, nullptr, "design_polararray", L("Repeat the body around an axis — set the count and sweep")},
|
||||
{"mirror", L("Mirror"), 4, "Shift+Z", "key:S+Z", L("Mirror needs a body — add or import one first"), 0x000004feu, 1, 0, false, false, nullptr, "design_mirror", L("Reflect a body about a plane")},
|
||||
{"pat_curve", L("Pattern on Curve"), 4, nullptr, nullptr, L("Pattern on Curve needs a body and a curve"), 0x00000090u, 1, 0, false, false, nullptr, nullptr, L("Repeat the body along a picked curve")},
|
||||
{"transform", L("Move"), 5, "Shift+Y", "key:S+Y", L("Transform needs a body — add or import one first"), 0x000021feu, 1, 0, false, false, nullptr, "design_move", L("Move and/or rotate an existing body")},
|
||||
{"mate", L("Mate"), 5, nullptr, "fly:placement#2", L("Mate needs two coordinate systems — create them first"), 0x00001202u, 2, 0, false, false, nullptr, "design_c_coincident", L("Assembly: align two coordinate systems (fastened, planar, revolute, slider, cylindrical)")},
|
||||
{"align", L("Align to"), 5, nullptr, nullptr, L("Align needs a body — add or import one first"), 0x00000002u, 1, 0, false, false, nullptr, nullptr, L("Align the body to a picked face or plane")},
|
||||
{"plane", L("Plane"), 6, "Shift+P", "key:S+P", nullptr, 0x00000453u, 0, 0, false, false, nullptr, "design_plane", L("Reference plane (offset / tilt / midplane / tangent / two edges / coincident)")},
|
||||
{"axis", L("Axis"), 6, "Shift+A", "key:S+A", nullptr, 0x00000057u, 0, 0, false, false, nullptr, "design_line", L("Datum axis (two points, face normal, cylinder centerline, two planes, along edge)")},
|
||||
{"coordsys_v", L("Coordinate system"), 6, "Shift+C", "key:S+C", nullptr, 0x00000043u, 0, 0, false, false, nullptr, "design_point", L("Datum coordinate system (world point, or face + direction edge)")},
|
||||
{"helix", L("Helix"), 6, nullptr, "fly:plane#3", nullptr, 0x00000405u, 0, 0, false, false, nullptr, "design_thread", L("Helical curve (spring path) — use as a sweep path for coils / springs / augers")},
|
||||
{"project", L("Project"), 6, nullptr, "fly:plane#4", L("Project needs a body — add or import one first"), 0x00000482u, 1, 0, false, false, nullptr, "design_sketch", L("Project body edges onto a plane as sketch entities")},
|
||||
{"measure", L("Measure"), 6, nullptr, nullptr, nullptr, 0x000b03feu, 0, 0, false, false, nullptr, nullptr, L("Measure between the picked points, edges or faces")},
|
||||
{"mass_props", L("Volume and area"), 6, nullptr, "btn:mass", nullptr, 0x000000feu, 1, 0, false, false, nullptr, "info", L("Report the volume and surface area of the selected body")},
|
||||
{"interference", L("Interference"), 6, nullptr, "btn:interference", L("Interference needs at least two bodies"), 0x00000280u, 2, 0, false, false, nullptr, nullptr, L("Check whether two bodies overlap — reports, changes nothing")},
|
||||
{"edit_feature", L("Edit"), 7, nullptr, "btn:edit", nullptr, 0x00007d8eu, 0, 0, false, false, nullptr, "design_edit", L("Reopen the selected feature to change what it was made from")},
|
||||
{"rename", L("Rename…"), 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;
|
||||
|
||||
|
||||
@@ -104,12 +104,20 @@
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Plater.hpp"
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "slic3r/GUI/GUI_ObjectList.hpp"
|
||||
#include "slic3r/GUI/Shortcuts.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
|
||||
// English-only pin for the Design tab (see design-ux-contract): one lever
|
||||
// de-translates this whole TU so our strings never half-translate against the host's
|
||||
// localized chrome. Host UI still follows the app locale; only this tab is pinned EN.
|
||||
// GOTCHA: every _L(...) in this file must take a STRING LITERAL (FromUTF8 wants const char*).
|
||||
#ifdef _L
|
||||
#undef _L
|
||||
#endif
|
||||
#define _L(s) wxString::FromUTF8(s)
|
||||
|
||||
namespace Slic3r { namespace GUI {
|
||||
|
||||
// Mesh -> B-rep import defaults (see GeometryEngine::mesh_to_brep).
|
||||
@@ -489,7 +497,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
}
|
||||
m_viewport->set_sketch_construction(m_construction->GetValue());
|
||||
set_status(StatusKind::Info, m_sketch_on.IsEmpty() ? hint
|
||||
: wxString::Format(_L("%s · plane: %s"), hint, m_sketch_on));
|
||||
: wxString::Format(_L("%s · on %s"), hint, m_sketch_on));
|
||||
};
|
||||
|
||||
// Sketch-tool shortcuts (single letters, active only while a sketch is open). Family tools
|
||||
@@ -722,11 +730,11 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
wxString where;
|
||||
const bool have_plane = sketch_plane_target(where);
|
||||
set_status(StatusKind::Info, have_plane
|
||||
? wxString::Format(_L("Sketch plane: %s — pick a tool"), where)
|
||||
? wxString::Format(_L("Sketching on %s — pick a tool"), where)
|
||||
: _L("Click a face or a reference plane in the viewport, then a sketch tool"));
|
||||
if (m_sketch_hint) { // the card must agree with the status line, not argue with it
|
||||
m_sketch_hint->SetLabel(have_plane
|
||||
? wxString::Format(_L("Sketch plane: %s.\nPick a tool, or press Menu for the list."), where)
|
||||
? wxString::Format(_L("Drawing on %s.\nPick a tool, or press Menu for the list."), where)
|
||||
: _L("Click a face or a reference plane, then a sketch tool."));
|
||||
m_sketch_hint->Refresh();
|
||||
m_cards->Layout();
|
||||
@@ -743,7 +751,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// Add material: every feature that grows new solid material — from a profile
|
||||
// (extrude/revolve/sweep/loft), from a face (thicken) or from a line (rib).
|
||||
feat_dropdown("material", "design_extrude", _L("Add material (extrude / revolve / sweep / loft / thicken / rib)"), {
|
||||
{"design_extrude", _L_CONTEXT("Extrude", "Design"), _L("Extrude a sketch profile, or push/pull a picked face"),
|
||||
{"design_extrude", _L("Extrude"), _L("Extrude a sketch profile, or push/pull a picked face"),
|
||||
[this] {
|
||||
// Onshape push/pull: an explicitly picked solid face (Face-level cycle, no loop
|
||||
// selected) is extruded as the profile — this takes priority over re-extruding an
|
||||
@@ -824,7 +832,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_sel_solid_face = -1;
|
||||
open_tool(Tool::Thicken); // syncs m_thicken_face_label from m_sel_solid_face
|
||||
}, 0},
|
||||
{"design_rib", _L_CONTEXT("Rib", "Design"), _L("Grow a thin wall from an open sketch line, fused to a body"),
|
||||
{"design_rib", _L("Rib"), _L("Grow a thin wall from an open sketch line, fused to a body"),
|
||||
[this] {
|
||||
if (m_doc.bodies.empty()) {
|
||||
set_status(StatusKind::Error, _L("Rib needs a solid body — add or import one first"));
|
||||
@@ -864,7 +872,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// shortcut at all, which also made its card unreachable to the rig.
|
||||
});
|
||||
|
||||
auto* b_pattern = icon_btn("design_pattern", _L_CONTEXT("Pattern", "Design"));
|
||||
auto* b_pattern = icon_btn("design_pattern", _L("Pattern"));
|
||||
std::function<void()> act_pattern = [this] {
|
||||
// Pattern replicates an existing body — needs at least one solid.
|
||||
if (m_doc.bodies.empty()) {
|
||||
@@ -970,7 +978,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
}, 0},
|
||||
// Project belongs here, not in Dress-up: it consumes a body but PRODUCES sketch
|
||||
// geometry, so it is reference/curve creation like the four above, not a finishing op.
|
||||
{"design_sketch", _L_CONTEXT("Project", "Design"), _L("Project body edges onto a plane as sketch entities"),
|
||||
{"design_sketch", _L("Project"), _L("Project body edges onto a plane as sketch entities"),
|
||||
[this] {
|
||||
if (m_doc.bodies.empty()) {
|
||||
set_status(StatusKind::Error, _L("Project needs a body — add or import one first"));
|
||||
@@ -1130,7 +1138,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
[this] { open_tool(Tool::Dressup); }, SHIFT('F')},
|
||||
{"design_draft", _L("Draft (taper a face)"), _L("Tilt a picked face by a draft angle"),
|
||||
[this] { open_tool(Tool::Draft); }, SHIFT('D')},
|
||||
{"design_shell", _L_CONTEXT("Shell", "Design"), _L("Hollow the body to a wall thickness, opening a picked face"),
|
||||
{"design_shell", _L("Shell"), _L("Hollow the body to a wall thickness, opening a picked face"),
|
||||
[this] { open_tool(Tool::Shell); }, SHIFT('K')},
|
||||
{"design_delete", _L("Delete Face"), _L("Remove faces from a body and heal the solid"),
|
||||
[this] {
|
||||
@@ -1156,7 +1164,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// Hole / Thread — drilling into a solid (both face-aware)
|
||||
feat_dropdown("hole", "design_hole", _L("Hole / thread"), {
|
||||
{"design_hole", _L("Hole"), _L("Drill a hole, centered on a picked face or placed on a plane"),
|
||||
{"design_hole", _L("Hole"), _L("Drill a hole, centerd on a picked face or placed on a plane"),
|
||||
[this] {
|
||||
// #2: drill on the picked solid face, centred on it (origin = face centroid,
|
||||
// normal = inward). Otherwise fall back to the plane dropdown. m_hole_x/y then
|
||||
@@ -1533,9 +1541,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
if (n > 0) {
|
||||
m_construction->SetValue(!m_construction->GetValue()); // the mode did not move
|
||||
set_status(StatusKind::Info, wxString::Format(
|
||||
_L_PLURAL("Converted %d entity between construction and real geometry",
|
||||
"Converted %d entities between construction and real geometry", n),
|
||||
n));
|
||||
_L("Converted %d entit%s between construction and real geometry"),
|
||||
n, n == 1 ? "y" : "ies"));
|
||||
return;
|
||||
}
|
||||
m_viewport->set_sketch_construction(m_construction->GetValue()); });
|
||||
@@ -1566,26 +1573,26 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
b->Bind(wxEVT_BUTTON, [this, type](wxCommandEvent&) { apply_constraint(type); });
|
||||
cadd(b);
|
||||
};
|
||||
cbtn("design_c_horizontal", _L_CONTEXT("Horizontal", "Sketch Constraint"), SketchConstraintType::Horizontal);
|
||||
cbtn("design_c_vertical", _L_CONTEXT("Vertical", "Sketch Constraint"), SketchConstraintType::Vertical);
|
||||
cbtn("design_c_parallel", _L_CONTEXT("Parallel", "Sketch Constraint"), SketchConstraintType::Parallel);
|
||||
cbtn("design_c_perpendicular", _L_CONTEXT("Perpendicular", "Sketch Constraint"), SketchConstraintType::Perpendicular);
|
||||
cbtn("design_c_coincident", _L_CONTEXT("Coincident", "Sketch Constraint"), SketchConstraintType::Coincident);
|
||||
cbtn("design_c_equal", _L_CONTEXT("Equal length", "Sketch Constraint"), SketchConstraintType::EqualLength);
|
||||
cbtn("design_c_equal_radius", _L_CONTEXT("Equal radius", "Sketch Constraint"), SketchConstraintType::EqualRadius);
|
||||
cbtn("design_c_collinear", _L_CONTEXT("Collinear", "Sketch Constraint"), SketchConstraintType::Collinear);
|
||||
cbtn("design_c_concentric", _L_CONTEXT("Concentric", "Sketch Constraint"), SketchConstraintType::Concentric);
|
||||
cbtn("design_c_tangent", _L_CONTEXT("Tangent", "Sketch Constraint"), SketchConstraintType::Tangent);
|
||||
cbtn("design_c_midpoint", _L_CONTEXT("Midpoint", "Sketch Constraint"), SketchConstraintType::Midpoint);
|
||||
cbtn("design_c_symmetric", _L_CONTEXT("Symmetric", "Sketch Constraint"), SketchConstraintType::Symmetric);
|
||||
cbtn("design_c_sym_v", _L_CONTEXT("Symmetric about the vertical axis", "Sketch Constraint"), SketchConstraintType::SymmetricAboutY);
|
||||
cbtn("design_c_sym_h", _L_CONTEXT("Symmetric about the horizontal axis", "Sketch Constraint"), SketchConstraintType::SymmetricAboutX);
|
||||
cbtn("design_c_angle", _L_CONTEXT("Angle", "Sketch Constraint"), SketchConstraintType::Angle);
|
||||
cbtn("design_c_radius", _L_CONTEXT("Radius", "Sketch Constraint"), SketchConstraintType::Radius);
|
||||
cbtn("design_c_diameter", _L_CONTEXT("Diameter", "Sketch Constraint"), SketchConstraintType::Diameter);
|
||||
cbtn("design_c_fix", _L_CONTEXT("Fix point (anchor in place)", "Sketch Constraint"), SketchConstraintType::Fix);
|
||||
cbtn("design_c_dist_x", _L_CONTEXT("Horizontal distance", "Sketch Constraint"), SketchConstraintType::DistanceX);
|
||||
cbtn("design_c_dist_y", _L_CONTEXT("Vertical distance", "Sketch Constraint"), SketchConstraintType::DistanceY);
|
||||
cbtn("design_c_horizontal", _L("Horizontal"), SketchConstraintType::Horizontal);
|
||||
cbtn("design_c_vertical", _L("Vertical"), SketchConstraintType::Vertical);
|
||||
cbtn("design_c_parallel", _L("Parallel"), SketchConstraintType::Parallel);
|
||||
cbtn("design_c_perpendicular", _L("Perpendicular"), SketchConstraintType::Perpendicular);
|
||||
cbtn("design_c_coincident", _L("Coincident"), SketchConstraintType::Coincident);
|
||||
cbtn("design_c_equal", _L("Equal length"), SketchConstraintType::EqualLength);
|
||||
cbtn("design_c_equal_radius", _L("Equal radius"), SketchConstraintType::EqualRadius);
|
||||
cbtn("design_c_collinear", _L("Collinear"), SketchConstraintType::Collinear);
|
||||
cbtn("design_c_concentric", _L("Concentric"), SketchConstraintType::Concentric);
|
||||
cbtn("design_c_tangent", _L("Tangent"), SketchConstraintType::Tangent);
|
||||
cbtn("design_c_midpoint", _L("Midpoint"), SketchConstraintType::Midpoint);
|
||||
cbtn("design_c_symmetric", _L("Symmetric"), SketchConstraintType::Symmetric);
|
||||
cbtn("design_c_sym_v", _L("Symmetric about the vertical axis"), SketchConstraintType::SymmetricAboutY);
|
||||
cbtn("design_c_sym_h", _L("Symmetric about the horizontal axis"), SketchConstraintType::SymmetricAboutX);
|
||||
cbtn("design_c_angle", _L("Angle"), SketchConstraintType::Angle);
|
||||
cbtn("design_c_radius", _L("Radius"), SketchConstraintType::Radius);
|
||||
cbtn("design_c_diameter", _L("Diameter"), SketchConstraintType::Diameter);
|
||||
cbtn("design_c_fix", _L("Fix point (anchor in place)"), SketchConstraintType::Fix);
|
||||
cbtn("design_c_dist_x", _L("Horizontal distance"), SketchConstraintType::DistanceX);
|
||||
cbtn("design_c_dist_y", _L("Vertical distance"), SketchConstraintType::DistanceY);
|
||||
// Trim/Extend are now standalone SKETCH scissors (Mode::Trim/Extend) in the sketch
|
||||
// toolbar, NOT Constrain buttons. The other edit ops (Mirror/Offset/Fillet/Chamfer/
|
||||
// Move/…) are first-class sketch tools too. Done constraining = the action-bar ✓.
|
||||
@@ -1852,9 +1859,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
mform->Add(spin_frame(m_move_dz), 0, wxEXPAND);
|
||||
|
||||
m_move_axis = make_combo(m_cards);
|
||||
m_move_axis->Append(_L_CONTEXT("X", "Axis"));
|
||||
m_move_axis->Append(_L_CONTEXT("Y", "Axis"));
|
||||
m_move_axis->Append(_L_CONTEXT("Z", "Axis"));
|
||||
m_move_axis->Append(_L("X"));
|
||||
m_move_axis->Append(_L("Y"));
|
||||
m_move_axis->Append(_L("Z"));
|
||||
m_move_axis->SetSelection(2);
|
||||
mform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Rotation axis")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
mform->Add(m_move_axis, 0, wxEXPAND);
|
||||
@@ -1886,7 +1893,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// --- Extrude dialog (consumes the selected sketch) ---
|
||||
m_box_extrude = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_extrude->Add(card_header(m_cards, "design_extrude", _L_CONTEXT("Extrude", "Design"), m_hdr_extrude), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_extrude->Add(card_header(m_cards, "design_extrude", _L("Extrude"), m_hdr_extrude), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_extrude->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
m_extrude_sketch_label = new wxStaticText(m_cards, wxID_ANY, _L("Sketch: —"));
|
||||
m_box_extrude->Add(m_extrude_sketch_label, 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
@@ -1904,9 +1911,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// End condition (order MUST match ExtrudeEnd: Blind/Symmetric/TwoSided/ThroughAll/
|
||||
// UpToFace/UpToVertex). Up-to-face uses the currently click-selected solid face.
|
||||
m_extrude_end = make_combo(m_cards);
|
||||
for (const char* s : { L("Blind"), L("Symmetric"), L("Two-sided"), L("Through all"),
|
||||
L("Up to face"), L("Up to vertex") })
|
||||
m_extrude_end->Append(_L(s));
|
||||
for (const char* s : { "Blind", "Symmetric", "Two-sided", "Through all",
|
||||
"Up to face", "Up to vertex" })
|
||||
m_extrude_end->Append(s);
|
||||
m_extrude_end->SetSelection(0);
|
||||
eform->Add(new wxStaticText(m_cards, wxID_ANY, _L("End")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
eform->Add(m_extrude_end, 0, wxEXPAND);
|
||||
@@ -1923,8 +1930,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// Order is load-bearing: index maps to BooleanMode (New=0, Add=1, Cut=2, Intersect=3).
|
||||
// Labels use Onshape wording so the choice reads as the user thinks of it.
|
||||
m_mode->Append(_L("New body")); // separate coexisting solid
|
||||
m_mode->Append(_L_CONTEXT("Join", "Boolean Mode")); // fuse into the target body (was "Add")
|
||||
m_mode->Append(_L_CONTEXT("Cut", "Boolean Mode")); // subtract from the target body
|
||||
m_mode->Append(_L("Join")); // fuse into the target body (was "Add")
|
||||
m_mode->Append(_L("Cut")); // subtract from the target body
|
||||
m_mode->Append(_L("Intersect")); // keep only the overlap
|
||||
m_mode->SetSelection(0);
|
||||
eform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Result")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -1960,7 +1967,6 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_face_group = make_combo(m_cards);
|
||||
m_face_group->Append(_L("Top")); // index 0 -> FaceGroup::Top
|
||||
m_face_group->Append(_L("Bottom")); // 1 -> Bottom
|
||||
// TRN Fillet/Chamfer edge group used when no edge is picked: the side edges, as opposed to the top and bottom ones.
|
||||
m_face_group->Append(_L("Lateral")); // 2 -> Lateral
|
||||
m_face_group->Append(_L("All")); // 3 -> All
|
||||
m_face_group->SetSelection(3);
|
||||
@@ -1981,9 +1987,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
auto* hform = two_col_form();
|
||||
|
||||
m_hole_plane = make_combo(m_cards);
|
||||
m_hole_plane->Append(_L_CONTEXT("XY", "Axis"));
|
||||
m_hole_plane->Append(_L_CONTEXT("XZ", "Axis"));
|
||||
m_hole_plane->Append(_L_CONTEXT("YZ", "Axis"));
|
||||
m_hole_plane->Append(_L("XY"));
|
||||
m_hole_plane->Append(_L("XZ"));
|
||||
m_hole_plane->Append(_L("YZ"));
|
||||
m_hole_plane->SetSelection(0);
|
||||
// Picking a plane here is an explicit choice: drop any on-face hijack (a stale face pick
|
||||
// could keep m_hole_on_face true, so the dropdown was ignored and the hole drilled on the
|
||||
@@ -2034,9 +2040,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
auto* tform = two_col_form();
|
||||
|
||||
m_thread_plane = make_combo(m_cards);
|
||||
m_thread_plane->Append(_L_CONTEXT("XY", "Axis"));
|
||||
m_thread_plane->Append(_L_CONTEXT("XZ", "Axis"));
|
||||
m_thread_plane->Append(_L_CONTEXT("YZ", "Axis"));
|
||||
m_thread_plane->Append(_L("XY"));
|
||||
m_thread_plane->Append(_L("XZ"));
|
||||
m_thread_plane->Append(_L("YZ"));
|
||||
m_thread_plane->SetSelection(0);
|
||||
tform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Thread plane")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
tform->Add(m_thread_plane, 0, wxEXPAND);
|
||||
@@ -2122,8 +2128,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
m_revolve_mode = make_combo(m_cards);
|
||||
m_revolve_mode->Append(_L("New body")); // same four words as Extrude
|
||||
m_revolve_mode->Append(_L_CONTEXT("Join", "Boolean Mode"));
|
||||
m_revolve_mode->Append(_L_CONTEXT("Cut", "Boolean Mode"));
|
||||
m_revolve_mode->Append(_L("Join"));
|
||||
m_revolve_mode->Append(_L("Cut"));
|
||||
m_revolve_mode->Append(_L("Intersect"));
|
||||
m_revolve_mode->SetSelection(0);
|
||||
rform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Result")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -2152,8 +2158,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
m_sweep_mode = make_combo(m_cards);
|
||||
m_sweep_mode->Append(_L("New body")); // same four words as Extrude
|
||||
m_sweep_mode->Append(_L_CONTEXT("Join", "Boolean Mode"));
|
||||
m_sweep_mode->Append(_L_CONTEXT("Cut", "Boolean Mode"));
|
||||
m_sweep_mode->Append(_L("Join"));
|
||||
m_sweep_mode->Append(_L("Cut"));
|
||||
m_sweep_mode->Append(_L("Intersect"));
|
||||
m_sweep_mode->SetSelection(0);
|
||||
sform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Result")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -2165,14 +2171,14 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// --- Pattern (replicate the target body: linear or circular) ---
|
||||
m_box_pattern = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_pattern->Add(card_header(m_cards, "design_pattern", _L_CONTEXT("Pattern", "Design"), m_hdr_pattern), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_pattern->Add(card_header(m_cards, "design_pattern", _L("Pattern"), m_hdr_pattern), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_pattern->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
{
|
||||
auto* pform = two_col_form();
|
||||
|
||||
m_pattern_type = make_combo(m_cards);
|
||||
m_pattern_type->Append(_L_CONTEXT("Linear", "Design"));
|
||||
m_pattern_type->Append(_L_CONTEXT("Circular", "Design"));
|
||||
m_pattern_type->Append(_L("Linear"));
|
||||
m_pattern_type->Append(_L("Circular"));
|
||||
m_pattern_type->SetSelection(0);
|
||||
pform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Type")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
pform->Add(m_pattern_type, 0, wxEXPAND);
|
||||
@@ -2208,7 +2214,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
auto* bform = two_col_form();
|
||||
|
||||
m_bool_op = make_combo(m_cards);
|
||||
m_bool_op->Append(_L_CONTEXT("Join", "Boolean Mode")); // the Extrude result word; the offer row says the same
|
||||
m_bool_op->Append(_L("Join")); // the Extrude result word; the offer row says the same
|
||||
m_bool_op->Append(_L("Subtract"));
|
||||
m_bool_op->Append(_L("Intersect"));
|
||||
m_bool_op->SetSelection(0);
|
||||
@@ -2248,7 +2254,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// --- Cut (split a body with a plane): parameters only; ✓/✗ live on the ribbon ---
|
||||
m_box_cut = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_cut->Add(card_header(m_cards, "design_cut", _L_CONTEXT("Cut", "Design"), m_hdr_cut), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_cut->Add(card_header(m_cards, "design_cut", _L("Cut"), m_hdr_cut), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_cut->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
{
|
||||
auto* cform = two_col_form();
|
||||
@@ -2307,7 +2313,6 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
m_plane_base = make_combo(m_cards);
|
||||
populate_plane_choices(m_plane_base); // XY/XZ/YZ + any existing datum planes
|
||||
// TRN Plane tool: the reference plane (XY, XZ, YZ or a datum) the new plane is built from.
|
||||
plform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Base")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
plform->Add(m_plane_base, 0, wxEXPAND);
|
||||
|
||||
@@ -2365,8 +2370,8 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
m_loft_mode = make_combo(m_cards);
|
||||
m_loft_mode->Append(_L("New body")); // same four words as Extrude
|
||||
m_loft_mode->Append(_L_CONTEXT("Join", "Boolean Mode"));
|
||||
m_loft_mode->Append(_L_CONTEXT("Cut", "Boolean Mode"));
|
||||
m_loft_mode->Append(_L("Join"));
|
||||
m_loft_mode->Append(_L("Cut"));
|
||||
m_loft_mode->Append(_L("Intersect"));
|
||||
m_loft_mode->SetSelection(0);
|
||||
lform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Result")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -2509,7 +2514,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
sform->Add(m_shell_face_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
m_box_shell = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_shell->Add(card_header(m_cards, "design_shell", _L_CONTEXT("Shell", "Design"), m_hdr_shell), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_shell->Add(card_header(m_cards, "design_shell", _L("Shell"), m_hdr_shell), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_shell->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
m_box_shell->Add(new wxStaticText(m_cards, wxID_ANY,
|
||||
_L("Pick a solid face to open it, then set the wall thickness.")),
|
||||
@@ -2558,9 +2563,9 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
xform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Translate Z")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
xform->Add(spin_frame(m_xf_dz), 0, wxEXPAND);
|
||||
m_xf_axis = make_combo(m_cards);
|
||||
m_xf_axis->Append(_L_CONTEXT("X", "Axis"));
|
||||
m_xf_axis->Append(_L_CONTEXT("Y", "Axis"));
|
||||
m_xf_axis->Append(_L_CONTEXT("Z", "Axis"));
|
||||
m_xf_axis->Append(_L("X"));
|
||||
m_xf_axis->Append(_L("Y"));
|
||||
m_xf_axis->Append(_L("Z"));
|
||||
m_xf_axis->SetSelection(2); // default Z
|
||||
m_xf_axis->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { refresh_preview(); });
|
||||
xform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Rotate axis")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
@@ -2657,7 +2662,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// --- Rib (thin wall from an open sketch line, fused to a body) ---
|
||||
m_box_rib = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_rib->Add(card_header(m_cards, "design_rib", _L_CONTEXT("Rib", "Design"), m_hdr_rib), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_rib->Add(card_header(m_cards, "design_rib", _L("Rib"), m_hdr_rib), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_rib->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
{
|
||||
auto* rform = two_col_form();
|
||||
@@ -2693,7 +2698,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
|
||||
// --- Project (project body edges onto a plane as sketch entities) ---
|
||||
m_box_project = new wxBoxSizer(wxVERTICAL);
|
||||
m_box_project->Add(card_header(m_cards, "design_sketch", _L_CONTEXT("Project", "Design"), m_hdr_project), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_project->Add(card_header(m_cards, "design_sketch", _L("Project"), m_hdr_project), 0, wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
m_box_project->Add(new wxStaticLine(m_cards), 0, wxEXPAND | wxALL, 8);
|
||||
{
|
||||
auto* pform = two_col_form();
|
||||
@@ -2898,11 +2903,11 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_cs_x = make_spin(m_cards, 0.0, -100000.0, 100000.0);
|
||||
m_cs_y = make_spin(m_cards, 0.0, -100000.0, 100000.0);
|
||||
m_cs_z = make_spin(m_cards, 0.0, -100000.0, 100000.0);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L_CONTEXT("X", "Axis")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(spin_frame(m_cs_x), 0, wxEXPAND);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L_CONTEXT("Y", "Axis")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Y")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(spin_frame(m_cs_y), 0, wxEXPAND);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L_CONTEXT("Z", "Axis")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("Z")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(spin_frame(m_cs_z), 0, wxEXPAND);
|
||||
|
||||
auto cs_pick = [&](const wxString& label, ::Button*& btn, wxStaticText*& lbl, CoordSysPick target) {
|
||||
@@ -2931,11 +2936,11 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
m_cs_hx = make_spin(m_cards, 1.0, -100000.0, 100000.0);
|
||||
m_cs_hy = make_spin(m_cards, 0.0, -100000.0, 100000.0);
|
||||
m_cs_hz = make_spin(m_cards, 0.0, -100000.0, 100000.0);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X direction X")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X hint X")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(spin_frame(m_cs_hx), 0, wxEXPAND);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X direction Y")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X hint Y")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(spin_frame(m_cs_hy), 0, wxEXPAND);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X direction Z")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(new wxStaticText(m_cards, wxID_ANY, _L("X hint Z")), 0, wxALIGN_CENTER_VERTICAL);
|
||||
csform->Add(spin_frame(m_cs_hz), 0, wxEXPAND);
|
||||
|
||||
m_box_coordsys->Add(csform, 0, wxEXPAND | wxLEFT | wxRIGHT | wxTOP, 12);
|
||||
@@ -3184,7 +3189,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// one click away on a row the user has just selected — the message points at it instead
|
||||
// of describing a problem with no way out.
|
||||
if (const std::string* why = (sel >= 0) ? mate_conflict_reason(sel) : nullptr) {
|
||||
set_status(StatusKind::Error, wxString::Format(_L("%s. Click the eye icon to suppress this mate."), wxString::FromUTF8(*why)));
|
||||
set_status(StatusKind::Error, wxString::FromUTF8(*why) + _L(" — the eye suppresses this mate"));
|
||||
} else if (sel >= 0 && sel < int(m_doc.features.size())) {
|
||||
// Name the two gestures the row supports, because neither is visible on it.
|
||||
set_status(StatusKind::Info, wxString::Format(
|
||||
@@ -3876,7 +3881,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
if (got && m_viewport) m_viewport->set_escalate_on_repick(true);
|
||||
}
|
||||
const int nb = int(m_doc.bodies.size());
|
||||
const bool bodytag = nb > 1;
|
||||
const wxString bodytag = (nb > 1) ? wxString::Format(_L("Body %d "), body + 1) : wxString();
|
||||
// Each sub-element line ends by naming the NEXT click (gem). Escalation to the
|
||||
// whole body is a gesture nothing on screen would otherwise reveal, and the status line
|
||||
// is the only surface that can teach it at the moment it applies. It REPLACES the old
|
||||
@@ -3888,22 +3893,13 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
// into the panel. Say "what applies to it", never "verbs" — that is this codebase's
|
||||
// word for a tool-offer entry, not a word the drawing office uses, and the plane and
|
||||
// bodies-list lines already say it the right way.
|
||||
set_status(level == 4 ? (bodytag ? wxString::Format(_L("Body %d: vertex selected — click again for the whole body"), body + 1)
|
||||
: _L("Vertex selected — click again for the whole body"))
|
||||
: level == 1 ? (bodytag ? wxString::Format(_L("Body %d selected (whole body) — right-click for what applies to it"), body + 1)
|
||||
: _L("Whole body selected — right-click for what applies to it"))
|
||||
: level == 2 ? (bodytag ? wxString::Format(_L("Body %d: face %d selected — right-click to push/pull it, or click again for the whole body"), body + 1, face)
|
||||
: wxString::Format(_L("Face %d selected — right-click to push/pull it, or click again for the whole body"), face))
|
||||
: level == 3 && m_sel_solid_edges.size() > 1
|
||||
? (bodytag ? wxString::Format(_L_PLURAL("Body %d: %zu edge selected — Shift+click adds or removes one, Fillet/Chamfer dresses them all",
|
||||
"Body %d: %zu edges selected — Shift+click adds or removes one, Fillet/Chamfer dresses them all",
|
||||
unsigned(m_sel_solid_edges.size())), body + 1, m_sel_solid_edges.size())
|
||||
: wxString::Format(_L_PLURAL("%zu edge selected — Shift+click adds or removes one, Fillet/Chamfer dresses them all",
|
||||
"%zu edges selected — Shift+click adds or removes one, Fillet/Chamfer dresses them all",
|
||||
unsigned(m_sel_solid_edges.size())), m_sel_solid_edges.size()))
|
||||
: level == 3 ? (bodytag ? wxString::Format(_L("Body %d: edge %d selected — Shift+click to add edges, or click again for the whole body"), body + 1, edge)
|
||||
: wxString::Format(_L("Edge %d selected — Shift+click to add edges, or click again for the whole body"), edge))
|
||||
: _L("Nothing selected"));
|
||||
set_status(level == 4 ? bodytag + _L("vertex selected — click again for the whole body")
|
||||
: level == 1 ? bodytag + _L("selected (whole body) — right-click for what applies to it")
|
||||
: level == 2 ? bodytag + wxString::Format(_L("face %d selected — right-click to push/pull it, or click again for the whole body"), face)
|
||||
: level == 3 && m_sel_solid_edges.size() > 1
|
||||
? bodytag + wxString::Format(_L_PLURAL("%zu edge selected — Shift+click adds or removes one, Fillet/Chamfer dresses them all", "%zu edges selected — Shift+click adds or removes one, Fillet/Chamfer dresses them all", m_sel_solid_edges.size()), m_sel_solid_edges.size())
|
||||
: level == 3 ? bodytag + wxString::Format(_L("edge %d selected — Shift+click to add edges, or click again for the whole body"), edge)
|
||||
: _L("Nothing selected"));
|
||||
m_status->Refresh();
|
||||
});
|
||||
// A click on nothing drops a list row as it drops a pick. Not while a card is open: the
|
||||
@@ -3998,7 +3994,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
if (m_viewport && m_viewport->is_sketching())
|
||||
m_viewport->set_sketch_plane(plane_from_choice(m_ref_plane));
|
||||
}
|
||||
const wxString nm = ref_plane_name(base);
|
||||
const char* nm = (base == 0) ? "XY" : (base == 1) ? "XZ" : (base == 2) ? "YZ" : "datum";
|
||||
// Both halves named the TOOLBAR, which no longer carries either button: the tools
|
||||
// moved to the offer. Name the gesture that actually works in each mode, and say
|
||||
// what a plain click does, since the two are easy to confuse on a plane.
|
||||
@@ -4039,12 +4035,10 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
if (m_xf_angle) m_xf_angle->SetValue(sign * aa.angle() * 180.0 / M_PI);
|
||||
}
|
||||
const int nb = int(m_doc.bodies.size());
|
||||
const wxString tag = (nb > 1) ? wxString::Format(_L("Body %d "), body + 1) : wxString();
|
||||
const Vec3d t = xform.translation();
|
||||
set_status(StatusKind::Info, nb > 1
|
||||
? wxString::Format(_L("Body %d placed at (%.1f, %.1f, %.1f) mm — drag arrows to move, rings to rotate"),
|
||||
body + 1, t.x(), t.y(), t.z())
|
||||
: wxString::Format(_L("Placed at (%.1f, %.1f, %.1f) mm — drag arrows to move, rings to rotate"),
|
||||
t.x(), t.y(), t.z()));
|
||||
set_status(StatusKind::Info, tag + wxString::Format(_L("placed (%.1f, %.1f, %.1f) mm — drag arrows to move, rings to rotate"),
|
||||
t.x(), t.y(), t.z()));
|
||||
});
|
||||
|
||||
// Fillet/Chamfer radius gizmo: dragging (or editing) the edge-anchored arrow writes the
|
||||
@@ -4131,7 +4125,7 @@ DesignPanel::DesignPanel(wxWindow* parent)
|
||||
set_status(error ? StatusKind::Error : StatusKind::Info, wxString::FromUTF8(msg));
|
||||
});
|
||||
m_viewport->set_on_sketch_exit_refused([this]() {
|
||||
set_status(StatusKind::Info, _L("Sketch kept — Confirm to commit it, Cancel to discard"));
|
||||
set_status(StatusKind::Info, _L("Sketch kept — Finish to commit it, Cancel to discard"));
|
||||
});
|
||||
|
||||
// Ctrl+Z / Ctrl+Shift+Z (Ctrl+Y) from the viewport → feature-history undo/redo.
|
||||
@@ -4449,7 +4443,7 @@ void DesignPanel::apply_dof_status(int dof, bool ok, bool has_constraints)
|
||||
m_dof_status->SetLabel(_L("✓ Fully constrained"));
|
||||
} else if (dof > 0) {
|
||||
m_dof_status->SetForegroundColour(dp_ctl_text());
|
||||
m_dof_status->SetLabel(wxString::Format(_L_PLURAL("%d degree of freedom", "%d degrees of freedom", dof), dof));
|
||||
m_dof_status->SetLabel(wxString::Format(_L("%d degrees of freedom"), dof));
|
||||
} else {
|
||||
m_dof_status->SetLabel(wxString());
|
||||
}
|
||||
@@ -4526,7 +4520,7 @@ void DesignPanel::set_ui_mode(UiMode m)
|
||||
if (sketching && m_sketch_banner_txt != nullptr)
|
||||
m_sketch_banner_txt->SetLabel(
|
||||
wxString::Format(_L("Editing: Sketch %d · N = look normal to the plane · "
|
||||
"Confirm or Cancel in the toolbar"),
|
||||
"Finish or Cancel in the toolbar"),
|
||||
m_feature_counter + 1));
|
||||
m_sketch_banner->Show(sketching);
|
||||
m_sketch_banner->GetParent()->Layout();
|
||||
@@ -4783,7 +4777,7 @@ void DesignPanel::text_dialog_done(bool accepted)
|
||||
void DesignPanel::on_import_svg()
|
||||
{
|
||||
wxFileDialog dlg(this, _L("Import SVG"), wxEmptyString, wxEmptyString,
|
||||
_L("SVG files (*.svg)|*.svg|All files|*.*"),
|
||||
"SVG files (*.svg)|*.svg|All files|*.*",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
@@ -4895,7 +4889,7 @@ bool DesignPanel::recompute_guarded(const wxString& message)
|
||||
void DesignPanel::on_import_step()
|
||||
{
|
||||
wxFileDialog dlg(this, _L("Import STEP"), wxEmptyString, wxEmptyString,
|
||||
_L("STEP files (*.step;*.stp)|*.step;*.stp|All files|*.*"),
|
||||
"STEP files (*.step;*.stp)|*.step;*.stp|All files|*.*",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
@@ -4943,9 +4937,7 @@ void DesignPanel::on_import_step()
|
||||
set_tree_selection(int(m_doc.features.size()) - 1);
|
||||
set_status_ok(); // canonical post-recompute viewport/pick/parts refresh
|
||||
set_status(StatusKind::Info, wxString::Format(
|
||||
_L_PLURAL("Imported %d solid — pick a face or edge, then Fillet / Cut / Shell to modify",
|
||||
"Imported %d solids — pick a face or edge, then Fillet / Cut / Shell to modify",
|
||||
unsigned(solids.size())),
|
||||
_L("Imported %d solid(s) — pick a face or edge, then Fillet / Cut / Shell to modify"),
|
||||
int(solids.size())));
|
||||
}
|
||||
|
||||
@@ -4956,7 +4948,7 @@ void DesignPanel::on_import_step()
|
||||
void DesignPanel::on_import_mesh()
|
||||
{
|
||||
wxFileDialog dlg(this, _L("Import mesh"), wxEmptyString, wxEmptyString,
|
||||
_L("Mesh files (*.stl;*.obj)|*.stl;*.obj|All files|*.*"),
|
||||
"Mesh files (*.stl;*.obj)|*.stl;*.obj|All files|*.*",
|
||||
wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
@@ -5039,8 +5031,8 @@ void DesignPanel::on_import_mesh()
|
||||
stats.kept_tris, stats.faces_final, stats.volume));
|
||||
} else {
|
||||
set_status(StatusKind::Warning, wxString::Format(
|
||||
_L("Imported as an open shell (not watertight). Boundary edges: %d, non-manifold "
|
||||
"edges: %d, triangles: %d → faces: %d. The source mesh has holes or duplicated "
|
||||
_L("Imported as an open shell (not watertight): %d boundary edge(s), %d non-manifold "
|
||||
"edge(s) — %d triangles → %d faces. The source mesh has holes or duplicated "
|
||||
"geometry; boolean features may fail on it"),
|
||||
stats.boundary_edges, stats.nonmanifold_edges, stats.kept_tris, stats.faces_final));
|
||||
}
|
||||
@@ -5151,7 +5143,7 @@ void DesignPanel::cancel_insert()
|
||||
set_ui_mode(UiMode::Feature);
|
||||
sync_sketch_display();
|
||||
refresh_tree();
|
||||
set_status(StatusKind::Info, _L("Insert canceled"));
|
||||
set_status(StatusKind::Info, _L("Insert cancelled"));
|
||||
}
|
||||
|
||||
void DesignPanel::on_transform_imported(int feat_idx)
|
||||
@@ -5178,7 +5170,7 @@ void DesignPanel::on_add_sketch()
|
||||
m_doc.add_sketch(shape, plane, m_width->GetValue(), m_height->GetValue(),
|
||||
m_radius->GetValue(), feature_name(_L("Sketch")));
|
||||
m_doc.recompute(); // a lone sketch yields an empty body; that is expected
|
||||
set_status(StatusKind::Info, wxString::Format(_L("Sketch added (plane: %s) — select it, then right-click to Extrude"), where));
|
||||
set_status(StatusKind::Info, wxString::Format(_L("Sketch added on %s — select it, then right-click to Extrude"), where));
|
||||
refresh_tree();
|
||||
}
|
||||
|
||||
@@ -5889,8 +5881,7 @@ void DesignPanel::on_check_interference()
|
||||
body_name(sorted[k].body_b), sorted[k].volume);
|
||||
if (sorted.size() > shown)
|
||||
list += wxString::FromUTF8(", …");
|
||||
set_status(StatusKind::Warning, wxString::Format(_L_PLURAL("%zu overlapping pair: %s", "%zu overlapping pairs: %s",
|
||||
unsigned(sorted.size())), sorted.size(), list));
|
||||
set_status(StatusKind::Warning, wxString::Format(_L("%zu overlapping pairs: %s"), sorted.size(), list));
|
||||
}
|
||||
|
||||
// Volume and surface area of the selected body. A report, not a feature: it never checkpoints,
|
||||
@@ -6090,7 +6081,7 @@ void DesignPanel::populate_plane_choices(ComboBox* c) const
|
||||
if (!c) return;
|
||||
const int keep = c->GetSelection();
|
||||
c->Clear();
|
||||
c->Append(_L_CONTEXT("XY", "Axis")); c->Append(_L_CONTEXT("XZ", "Axis")); c->Append(_L_CONTEXT("YZ", "Axis"));
|
||||
c->Append(_L("XY")); c->Append(_L("XZ")); c->Append(_L("YZ"));
|
||||
for (const auto& dp : m_doc.resolve_datum_planes())
|
||||
c->Append(wxString::FromUTF8(dp.first));
|
||||
c->SetSelection((keep >= 0 && keep < int(c->GetCount())) ? keep : 0);
|
||||
@@ -6139,8 +6130,8 @@ SketchPlane DesignPanel::sketch_plane_from_selection(wxString& what) const
|
||||
const int fi = (m_sel_solid_face >= 0 && m_sel_solid_body >= 0) ? m_sel_solid_face : m_pick_face;
|
||||
if (fb >= 0 && fi >= 0 && m_doc.plane_of_face(fb, fi, p)) {
|
||||
what = (m_doc.bodies.size() > 1)
|
||||
? wxString::Format(_L("Picked face (Body %d)"), fb + 1)
|
||||
: _L("Picked face");
|
||||
? wxString::Format(_L("the picked face of Body %d"), fb + 1)
|
||||
: _L("the picked face");
|
||||
return p;
|
||||
}
|
||||
what = ref_plane_name(m_ref_plane);
|
||||
@@ -6158,8 +6149,8 @@ bool DesignPanel::sketch_plane_target(wxString& what) const
|
||||
SketchPlane p;
|
||||
if (fb >= 0 && fi >= 0 && m_doc.plane_of_face(fb, fi, p)) {
|
||||
what = (m_doc.bodies.size() > 1)
|
||||
? wxString::Format(_L("Picked face (Body %d)"), fb + 1)
|
||||
: _L("Picked face");
|
||||
? wxString::Format(_L("the picked face of Body %d"), fb + 1)
|
||||
: _L("the picked face");
|
||||
return true;
|
||||
}
|
||||
if (m_plane_picked) { what = ref_plane_name(m_ref_plane); return true; }
|
||||
@@ -6385,10 +6376,8 @@ static wxString sketch_step_prompt(DesignSketchTool::Mode m, int step, int picks
|
||||
switch (m) {
|
||||
case Mode::Select:
|
||||
return picks > 0
|
||||
? wxString::Format(_L_PLURAL("%d selected · Del removes it · Shift-click adds · "
|
||||
"double-click takes the whole loop",
|
||||
"%d selected · Del removes them · Shift-click adds · "
|
||||
"double-click takes the whole loop", picks), picks)
|
||||
? wxString::Format(_L("%d selected · Del removes them · Shift-click adds · "
|
||||
"double-click takes the whole loop"), picks)
|
||||
: _L("Select — click an entity to pick it · drag an endpoint or center to move it · "
|
||||
"Shift-click adds · Del removes");
|
||||
case Mode::Constrain:
|
||||
@@ -6637,7 +6626,8 @@ void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
return (v.accepts & bit) && v.need_bodies <= bodies && v.need_sketches <= sketches
|
||||
&& (!v.need_sheet || sheet);
|
||||
};
|
||||
// Row names, verb names, refusals, families and hints are L()-marked in the generated table.
|
||||
// Names and reasons live in the generated table as plain literals; they are the same strings
|
||||
// the toolbar already ships, so the catalogue already carries their translations.
|
||||
// Scope the lookup to the APPLICATION catalog. A bare wxGetTranslation() searches every
|
||||
// loaded catalog, wxWidgets' own wxstd included — so on a non-English system the two row
|
||||
// names that happen to be wx standard strings came back translated while the other six,
|
||||
@@ -6651,7 +6641,7 @@ void DesignPanel::show_offer_menu(const wxPoint& screen_pos)
|
||||
return wxGetTranslation(wxString::FromUTF8(s), SLIC3R_APP_KEY);
|
||||
};
|
||||
auto label = [&](const OfferVerb& v) {
|
||||
wxString s = I18N::translate(v.name, "Design");
|
||||
wxString s = tr(v.name);
|
||||
if (v.key && *v.key) s += "\t" + wxString::FromUTF8(v.key);
|
||||
return s;
|
||||
};
|
||||
@@ -7354,8 +7344,12 @@ void DesignPanel::hydrate_from_model()
|
||||
// that has none yet. A design in progress keeps its origin: its sketches have it baked into
|
||||
// their planes, so moving it under them (a printer change between visits) would slide the
|
||||
// datum planes off the geometry. A loaded project brings its own (load_recipe).
|
||||
// The centre of the Design bed, not the plater's: the plater moves its bed onto the current
|
||||
// plate, so on plate 2+ the origin would land one plate stride away from the bed drawn here.
|
||||
// Re-synced first because the control socket calls this without on_tab_shown().
|
||||
if (!m_doc.origin_from_recipe) {
|
||||
const Vec2d bc = plater->build_volume().bed_center();
|
||||
m_viewport->refresh_bed();
|
||||
const Vec2d bc = m_viewport->bed_center();
|
||||
m_doc.modeling_origin = Vec3d(bc.x(), bc.y(), 0.0);
|
||||
// A document started here takes the weld rule from the preference; a loaded one
|
||||
// brings its own (load_recipe).
|
||||
@@ -7639,8 +7633,8 @@ void DesignPanel::flip_section_view()
|
||||
if (!m_viewport || !m_section_on) return;
|
||||
m_section_upper = !m_section_upper;
|
||||
m_viewport->set_section_plane(true, m_section_cut_z, m_section_upper);
|
||||
set_status(StatusKind::Info, m_section_upper ? _L("Section view — showing the upper half")
|
||||
: _L("Section view — showing the lower half"));
|
||||
set_status(StatusKind::Info, wxString::Format(_L("Section view — showing the %s half"),
|
||||
m_section_upper ? _L("upper") : _L("lower")));
|
||||
}
|
||||
|
||||
void DesignPanel::sync_body_visible()
|
||||
@@ -7902,8 +7896,7 @@ void DesignPanel::set_tree_selection(int row)
|
||||
|
||||
// The selection (the solid pick, the hit face and the committed-loop pick) names bodies, faces and
|
||||
// features by index, so replacing or renumbering the feature list (undo/redo, New Design, load,
|
||||
// delete, reorder) drops it, the viewport's highlights with it. The solid highlight too: a rebuild
|
||||
// that leaves no body never reaches set_solid_pick. The callers repaint.
|
||||
// delete, reorder) drops it, the viewport's highlights with it. The callers repaint.
|
||||
void DesignPanel::drop_selection()
|
||||
{
|
||||
if (m_viewport != nullptr)
|
||||
@@ -8099,8 +8092,18 @@ void DesignPanel::on_toggle_visibility()
|
||||
refresh_tree(); // greys the row
|
||||
set_tree_selection(sel); // keep the toggled feature selected
|
||||
if (m_viewport != nullptr) {
|
||||
if (m_doc.display_mesh.its.indices.empty()) m_viewport->clear_mesh();
|
||||
else feed_bodies();
|
||||
if (m_doc.display_mesh.its.indices.empty()) {
|
||||
m_viewport->clear_mesh();
|
||||
} else {
|
||||
// Not set_status_ok(), which would take the gizmo from a Move still open.
|
||||
feed_bodies();
|
||||
// Hiding or showing a feature rebuilds the bodies and can renumber them, so the pick is
|
||||
// re-pointed and its selection dropped. That also brings picking back for a body shown
|
||||
// after everything was hidden (clear_mesh() dropped it).
|
||||
m_viewport->set_solid_pick(&m_doc.bodies, &m_disp_pick_mesh,
|
||||
&m_doc.display_tri_face, &m_doc.display_tri_body,
|
||||
&m_body_visible, &m_body_xform);
|
||||
}
|
||||
}
|
||||
sync_sketch_display(); // skips the hidden sketch + direct-renders
|
||||
set_status(StatusKind::Info, shown ? _L("Feature shown") : _L("Feature hidden"));
|
||||
@@ -8304,10 +8307,8 @@ void DesignPanel::apply_live_constraint(SketchConstraintType type)
|
||||
// tool auto-selects only the entity it just drew — so after drawing two lines exactly
|
||||
// one is selected and the button correctly refuses. Say how many are picked, or the
|
||||
// refusal is indistinguishable from a dead button.
|
||||
fail(wxString::Format(_L_PLURAL("%s — %d selected. Press Esc for the Select tool, click one "
|
||||
"line, then Shift- or Ctrl-click the other.",
|
||||
"%s — %d selected. Press Esc for the Select tool, click one "
|
||||
"line, then Shift- or Ctrl-click the other.", unsigned(sel.size())),
|
||||
fail(wxString::Format(_L("%s — %d selected. Press Esc for the Select tool, click one "
|
||||
"line, then Shift- or Ctrl-click the other."),
|
||||
constraint_reject_text(plan.reason, type), int(sel.size())));
|
||||
return;
|
||||
case ConstraintPlan::Kind::AskValue:
|
||||
@@ -8375,7 +8376,7 @@ void DesignPanel::refresh_constrain_dof()
|
||||
m_dof_status->SetLabel(_L("✓ Fully constrained")); m_dof_status->Show(!m_dof_status->GetLabel().IsEmpty());
|
||||
} else if (r.dof > 0) {
|
||||
m_dof_status->SetForegroundColour(dp_ctl_text());
|
||||
m_dof_status->SetLabel(wxString::Format(_L_PLURAL("%d degree of freedom", "%d degrees of freedom", r.dof), r.dof)); m_dof_status->Show(!m_dof_status->GetLabel().IsEmpty());
|
||||
m_dof_status->SetLabel(wxString::Format(_L("%d degrees of freedom"), r.dof)); m_dof_status->Show(!m_dof_status->GetLabel().IsEmpty());
|
||||
} else {
|
||||
m_dof_status->SetLabel(wxString()); m_dof_status->Show(!m_dof_status->GetLabel().IsEmpty());
|
||||
}
|
||||
@@ -8409,8 +8410,7 @@ wxString DesignPanel::constraint_label(const SketchEntityConstraintDef& d) const
|
||||
// an empty string, or a NULL format string that crashes wxString::Format.
|
||||
wxString s; s << wxUniChar(c) << ei; // avoid %c assert in Unicode build
|
||||
if (r == SketchPointRole::P1) s += wxString::FromUTF8("·P1");
|
||||
// TRN Design sketch: abbreviation of "center", appended to a point tag such as "L3·Ctr"
|
||||
else if (r == SketchPointRole::Center) s += _L("·Ctr");
|
||||
else if (r == SketchPointRole::Center) s += wxString::FromUTF8("·Ctr");
|
||||
else if (r == SketchPointRole::P0) s += wxString::FromUTF8("·P0");
|
||||
return s;
|
||||
};
|
||||
@@ -8420,27 +8420,27 @@ wxString DesignPanel::constraint_label(const SketchEntityConstraintDef& d) const
|
||||
};
|
||||
switch (d.type) {
|
||||
case T::Fix: return wxString::Format(_L("Fix %s"), tag(d.ea, d.ra));
|
||||
case T::Coincident: return two(_L_CONTEXT("Coincident", "Sketch Constraint"));
|
||||
case T::Horizontal: return two(_L_CONTEXT("Horizontal", "Sketch Constraint"));
|
||||
case T::Vertical: return two(_L_CONTEXT("Vertical", "Sketch Constraint"));
|
||||
case T::Coincident: return two(_L("Coincident"));
|
||||
case T::Horizontal: return two(_L("Horizontal"));
|
||||
case T::Vertical: return two(_L("Vertical"));
|
||||
case T::Distance: return wxString::Format("%s = %s", two(_L("Distance")), en_format(d.value));
|
||||
case T::LockX: return wxString::Format(_L("Lock X %s"), tag(d.ea, d.ra));
|
||||
case T::LockY: return wxString::Format(_L("Lock Y %s"), tag(d.ea, d.ra));
|
||||
case T::EqualLength: return two(_L_CONTEXT("Equal", "Sketch Constraint"));
|
||||
case T::Parallel: return two(_L_CONTEXT("Parallel", "Sketch Constraint"));
|
||||
case T::Perpendicular: return two(_L_CONTEXT("Perpendicular", "Sketch Constraint"));
|
||||
case T::Concentric: return two(_L_CONTEXT("Concentric", "Sketch Constraint"));
|
||||
case T::Tangent: return two(_L_CONTEXT("Tangent", "Sketch Constraint"));
|
||||
case T::Midpoint: return two(_L_CONTEXT("Midpoint", "Sketch Constraint"));
|
||||
case T::EqualLength: return two(_L("Equal"));
|
||||
case T::Parallel: return two(_L("Parallel"));
|
||||
case T::Perpendicular: return two(_L("Perpendicular"));
|
||||
case T::Concentric: return two(_L("Concentric"));
|
||||
case T::Tangent: return two(_L("Tangent"));
|
||||
case T::Midpoint: return two(_L("Midpoint"));
|
||||
case T::Symmetric: return wxString::Format(_L("Symmetric %s — %s / %s"),
|
||||
tag(d.ea, d.ra), tag(d.eb, d.rb), tag(d.ec, d.rc));
|
||||
case T::SymmetricAboutY: return wxString::Format(_L("Symmetric about Y axis %s — %s"),
|
||||
tag(d.ea, d.ra), tag(d.eb, d.rb));
|
||||
case T::SymmetricAboutX: return wxString::Format(_L("Symmetric about X axis %s — %s"),
|
||||
tag(d.ea, d.ra), tag(d.eb, d.rb));
|
||||
case T::Angle: return wxString::Format(wxString::FromUTF8("%s = %s°"), two(_L_CONTEXT("Angle", "Sketch Constraint")), en_format(d.value * 180.0 / M_PI, 1));
|
||||
case T::Radius: return wxString::Format("%s %s = %s", _L_CONTEXT("Radius", "Sketch Constraint"), tag(d.ea, d.ra), en_format(d.value));
|
||||
case T::Diameter: return wxString::Format("%s %s = %s", _L_CONTEXT("Diameter", "Sketch Constraint"), tag(d.ea, d.ra), en_format(d.value));
|
||||
case T::Angle: return wxString::Format(wxString::FromUTF8("%s = %s°"), two(_L("Angle")), en_format(d.value * 180.0 / M_PI, 1));
|
||||
case T::Radius: return wxString::Format("%s %s = %s", _L("Radius"), tag(d.ea, d.ra), en_format(d.value));
|
||||
case T::Diameter: return wxString::Format("%s %s = %s", _L("Diameter"), tag(d.ea, d.ra), en_format(d.value));
|
||||
case T::PointOnLine: return two(_L("On line"));
|
||||
case T::PointOnObject: return two(_L("On edge"));
|
||||
}
|
||||
@@ -9714,7 +9714,7 @@ void DesignPanel::load_feature_into_dialog(const CadFeature& f)
|
||||
wxString s;
|
||||
for (size_t i = 0; i < m_del_faces.size(); ++i) {
|
||||
if (i > 0) s += ", ";
|
||||
s += wxString::Format(_L("Face %d"), m_del_faces[i]);
|
||||
s += wxString::Format("Face %d", m_del_faces[i]);
|
||||
}
|
||||
m_del_face_list->SetLabel(s.empty() ? _L("(none)") : s);
|
||||
}
|
||||
@@ -9780,7 +9780,7 @@ void DesignPanel::on_edit_feature()
|
||||
m_viewport->set_display_sketches({});
|
||||
m_viewport->edit_sketch(f.entities, f.entity_constraints, f.plane);
|
||||
}
|
||||
set_status(StatusKind::Info, _L("Editing sketch — drag a handle or double-click a dimension to edit it"));
|
||||
set_status(StatusKind::Info, _L("Editing sketch — drag a handle or click a quote to edit"));
|
||||
} else {
|
||||
load_feature_into_dialog(f);
|
||||
open_tool(Tool::Sketch);
|
||||
@@ -9975,7 +9975,7 @@ void DesignPanel::on_export_step()
|
||||
return;
|
||||
}
|
||||
wxFileDialog dlg(this, _L("Export STEP"), wxEmptyString, "model.step",
|
||||
_L("STEP files (*.step;*.stp)|*.step;*.stp"),
|
||||
"STEP files (*.step;*.stp)|*.step;*.stp",
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
|
||||
if (dlg.ShowModal() != wxID_OK)
|
||||
return;
|
||||
@@ -11197,10 +11197,12 @@ void DesignPanel::refresh_preview()
|
||||
// Confirm so the user sees the gate before clicking; the red status says why.
|
||||
m_candidate_ok = ok;
|
||||
update_confirm_button();
|
||||
// Fillet/Chamfer/Draft: once the target edge/face yields a valid result, show ONLY the
|
||||
// Fillet/Chamfer/Draft/Hole: once the target edge/face yields a valid result, show ONLY the
|
||||
// preview (hide the base bodies) so the user sees the finished shape, not the old solid
|
||||
// doubled with the ghost. Before a valid pick the body stays visible so it can be picked.
|
||||
m_viewport->set_body_hidden((m_active == Tool::Dressup || m_active == Tool::Draft) && ok);
|
||||
// doubled with the ghost. A hole's cut lies inside the old solid, so without this the body
|
||||
// would hide it entirely. Before a valid pick the body stays visible so it can be picked.
|
||||
m_viewport->set_body_hidden((m_active == Tool::Dressup || m_active == Tool::Draft ||
|
||||
m_active == Tool::Hole) && ok);
|
||||
m_status->Refresh();
|
||||
|
||||
// Refresh the in-canvas Extrude depth arrow (self-gates: only while the Extrude card is open).
|
||||
@@ -11492,20 +11494,20 @@ void DesignPanel::open_tool(Tool t)
|
||||
};
|
||||
switch (t) {
|
||||
case Tool::Sketch: m_hdr_sketch->SetLabel(title(_L("Sketch"))); break;
|
||||
case Tool::Extrude: m_hdr_extrude->SetLabel(title(_L_CONTEXT("Extrude", "Design"))); break;
|
||||
case Tool::Extrude: m_hdr_extrude->SetLabel(title(_L("Extrude"))); break;
|
||||
case Tool::Dressup: m_hdr_dressup->SetLabel(title(
|
||||
m_dressup_type->GetSelection() == 0 ? _L("Fillet") : _L("Chamfer"))); break;
|
||||
case Tool::Hole: m_hdr_hole->SetLabel(title(_L("Hole"))); break;
|
||||
case Tool::Thread: m_hdr_thread->SetLabel(title(_L("Thread"))); break;
|
||||
case Tool::Shell: m_hdr_shell->SetLabel(title(_L_CONTEXT("Shell", "Design"))); break;
|
||||
case Tool::Shell: m_hdr_shell->SetLabel(title(_L("Shell"))); break;
|
||||
case Tool::Revolve: m_hdr_revolve->SetLabel(title(_L("Revolve"))); break;
|
||||
case Tool::Sweep: m_hdr_sweep->SetLabel(title(_L("Sweep"))); break;
|
||||
case Tool::Pattern: m_hdr_pattern->SetLabel(title(_L_CONTEXT("Pattern", "Design"))); break;
|
||||
case Tool::Pattern: m_hdr_pattern->SetLabel(title(_L("Pattern"))); break;
|
||||
case Tool::Plane: m_hdr_plane->SetLabel(title(_L("Plane"))); break;
|
||||
case Tool::Loft: m_hdr_loft->SetLabel(title(_L("Loft"))); break;
|
||||
case Tool::Draft: m_hdr_draft->SetLabel(title(_L("Draft"))); break;
|
||||
case Tool::Boolean: m_hdr_boolean->SetLabel(title(_L("Boolean"))); break;
|
||||
case Tool::Cut: m_hdr_cut->SetLabel(title(_L_CONTEXT("Cut", "Design"))); break;
|
||||
case Tool::Cut: m_hdr_cut->SetLabel(title(_L("Cut"))); break;
|
||||
case Tool::Axis: m_hdr_axis->SetLabel(title(_L("Axis"))); break;
|
||||
case Tool::CoordSys: m_hdr_coordsys->SetLabel(title(_L("Coordinate system"))); break;
|
||||
case Tool::SurfaceExtrude: m_hdr_surf_extrude->SetLabel(title(_L("Surface Extrude"))); break;
|
||||
@@ -11517,8 +11519,8 @@ void DesignPanel::open_tool(Tool t)
|
||||
case Tool::Transform: m_hdr_transform->SetLabel(title(_L("Transform"))); break;
|
||||
case Tool::Mirror: m_hdr_mirror->SetLabel(title(_L("Mirror"))); break;
|
||||
case Tool::Thicken: m_hdr_thicken->SetLabel(title(_L("Thicken"))); break;
|
||||
case Tool::Rib: m_hdr_rib->SetLabel(title(_L_CONTEXT("Rib", "Design"))); break;
|
||||
case Tool::Project: m_hdr_project->SetLabel(title(_L_CONTEXT("Project", "Design"))); break;
|
||||
case Tool::Rib: m_hdr_rib->SetLabel(title(_L("Rib"))); break;
|
||||
case Tool::Project: m_hdr_project->SetLabel(title(_L("Project"))); break;
|
||||
case Tool::DeleteFace: m_hdr_delete_face->SetLabel(title(_L("Delete Face"))); break;
|
||||
case Tool::Helix: m_hdr_helix->SetLabel(title(_L("Helix"))); break;
|
||||
case Tool::Mate: m_hdr_mate->SetLabel(title(_L("Mate"))); break;
|
||||
@@ -11755,7 +11757,7 @@ void DesignPanel::tool_cancel()
|
||||
show_move_card(false);
|
||||
feed_bodies(); // re-render the reverted placement
|
||||
update_action_bar();
|
||||
set_status(StatusKind::Info, _L("Move canceled"));
|
||||
set_status(StatusKind::Info, _L("Move cancelled"));
|
||||
return;
|
||||
}
|
||||
if (m_active == Tool::Insert) { cancel_insert(); return; }
|
||||
@@ -11885,7 +11887,7 @@ void DesignPanel::escape()
|
||||
return;
|
||||
}
|
||||
if (m_ui_mode == UiMode::Sketch) {
|
||||
set_status(StatusKind::Info, _L("Sketch kept — Confirm to commit it, Cancel to discard"));
|
||||
set_status(StatusKind::Info, _L("Sketch kept — Finish to commit it, Cancel to discard"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -11968,10 +11970,8 @@ void DesignPanel::do_undo_redo(bool redo)
|
||||
drop_selection();
|
||||
reset_edit_state();
|
||||
after_tree_edit(true); // refresh tree + viewport meshes + status from the restored doc
|
||||
const size_t depth = redo ? m_doc.redo_depth() : m_doc.undo_depth();
|
||||
set_status(StatusKind::Info, wxString::Format(redo ? _L_PLURAL("Redo (%zu more step)", "Redo (%zu more steps)", unsigned(depth))
|
||||
: _L_PLURAL("Undo (%zu more step)", "Undo (%zu more steps)", unsigned(depth)),
|
||||
depth));
|
||||
set_status(StatusKind::Info, wxString::Format(redo ? _L("Redo (%zu more)") : _L("Undo (%zu more)"),
|
||||
redo ? m_doc.redo_depth() : m_doc.undo_depth()));
|
||||
}
|
||||
|
||||
// --- Document variables panel ---------------------------------------------------------
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <iterator>
|
||||
#include "libslic3r/AppConfig.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp"
|
||||
@@ -77,6 +78,7 @@ static bool point_in_poly(const Vec2d& q, const std::vector<Vec2d>& poly);
|
||||
static bool ray_triangle(const Vec3d& ro, const Vec3d& rd, const Vec3d& v0, const Vec3d& v1,
|
||||
const Vec3d& v2, double& t);
|
||||
static double ray_segment_dist3(const Vec3d& ro, const Vec3d& rd, const Vec3d& a, const Vec3d& b);
|
||||
static double ray_axis_proj(const Linef3& ray, const Vec3d& anchor, const Vec3d& dir);
|
||||
|
||||
// Project a world-space point to canvas screen pixels (device px, GL viewport units;
|
||||
// origin top-left after the GL y-flip). Mirrors GLCanvas3D's world->screen pattern:
|
||||
@@ -1725,7 +1727,6 @@ void DesignSketchTool::open_polygon_side_editor(int fi)
|
||||
if (f.begin < 0 || f.begin >= int(m_entities.size())) return;
|
||||
const double side = (m_entities[f.begin].p1 - m_entities[f.begin].p0).norm();
|
||||
const wxPoint px(m_last_mouse_x, m_last_mouse_y);
|
||||
// TRN Design sketch: length of one side of a regular polygon (inline value editor title)
|
||||
on_inline_edit(px, side, _u8L("Side"),
|
||||
[this, fi](double v) { set_polygon_side(fi, v); },
|
||||
[]() {});
|
||||
@@ -3474,18 +3475,19 @@ static double display_edge_tol(const TopoDS_Shape& shape)
|
||||
void DesignSketchTool::refresh_body_edges()
|
||||
{
|
||||
const size_t n = m_solid_bodies != nullptr ? m_solid_bodies->size() : 0;
|
||||
bool rebuilt = n < m_body_edges_shape.size(); // a body went away
|
||||
m_body_edges.resize(n);
|
||||
m_body_edges_key.resize(n, nullptr);
|
||||
m_body_edges_shape.resize(n);
|
||||
m_body_edges_tol.resize(n, 0.0);
|
||||
for (size_t b = 0; b < n; ++b) {
|
||||
const void* key = body_key(int(b));
|
||||
if (key == m_body_edges_key[b] && key != nullptr)
|
||||
continue;
|
||||
m_body_edges_key[b] = key;
|
||||
m_body_edges[b].clear();
|
||||
if (key == nullptr)
|
||||
if (is_current_shape(int(b), m_body_edges_shape[b]))
|
||||
continue;
|
||||
rebuilt = rebuilt || !m_body_edges_shape[b].IsNull(); // a body sampled before was rebuilt
|
||||
const TopoDS_Shape& shape = (*m_solid_bodies)[b].shape;
|
||||
m_body_edges_shape[b] = shape;
|
||||
m_body_edges[b].clear();
|
||||
if (shape.IsNull())
|
||||
continue;
|
||||
m_body_edges_tol[b] = display_edge_tol(shape);
|
||||
try {
|
||||
m_body_edges[b] = GeometryEngine::display_edges(shape, m_body_edges_tol[b]);
|
||||
@@ -3493,15 +3495,36 @@ void DesignSketchTool::refresh_body_edges()
|
||||
m_body_edges[b].clear(); // an unsampleable edge costs its body the lines, nothing else
|
||||
}
|
||||
}
|
||||
// A pick expires once the body it was taken on is rebuilt, which renumbers its faces and
|
||||
// edges. Judged by that body's shape, not by these lines: a recompute that refreshed no lines
|
||||
// (adding a datum, say) may come before a pick on the bodies it built, and that pick holds.
|
||||
// The hover is resolved again on the next mouse move, so any rebuild just drops it.
|
||||
if (m_solid_sel != SolidSel::None && !is_current_shape(m_sel_body, m_sel_shape))
|
||||
clear_solid_selection();
|
||||
else if (rebuilt)
|
||||
m_pre = SolidPick{};
|
||||
// The face outlines of a rebuilt body let go of its shape, which may be a large import.
|
||||
const auto stale = [this](const FaceHighlight& h) { return !is_current_shape(h.body, h.shape); };
|
||||
if (stale(m_sel_hl)) m_sel_hl = FaceHighlight{};
|
||||
if (stale(m_pre_hl)) m_pre_hl = FaceHighlight{};
|
||||
m_hl_faces.erase(std::remove_if(m_hl_faces.begin(), m_hl_faces.end(), stale), m_hl_faces.end());
|
||||
}
|
||||
|
||||
// The identity of a body's current shape. Face ids and sampled edges hold while it does.
|
||||
const void* DesignSketchTool::body_key(int body) const
|
||||
const TopoDS_Shape* DesignSketchTool::body_shape(int body) const
|
||||
{
|
||||
if (m_solid_bodies == nullptr || body < 0 || body >= int(m_solid_bodies->size()))
|
||||
return nullptr;
|
||||
const TopoDS_Shape& shape = (*m_solid_bodies)[body].shape;
|
||||
return shape.IsNull() ? nullptr : shape.TShape().get();
|
||||
return shape.IsNull() ? nullptr : &shape;
|
||||
}
|
||||
|
||||
// Whether `sampled` is still the body's shape. The caches hold the shape itself rather than its
|
||||
// address, so the sampled TShape stays alive and no rebuilt body can be allocated in its place.
|
||||
// IsSame also compares the Location, which the sampled lines are in.
|
||||
bool DesignSketchTool::is_current_shape(int body, const TopoDS_Shape& sampled) const
|
||||
{
|
||||
const TopoDS_Shape* shape = body_shape(body);
|
||||
return shape != nullptr && sampled.IsSame(*shape);
|
||||
}
|
||||
|
||||
// View-facing ribbons along a body's polylines (shape coordinates), `hw` either side of the line
|
||||
@@ -3604,6 +3627,7 @@ void DesignSketchTool::clear_solid_selection()
|
||||
{
|
||||
m_solid_sel = SolidSel::None;
|
||||
m_sel_body = m_sel_face = m_sel_edge = -1;
|
||||
m_sel_shape = TopoDS_Shape();
|
||||
m_sel_edge_pts.clear();
|
||||
m_sel_edges_more.clear();
|
||||
m_sel_edges_more_pts.clear();
|
||||
@@ -3631,6 +3655,7 @@ void DesignSketchTool::select_body(int body)
|
||||
return;
|
||||
}
|
||||
m_sel_body = body;
|
||||
m_sel_shape = (*m_solid_bodies)[body].shape;
|
||||
m_sel_face = m_sel_edge = -1;
|
||||
m_sel_edge_pts.clear();
|
||||
m_sel_edges_more.clear();
|
||||
@@ -3642,7 +3667,7 @@ void DesignSketchTool::set_highlight_faces(const std::vector<std::pair<int, int>
|
||||
{
|
||||
std::map<int, std::vector<int>> by_body;
|
||||
for (const auto& [b, f] : faces)
|
||||
if (body_key(b) != nullptr)
|
||||
if (body_shape(b) != nullptr)
|
||||
by_body[b].push_back(f);
|
||||
// Reuse each body's entry, so re-sending the same faces resamples nothing.
|
||||
std::vector<FaceHighlight> next;
|
||||
@@ -3664,13 +3689,16 @@ DesignSketchTool::FaceHighlight DesignSketchTool::make_face_highlight(int body,
|
||||
std::sort(faces.begin(), faces.end());
|
||||
faces.erase(std::unique(faces.begin(), faces.end()), faces.end());
|
||||
h.faces = std::move(faces);
|
||||
h.key = body_key(body);
|
||||
if (h.key == nullptr || h.faces.empty())
|
||||
const TopoDS_Shape* shape = body_shape(body);
|
||||
if (shape == nullptr)
|
||||
return h;
|
||||
h.shape = *shape;
|
||||
if (h.faces.empty())
|
||||
return h;
|
||||
// Sampled at the body edges' tolerance; a whole body reuses its body edges.
|
||||
const TopoDS_Shape& shape = (*m_solid_bodies)[body].shape;
|
||||
const std::vector<TopoDS_Face> all = GeometryEngine::faces_of(shape);
|
||||
const bool cached = body < int(m_body_edges_key.size()) && m_body_edges_key[body] == h.key;
|
||||
const std::vector<TopoDS_Face> all = GeometryEngine::faces_of(*shape);
|
||||
const bool cached = body < int(m_body_edges_shape.size())
|
||||
&& is_current_shape(body, m_body_edges_shape[body]);
|
||||
if (cached && h.faces.size() == all.size() && h.faces.front() == 0 && h.faces.back() == int(all.size()) - 1) {
|
||||
h.edges = m_body_edges[body];
|
||||
return h;
|
||||
@@ -3682,7 +3710,7 @@ DesignSketchTool::FaceHighlight DesignSketchTool::make_face_highlight(int body,
|
||||
if (f >= 0 && f < int(all.size()))
|
||||
builder.Add(picked, all[f]);
|
||||
try {
|
||||
h.edges = GeometryEngine::display_edges(picked, cached ? m_body_edges_tol[body] : display_edge_tol(shape));
|
||||
h.edges = GeometryEngine::display_edges(picked, cached ? m_body_edges_tol[body] : display_edge_tol(*shape));
|
||||
} catch (const Standard_Failure&) {
|
||||
h.edges.clear(); // an edge that cannot be sampled costs the outline, never the fill
|
||||
}
|
||||
@@ -3692,12 +3720,13 @@ DesignSketchTool::FaceHighlight DesignSketchTool::make_face_highlight(int body,
|
||||
std::vector<std::pair<int, int>> DesignSketchTool::picked_faces() const
|
||||
{
|
||||
std::vector<std::pair<int, int>> out;
|
||||
if (body_key(m_sel_body) == nullptr)
|
||||
const TopoDS_Shape* shape = body_shape(m_sel_body);
|
||||
if (shape == nullptr)
|
||||
return out;
|
||||
if (m_solid_sel == SolidSel::Face && m_sel_face >= 0)
|
||||
out.emplace_back(m_sel_body, m_sel_face);
|
||||
else if (m_solid_sel == SolidSel::Whole)
|
||||
for (int f = 0, n = GeometryEngine::face_count((*m_solid_bodies)[m_sel_body].shape); f < n; ++f)
|
||||
for (int f = 0, n = GeometryEngine::face_count(*shape); f < n; ++f)
|
||||
out.emplace_back(m_sel_body, f);
|
||||
return out;
|
||||
}
|
||||
@@ -3706,7 +3735,7 @@ std::vector<std::pair<int, int>> DesignSketchTool::selected_faces() const
|
||||
{
|
||||
std::vector<std::pair<int, int>> out = picked_faces();
|
||||
for (const FaceHighlight& h : m_hl_faces)
|
||||
if (h.key != nullptr && h.key == body_key(h.body))
|
||||
if (is_current_shape(h.body, h.shape))
|
||||
for (int f : h.faces)
|
||||
out.emplace_back(h.body, f);
|
||||
std::sort(out.begin(), out.end());
|
||||
@@ -3714,12 +3743,83 @@ std::vector<std::pair<int, int>> DesignSketchTool::selected_faces() const
|
||||
return out;
|
||||
}
|
||||
|
||||
BoundingBoxf3 DesignSketchTool::fit_box(const GLVolumeCollection& volumes) const
|
||||
{
|
||||
BoundingBoxf3 box;
|
||||
const auto add_entity = [&box, this](const SketchEntity& e, const SketchPlane& plane) {
|
||||
bool closed = false;
|
||||
for (const Vec2d& p : entity_polyline(e, closed))
|
||||
box.merge(plane.to_world(p));
|
||||
};
|
||||
const auto add_entities = [&add_entity](const std::vector<SketchEntity>& ents, const SketchPlane& plane) {
|
||||
for (const SketchEntity& e : ents)
|
||||
add_entity(e, plane);
|
||||
};
|
||||
|
||||
// The selected faces, a whole body's included, from the pick mesh: it is in world
|
||||
// coordinates already, moved bodies and all.
|
||||
const std::vector<std::pair<int, int>> faces = selected_faces();
|
||||
if (!faces.empty() && m_solid_mesh != nullptr && m_solid_tri_face != nullptr && m_solid_tri_body != nullptr) {
|
||||
const indexed_triangle_set& its = m_solid_mesh->its;
|
||||
const size_t n = std::min({ its.indices.size(), m_solid_tri_face->size(), m_solid_tri_body->size() });
|
||||
for (size_t t = 0; t < n; ++t)
|
||||
if (std::binary_search(faces.begin(), faces.end(), std::make_pair((*m_solid_tri_body)[t], (*m_solid_tri_face)[t])))
|
||||
for (int i = 0; i < 3; ++i)
|
||||
box.merge(its.vertices[its.indices[t][i]].cast<double>());
|
||||
}
|
||||
if (m_solid_sel == SolidSel::Edge) {
|
||||
for (const Vec3d& p : m_sel_edge_pts)
|
||||
box.merge(p);
|
||||
for (const std::vector<Vec3d>& pts : m_sel_edges_more_pts)
|
||||
for (const Vec3d& p : pts)
|
||||
box.merge(p);
|
||||
} else if (m_solid_sel == SolidSel::Vertex)
|
||||
box.merge(m_sel_vertex_pt);
|
||||
// A committed sketch's picked region with its holes; a stroke outside any region, the sketch.
|
||||
if (m_display_pick >= 0)
|
||||
for (const DisplaySketch& d : m_display_sketches)
|
||||
if (d.feature == m_display_pick)
|
||||
add_entities(m_display_pick_region >= 0 ? selected_loop_entities() : d.entities, d.plane);
|
||||
if (m_active) {
|
||||
for (int i : m_selection)
|
||||
if (i >= 0 && i < int(m_entities.size()))
|
||||
add_entity(m_entities[i], m_plane);
|
||||
for (const auto& [i, role] : m_point_sel)
|
||||
if (i >= 0 && i < int(m_entities.size())) {
|
||||
const SketchEntity& e = m_entities[i];
|
||||
box.merge(m_plane.to_world(role == SketchPointRole::Center ? e.center :
|
||||
role == SketchPointRole::P1 ? e.p1 : e.p0));
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing selected: everything on show. A hidden body's volume is inactive.
|
||||
if (!box.defined) {
|
||||
for (const GLVolume* v : volumes.volumes)
|
||||
if (v->is_active)
|
||||
box.merge(v->transformed_bounding_box());
|
||||
for (const DisplaySketch& d : m_display_sketches)
|
||||
add_entities(d.entities, d.plane);
|
||||
if (m_active)
|
||||
add_entities(m_entities, m_plane);
|
||||
}
|
||||
|
||||
// A vertex has no size, nor has a sketch along its normal, and the camera cannot frame an
|
||||
// extent it does not see. Every axis gets at least a small neighbourhood.
|
||||
if (box.defined) {
|
||||
constexpr double min_extent = 10.; // mm
|
||||
const Vec3d grow = (Vec3d::Constant(min_extent) - box.size()).cwiseMax(0.) * 0.5;
|
||||
box.min -= grow;
|
||||
box.max += grow;
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
const DesignSketchTool::FaceHighlight& DesignSketchTool::cached_face_highlight(FaceHighlight& cache, int body,
|
||||
std::vector<int> faces) const
|
||||
{
|
||||
std::sort(faces.begin(), faces.end());
|
||||
faces.erase(std::unique(faces.begin(), faces.end()), faces.end());
|
||||
if (cache.body != body || cache.faces != faces || cache.key != body_key(body))
|
||||
if (cache.body != body || cache.faces != faces || !is_current_shape(body, cache.shape))
|
||||
cache = make_face_highlight(body, std::move(faces));
|
||||
return cache;
|
||||
}
|
||||
@@ -3730,7 +3830,7 @@ const DesignSketchTool::FaceHighlight& DesignSketchTool::cached_face_highlight(F
|
||||
// version: the line alone, thinner and fainter.
|
||||
void DesignSketchTool::render_face_outline(const FaceHighlight& h, bool quiet)
|
||||
{
|
||||
if (m_body_edges_hidden || h.key == nullptr || h.key != body_key(h.body) || !body_pickable(h.body))
|
||||
if (m_body_edges_hidden || !is_current_shape(h.body, h.shape) || !body_pickable(h.body))
|
||||
return; // hidden, or the body was rebuilt and these face ids are stale
|
||||
|
||||
const Camera& cam = wxGetApp().plater()->get_camera();
|
||||
@@ -4007,6 +4107,7 @@ bool DesignSketchTool::handle_solid_click(GLCanvas3D& canvas, const wxMouseEvent
|
||||
m_sel_edges_more_pts.clear();
|
||||
|
||||
m_sel_body = p.body;
|
||||
m_sel_shape = (*m_solid_bodies)[p.body].shape; // resolve_solid_pick checked p.body
|
||||
m_sel_face = p.face;
|
||||
m_sel_edge = p.edge;
|
||||
m_sel_edge_pts = std::move(p.edge_pts);
|
||||
@@ -5285,6 +5386,144 @@ void DesignSketchTool::drag_rib_handle(GLCanvas3D& canvas, const wxMouseEvent& e
|
||||
}
|
||||
|
||||
// ---- Reference/base planes (Onshape-style default planes) -----------------------------
|
||||
namespace {
|
||||
// One level of a BSP tree whose splitters are the planes themselves, in order: the painter's
|
||||
// algorithm made exact for polygons that cross. Pieces of plane k (and of any plane lying in it)
|
||||
// are in the splitter; every other piece is wholly on one side or is cut in two. Far side, then the
|
||||
// splitter's own pieces, then the near side is back to front. A piece is only cut when it has
|
||||
// corners strictly on both sides, so neither half can be degenerate.
|
||||
void paint_back_to_front(std::vector<PlanePiece>&& in, size_t k, const std::vector<SketchPlane>& planes, double eps,
|
||||
const Vec3d& eye, const Vec3d& forward, bool perspective, std::vector<PlanePiece>& out)
|
||||
{
|
||||
if (in.empty())
|
||||
return;
|
||||
if (k == planes.size()) { // not reached: every piece is in the splitter at its own plane's level
|
||||
std::move(in.begin(), in.end(), std::back_inserter(out));
|
||||
return;
|
||||
}
|
||||
// The square's own normal: SketchPlane::normal is reversed on XZ, and the side tests only need
|
||||
// one consistent choice.
|
||||
const Vec3d n = planes[k].x_axis.cross(planes[k].y_axis).normalized();
|
||||
const double d = n.dot(planes[k].origin);
|
||||
std::vector<PlanePiece> front, back, on;
|
||||
for (PlanePiece& piece : in) {
|
||||
if (piece.plane == int(k)) {
|
||||
on.push_back(std::move(piece));
|
||||
continue;
|
||||
}
|
||||
std::vector<double> s;
|
||||
int sides = 0;
|
||||
for (const Vec3d& c : piece.corners) {
|
||||
s.push_back(n.dot(c) - d);
|
||||
sides |= s.back() > eps ? 1 : s.back() < -eps ? 2 : 0;
|
||||
}
|
||||
if (sides == 0)
|
||||
on.push_back(std::move(piece));
|
||||
else if (sides == 1)
|
||||
front.push_back(std::move(piece));
|
||||
else if (sides == 2)
|
||||
back.push_back(std::move(piece));
|
||||
else {
|
||||
PlanePiece f{ piece.plane, {} }, b{ piece.plane, {} };
|
||||
const size_t m = piece.corners.size();
|
||||
for (size_t i = 0; i < m; ++i) {
|
||||
const size_t j = (i + 1) % m;
|
||||
const Vec3d& a = piece.corners[i];
|
||||
if (s[i] >= -eps) f.corners.push_back(a);
|
||||
if (s[i] <= eps) b.corners.push_back(a);
|
||||
if ((s[i] > eps && s[j] < -eps) || (s[i] < -eps && s[j] > eps)) {
|
||||
const Vec3d x = a + (piece.corners[j] - a) * (s[i] / (s[i] - s[j]));
|
||||
f.corners.push_back(x);
|
||||
b.corners.push_back(x);
|
||||
}
|
||||
}
|
||||
front.push_back(std::move(f));
|
||||
back.push_back(std::move(b));
|
||||
}
|
||||
}
|
||||
// An orthographic eye is at infinity behind the view direction. Camera::get_position() is a
|
||||
// finite point there and can sit on the wrong side of a plane, so only the direction counts.
|
||||
const bool eye_in_front = perspective ? n.dot(eye) - d > 0. : n.dot(forward) < 0.;
|
||||
paint_back_to_front(std::move(eye_in_front ? back : front), k + 1, planes, eps, eye, forward, perspective, out);
|
||||
std::move(on.begin(), on.end(), std::back_inserter(out));
|
||||
paint_back_to_front(std::move(eye_in_front ? front : back), k + 1, planes, eps, eye, forward, perspective, out);
|
||||
}
|
||||
|
||||
Vec2d in_frame(const SketchPlane& p, const Vec3d& x) { return Vec2d((x - p.origin).dot(p.x_axis), (x - p.origin).dot(p.y_axis)); }
|
||||
|
||||
double plane_distance(const SketchPlane& p, const Vec3d& x) { return p.x_axis.cross(p.y_axis).normalized().dot(x - p.origin); }
|
||||
|
||||
// Shrink the segment ab, which lies in p, to its part inside p's square (Liang-Barsky). False when
|
||||
// nothing is left.
|
||||
bool clip_to_square(Vec3d& a, Vec3d& b, const SketchPlane& p, double half, double eps)
|
||||
{
|
||||
const Vec2d s = in_frame(p, a), d = in_frame(p, b) - s;
|
||||
double t0 = 0., t1 = 1.;
|
||||
for (int axis = 0; axis < 2; ++axis)
|
||||
for (double sign : { -1., 1. }) { // keep sign * (s + t * d)[axis] <= half
|
||||
const double room = half + eps - sign * s[axis], rate = sign * d[axis];
|
||||
if (rate > 0.)
|
||||
t1 = std::min(t1, room / rate);
|
||||
else if (rate < 0.)
|
||||
t0 = std::max(t0, room / rate);
|
||||
else if (room < 0.)
|
||||
return false;
|
||||
}
|
||||
if (t1 - t0 < 1e-9)
|
||||
return false;
|
||||
const Vec3d ab = b - a;
|
||||
b = a + ab * t1;
|
||||
a = a + ab * t0;
|
||||
return true;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<PlanePiece> planes_back_to_front(const std::vector<SketchPlane>& planes, double half,
|
||||
const Vec3d& eye, const Vec3d& forward, bool perspective)
|
||||
{
|
||||
std::vector<PlanePiece> squares;
|
||||
for (int i = 0; i < int(planes.size()); ++i) {
|
||||
const SketchPlane& p = planes[i];
|
||||
squares.push_back({ i, { p.to_world(Vec2d(-half, -half)), p.to_world(Vec2d(half, -half)),
|
||||
p.to_world(Vec2d(half, half)), p.to_world(Vec2d(-half, half)) } });
|
||||
}
|
||||
const double eps = 1e-6 * std::max(half, 1.);
|
||||
std::vector<PlanePiece> out;
|
||||
paint_back_to_front(std::move(squares), 0, planes, eps, eye, forward, perspective, out);
|
||||
|
||||
// What to outline: the piece's share of its square's border, and of where it crosses another
|
||||
// square. Every other edge is a cut lying in the plane that made it, but the cut ran along that
|
||||
// whole infinite plane, so it is clipped to that plane's square: a datum that never reaches a base
|
||||
// plane gets no line across it. A piece is convex, so an edge with both ends on one side line of
|
||||
// its square lies along that side.
|
||||
for (PlanePiece& piece : out) {
|
||||
const SketchPlane& own = planes[piece.plane];
|
||||
for (size_t i = 0; i < piece.corners.size(); ++i) {
|
||||
const Vec3d& a = piece.corners[i];
|
||||
const Vec3d& b = piece.corners[(i + 1) % piece.corners.size()];
|
||||
const Vec2d fa = in_frame(own, a), fb = in_frame(own, b);
|
||||
bool border = false;
|
||||
for (int axis = 0; axis < 2; ++axis)
|
||||
for (double side : { -half, half })
|
||||
border = border || (std::abs(fa[axis] - side) <= eps && std::abs(fb[axis] - side) <= eps);
|
||||
if (border) {
|
||||
piece.lines.emplace_back(a, b);
|
||||
continue;
|
||||
}
|
||||
for (int k = 0; k < int(planes.size()); ++k) {
|
||||
auto in_k = [&](const Vec3d& x) { return std::abs(plane_distance(planes[k], x)) <= 2. * eps; };
|
||||
// Not a plane the piece itself lies in: clipped to its own square, a cut is kept whole.
|
||||
if (k == piece.plane || !in_k(a) || !in_k(b) || std::all_of(piece.corners.begin(), piece.corners.end(), in_k))
|
||||
continue;
|
||||
Vec3d ca = a, cb = b;
|
||||
if (clip_to_square(ca, cb, planes[k], half, eps))
|
||||
piece.lines.emplace_back(ca, cb);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void DesignSketchTool::set_base_pick(std::vector<SketchPlane> planes, std::vector<int> bases,
|
||||
std::vector<std::string> labels)
|
||||
{
|
||||
@@ -5320,49 +5559,72 @@ double DesignSketchTool::dbp_half_extent() const
|
||||
return half;
|
||||
}
|
||||
|
||||
// Draw the reference planes as large translucent labelled squares; the hovered one brightens.
|
||||
// Draw the reference planes as labelled translucent squares outlined in their own hue; the hovered
|
||||
// one brightens. Depth testing is off (they overlay the bed and any bodies), so draw order is the
|
||||
// blend order — and the planes cross, so they go down piece by piece, back to front.
|
||||
void DesignSketchTool::render_base_pick()
|
||||
{
|
||||
if (!m_dbp_active || m_dbp_planes.empty()) return;
|
||||
using EPT = GLModel::Geometry::EPrimitiveType;
|
||||
using EVL = GLModel::Geometry::EVertexLayout;
|
||||
const double H = dbp_half_extent();
|
||||
// Onshape-ish per-plane tints: XY blue, XZ green, YZ red (keyed by base index 0/1/2; datums grey).
|
||||
auto tint = [](int base, bool hot) -> ColorRGBA {
|
||||
float a = hot ? 0.10f : 0.047f; // base planes kept faint (reduced ~2/3 from 0.30/0.14)
|
||||
if (base == 0) return ColorRGBA(0.30f, 0.55f, 0.95f, a);
|
||||
if (base == 1) return ColorRGBA(0.35f, 0.80f, 0.45f, a);
|
||||
if (base == 2) return ColorRGBA(0.92f, 0.42f, 0.42f, a);
|
||||
return ColorRGBA(0.70f, 0.72f, 0.78f, a);
|
||||
// Two layers of alpha a blended in either order differ by only a^2 of their colour difference,
|
||||
// so a faint fill hides which plane is in front however well the pieces are sorted: at 0.16 that
|
||||
// is under 3%, and the crossing planes read as one grey smear. At 0.35 it is ~12%, and the bed
|
||||
// grid still reads through all three.
|
||||
constexpr float kFillAlpha = 0.35f, kFillAlphaHot = 0.50f;
|
||||
constexpr float kLineAlpha = 0.90f, kLineAlphaHot = 1.00f;
|
||||
// Onshape-ish per-plane hues: XY blue, XZ green, YZ red (keyed by base index 0/1/2; datums grey).
|
||||
auto hue = [](int base) -> ColorRGBA {
|
||||
if (base == 0) return ColorRGBA(0.30f, 0.55f, 0.95f, 1.0f);
|
||||
if (base == 1) return ColorRGBA(0.35f, 0.80f, 0.45f, 1.0f);
|
||||
if (base == 2) return ColorRGBA(0.92f, 0.42f, 0.42f, 1.0f);
|
||||
return ColorRGBA(0.70f, 0.72f, 0.78f, 1.0f);
|
||||
};
|
||||
const Camera& cam = wxGetApp().plater()->get_camera();
|
||||
const Vec3d vd = cam.get_dir_forward();
|
||||
const double hw = 1.5 / std::max(cam.get_zoom(), 1e-6); // outline ribbon, as on datum planes
|
||||
glsafe(::glDisable(GL_DEPTH_TEST));
|
||||
glsafe(::glDisable(GL_CULL_FACE));
|
||||
glsafe(::glEnable(GL_BLEND)); // alpha is ignored without this
|
||||
glsafe(::glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA));
|
||||
const SketchPlane saved_plane = m_plane;
|
||||
for (size_t i = 0; i < m_dbp_planes.size(); ++i) {
|
||||
const SketchPlane& p = m_dbp_planes[i];
|
||||
const Vec3d q0 = p.to_world(Vec2d(-H, -H)), q1 = p.to_world(Vec2d(H, -H)),
|
||||
q2 = p.to_world(Vec2d(H, H)), q3 = p.to_world(Vec2d(-H, H));
|
||||
GLModel::Geometry quad; quad.format = { EPT::Triangles, EVL::P3 };
|
||||
quad.add_vertex((Vec3f)q0.cast<float>()); quad.add_vertex((Vec3f)q1.cast<float>());
|
||||
quad.add_vertex((Vec3f)q2.cast<float>()); quad.add_vertex((Vec3f)q3.cast<float>());
|
||||
quad.add_triangle(0, 1, 2); quad.add_triangle(0, 2, 3);
|
||||
GLModel m; m.init_from(std::move(quad));
|
||||
const bool hot = (int(i) == m_dbp_hover);
|
||||
const int base = (i < m_dbp_base.size()) ? m_dbp_base[i] : -1;
|
||||
m.set_color(tint(base, hot));
|
||||
m.render();
|
||||
for (const PlanePiece& piece : planes_back_to_front(m_dbp_planes, H, cam.get_position(), vd,
|
||||
cam.get_type() == Camera::EType::Perspective)) {
|
||||
const bool hot = piece.plane == m_dbp_hover;
|
||||
ColorRGBA col = hue(piece.plane < int(m_dbp_base.size()) ? m_dbp_base[piece.plane] : -1);
|
||||
GLModel::Geometry fill; fill.format = { EPT::Triangles, EVL::P3 };
|
||||
for (const Vec3d& q : piece.corners) fill.add_vertex((Vec3f)q.cast<float>());
|
||||
for (unsigned int i = 1; i + 1 < piece.corners.size(); ++i) fill.add_triangle(0, i, i + 1); // convex: a fan
|
||||
GLModel fm; fm.init_from(std::move(fill));
|
||||
col.a(hot ? kFillAlphaHot : kFillAlpha);
|
||||
fm.set_color(col);
|
||||
fm.render();
|
||||
|
||||
// Label near the top-left corner, drawn in the plane (draw_text lifts through m_plane).
|
||||
if (i < m_dbp_labels.size() && !m_dbp_labels[i].empty()) {
|
||||
m_plane = p;
|
||||
const double th = H * 0.10;
|
||||
const ColorRGBA lc = tint(base, true); ColorRGBA lcs(lc.r(), lc.g(), lc.b(), 1.0f);
|
||||
draw_text(m_line_model, m_dbp_labels[i], Vec2d(-H + th * 2.0, H - th * 1.6), th, lcs);
|
||||
// Its outline and crossing lines go down with it, so a line behind another plane is tinted
|
||||
// by it exactly like the plane it lies on.
|
||||
std::vector<std::vector<Vec3d>> strokes;
|
||||
for (const auto& [a, b] : piece.lines) strokes.push_back({ a, b });
|
||||
GLModel::Geometry outline;
|
||||
append_ribbons(outline, -1, strokes, vd, Vec3d::Zero(), hw); // body -1: already world coordinates
|
||||
if (!outline.is_empty()) {
|
||||
GLModel om; om.init_from(std::move(outline));
|
||||
col.a(hot ? kLineAlphaHot : kLineAlpha);
|
||||
om.set_color(col);
|
||||
om.render();
|
||||
}
|
||||
}
|
||||
m_plane = saved_plane; // draw_text renders each label immediately (draw_strokes self-renders)
|
||||
|
||||
// Label near the top-left corner, drawn in the plane (draw_text lifts through m_plane). Labels
|
||||
// are ImGui chips, on top of the planes whatever the order here.
|
||||
const SketchPlane saved_plane = m_plane;
|
||||
const double th = H * 0.10;
|
||||
for (size_t i = 0; i < m_dbp_planes.size() && i < m_dbp_labels.size(); ++i) {
|
||||
if (m_dbp_labels[i].empty()) continue;
|
||||
m_plane = m_dbp_planes[i];
|
||||
draw_text(m_line_model, m_dbp_labels[i], Vec2d(-H + th * 2.0, H - th * 1.6), th,
|
||||
hue(i < m_dbp_base.size() ? m_dbp_base[i] : -1));
|
||||
}
|
||||
m_plane = saved_plane;
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
}
|
||||
|
||||
@@ -5375,7 +5637,7 @@ int DesignSketchTool::hit_test_base_pick(GLCanvas3D& canvas, const wxMouseEvent&
|
||||
|
||||
// THE LABEL WINS, and it has to. Each plane's name is a screen-space chip centred on its
|
||||
// own in-plane anchor, and it is the one part of a base plane a user aims at deliberately —
|
||||
// the quads are near-transparent and overlap everywhere. Ray-casting the quads alone made
|
||||
// the quads are translucent and overlap everywhere. Ray-casting the quads alone made
|
||||
// the labels pure decoration: on a fresh document at 1920x1060, clicking "XY" reported
|
||||
// "XZ plane selected", because the XZ quad happens to sit in front at that pixel. Nothing
|
||||
// about the click was ambiguous to the user; they clicked the word XY.
|
||||
@@ -5558,19 +5820,22 @@ bool DesignSketchTool::hit_test_move_arrow(GLCanvas3D& canvas, const wxMouseEven
|
||||
axis = best; return true;
|
||||
}
|
||||
|
||||
// Skew-line closest point of the mouse ray to the axis line through the ORIGINAL centroid
|
||||
// -> signed offset along that axis (no clamp; a body can move either way).
|
||||
void DesignSketchTool::drag_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis)
|
||||
// Record how far along the arrow it was grabbed (NaN while the camera looks down the axis; the
|
||||
// first drag sample that projects stands in), so the body's centre does not snap to the grab.
|
||||
void DesignSketchTool::grab_move_arrow(int axis, const Linef3& ray)
|
||||
{
|
||||
const Linef3 r = canvas.mouse_ray(Point(evt.GetX(), evt.GetY()));
|
||||
const Vec3d ro = r.a, rd = r.b - r.a;
|
||||
const Vec3d axes[3] = { Vec3d::UnitX(), Vec3d::UnitY(), Vec3d::UnitZ() };
|
||||
const Vec3d e = axes[axis];
|
||||
const Vec3d w0 = m_mv_base - ro;
|
||||
const double a = e.dot(e), b = e.dot(rd), c = rd.dot(rd), dd = e.dot(w0), ee = rd.dot(w0);
|
||||
const double denom = a * c - b * b;
|
||||
if (std::abs(denom) < 1e-7) return; // camera ∥ axis: leave offset as-is
|
||||
m_mv_offset[axis] = (b * ee - c * dd) / denom;
|
||||
m_mv_drag = axis;
|
||||
m_mv_grab_along = ray_axis_proj(ray, m_mv_base + m_mv_offset, Vec3d::Unit(axis));
|
||||
}
|
||||
|
||||
// Slide the body along the grabbed axis by the cursor's travel (no clamp; it can move either way).
|
||||
void DesignSketchTool::drag_move_arrow(const Linef3& ray)
|
||||
{
|
||||
if (m_mv_drag < 0 || m_mv_drag > 2) return;
|
||||
const double proj = ray_axis_proj(ray, m_mv_base + m_mv_offset, Vec3d::Unit(m_mv_drag));
|
||||
if (std::isnan(proj)) return; // camera ∥ axis: leave offset as-is
|
||||
if (std::isnan(m_mv_grab_along)) { m_mv_grab_along = proj; return; }
|
||||
m_mv_offset[m_mv_drag] += proj - m_mv_grab_along;
|
||||
if (on_body_move_changed) on_body_move_changed(m_mv_body, compose_move_xform());
|
||||
}
|
||||
|
||||
@@ -5940,21 +6205,10 @@ int DesignSketchTool::hit_test_hole_handle(GLCanvas3D& canvas, const wxMouseEven
|
||||
return best;
|
||||
}
|
||||
|
||||
// Skew-line closest point of the mouse ray to an axis (anchor + t*dir) -> signed distance along
|
||||
// dir. NaN when the camera is ~parallel to the axis (no meaningful projection).
|
||||
double DesignSketchTool::hole_axis_proj(GLCanvas3D& canvas, const wxMouseEvent& evt,
|
||||
const Vec3d& anchor, const Vec3d& dir) const
|
||||
{
|
||||
const Linef3 r = canvas.mouse_ray(Point(evt.GetX(), evt.GetY()));
|
||||
const Vec3d ro = r.a, rd = r.b - r.a;
|
||||
const Vec3d e = dir;
|
||||
const Vec3d w0 = anchor - ro;
|
||||
const double a = e.dot(e), b = e.dot(rd), c = rd.dot(rd), dd = e.dot(w0), ee = rd.dot(w0);
|
||||
const double denom = a * c - b * b;
|
||||
// Relative near-parallel guard: when the camera ray is ~along the axis (e.g. the depth axis in
|
||||
// top view) denom collapses; a tiny absolute floor lets a huge, unstable projection through.
|
||||
if (std::abs(denom) < 1e-4 * std::max(a * c, 1e-12)) return std::nan("");
|
||||
return (b * ee - c * dd) / denom;
|
||||
return ray_axis_proj(canvas.mouse_ray(Point(evt.GetX(), evt.GetY())), anchor, dir);
|
||||
}
|
||||
|
||||
void DesignSketchTool::start_hole_drag(GLCanvas3D& canvas, const wxMouseEvent& evt, int which)
|
||||
@@ -9845,7 +10099,7 @@ std::string DesignSketchTool::build_readout() const
|
||||
std::string out = b;
|
||||
// Tell the user how to end a polyline chain — there's no other affordance for it.
|
||||
if (m_mode == Mode::Polyline && m_points.size() >= 2)
|
||||
out += " " + _u8L("right-click or double-click to finish, click start to close");
|
||||
out += " right-click or double-click to finish, click start to close";
|
||||
return out;
|
||||
}
|
||||
if (!m_active) return std::string();
|
||||
@@ -9918,6 +10172,21 @@ static double ray_segment_dist3(const Vec3d& ro, const Vec3d& rd, const Vec3d& a
|
||||
return (pr - ps).norm();
|
||||
}
|
||||
|
||||
// Skew-line closest point of the mouse ray to an axis (anchor + t*dir) -> signed distance along
|
||||
// dir. NaN when the camera is ~parallel to the axis (no meaningful projection).
|
||||
static double ray_axis_proj(const Linef3& ray, const Vec3d& anchor, const Vec3d& dir)
|
||||
{
|
||||
const Vec3d ro = ray.a, rd = ray.vector();
|
||||
const Vec3d e = dir;
|
||||
const Vec3d w0 = anchor - ro;
|
||||
const double a = e.dot(e), b = e.dot(rd), c = rd.dot(rd), dd = e.dot(w0), ee = rd.dot(w0);
|
||||
const double denom = a * c - b * b;
|
||||
// Relative near-parallel guard: when the camera ray is ~along the axis (e.g. the depth axis in
|
||||
// top view) denom collapses; a tiny absolute floor lets a huge, unstable projection through.
|
||||
if (std::abs(denom) < 1e-4 * std::max(a * c, 1e-12)) return std::nan("");
|
||||
return (b * ee - c * dd) / denom;
|
||||
}
|
||||
|
||||
// Screen-plane distance from p to a sketch entity, for click picking in Constrain
|
||||
// mode. Circles/arcs measure distance to the ring; points to their position.
|
||||
static double entity_pick_dist(const Vec2d& p, const SketchEntity& e)
|
||||
@@ -10316,14 +10585,14 @@ std::vector<int> DesignSketchTool::connected_loop(int seed) const
|
||||
// the offer was excluded in sketch mode wholesale so a right-click could end a polyline chain,
|
||||
// abandon an anchor or exit a tool. That made every sketch row in the atlas unreachable.
|
||||
// The honest test is not "which mode are we in" but "did the tool actually USE this right-click",
|
||||
// and only the tool knows. Wrapping on_mouse records that once, for every terminator, instead of
|
||||
// threading a flag through the twenty-odd sites that consume a RightDown.
|
||||
// and only the tool knows: take_right_click returns it for every terminator, from the
|
||||
// twenty-odd sites that consume a RightDown.
|
||||
// Right-click abandons the anchor a draw tool has down. With NOTHING down there is nothing to
|
||||
// abandon — and consuming the click anyway made the offer unreachable from every armed draw tool:
|
||||
// on_mouse records the consumption in m_right_consumed and DesignCanvas's RIGHT_UP handler
|
||||
// suppresses the menu whenever it is set, so right-click became a no-op that also hid the one door
|
||||
// to half the vocabulary (47 of 86 verbs have no shortcut). Measured on the rig: with Line armed,
|
||||
// two right-clicks in a row produced no menu and no tool change; only Escape freed it.
|
||||
// DesignCanvas's RIGHT_UP handler suppresses the menu whenever the tool used the click, so
|
||||
// right-click became a no-op that also hid the one door to half the vocabulary (47 of 86 verbs
|
||||
// have no shortcut). Measured on the rig: with Line armed, two right-clicks in a row produced no
|
||||
// menu and no tool change; only Escape freed it.
|
||||
// Same rule as xmh6, which said it for the selection: clearing nothing is not a gesture
|
||||
// terminator. ghcz.
|
||||
bool DesignSketchTool::right_abandon()
|
||||
@@ -10335,12 +10604,24 @@ bool DesignSketchTool::right_abandon()
|
||||
return true;
|
||||
}
|
||||
|
||||
// The camera follows Preferences > Control here as in Prepare, so no tool may take a gesture the
|
||||
// camera owns. A right press may start whatever drag action the right button is given, and
|
||||
// whether it did is known only at the release: ending a chain on the press made every pan or
|
||||
// orbit started there end the chain too. The press goes to the camera and is kept;
|
||||
// DesignCanvas's RIGHT_UP handler replays it through take_right_click when it was a click.
|
||||
bool DesignSketchTool::on_mouse(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
{
|
||||
const bool consumed = on_mouse_impl(evt, canvas);
|
||||
if (evt.RightDown())
|
||||
m_right_consumed = consumed;
|
||||
return consumed;
|
||||
if (evt.RightDown()) {
|
||||
m_right_press = evt;
|
||||
return false;
|
||||
}
|
||||
return on_mouse_impl(evt, canvas);
|
||||
}
|
||||
|
||||
bool DesignSketchTool::take_right_click(GLCanvas3D& canvas)
|
||||
{
|
||||
auto press = std::exchange(m_right_press, {});
|
||||
return press && on_mouse_impl(*press, canvas);
|
||||
}
|
||||
|
||||
bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
@@ -10414,7 +10695,7 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
// exits move mode. A LeftDown that misses the arrows falls through to solid re-pick.
|
||||
if (m_mv_active) {
|
||||
if (m_mv_drag >= 0 && evt.Dragging() && evt.LeftIsDown()) {
|
||||
if (m_mv_drag < 3) drag_move_arrow(canvas, evt, m_mv_drag);
|
||||
if (m_mv_drag < 3) drag_move_arrow(canvas.mouse_ray(Point(evt.GetX(), evt.GetY())));
|
||||
else drag_move_arc(canvas, evt, m_mv_drag - 3);
|
||||
return true;
|
||||
}
|
||||
@@ -10432,7 +10713,8 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
if (evt.LeftDown()) {
|
||||
int axis = -1;
|
||||
if (hit_test_move_arrow(canvas, evt, axis)) { // translate arrows win over rings
|
||||
m_mv_drag = axis; m_mv_press_x = evt.GetX(); m_mv_press_y = evt.GetY();
|
||||
grab_move_arrow(axis, canvas.mouse_ray(Point(evt.GetX(), evt.GetY())));
|
||||
m_mv_press_x = evt.GetX(); m_mv_press_y = evt.GetY();
|
||||
return true;
|
||||
}
|
||||
if (hit_test_move_arc(canvas, evt, axis)) {
|
||||
@@ -11289,9 +11571,9 @@ bool DesignSketchTool::on_mouse_impl(wxMouseEvent& evt, GLCanvas3D& canvas)
|
||||
return true;
|
||||
}
|
||||
if (evt.RightDown()) {
|
||||
// Hand the click back (return false) so the offer opens: the m_right_consumed flag
|
||||
// this return value feeds means "the tool USED this right-click", and a plain
|
||||
// right-click in Select mode is not a gesture terminator.
|
||||
// Hand the click back (return false) so the offer opens: take_right_click returns
|
||||
// this value as "the tool USED this right-click", and a plain right-click in Select
|
||||
// mode is not a gesture terminator.
|
||||
//
|
||||
// But do NOT drop the selection on the way out. The offer menu describes WHAT IS
|
||||
// SELECTED, so clearing first guaranteed it could only ever describe nothing: select
|
||||
|
||||
@@ -2,26 +2,31 @@
|
||||
#define slic3r_DesignSketchTool_hpp_
|
||||
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Line.hpp"
|
||||
#include "libslic3r/CAD/SketchEngine.hpp"
|
||||
#include "libslic3r/CAD/CadDocument.hpp" // CadBody for per-body solid picking
|
||||
#include <TopoDS_Shape.hxx>
|
||||
#include "libslic3r/CAD/SketchInference.hpp"
|
||||
#include "slic3r/GUI/GLModel.hpp"
|
||||
#include "slic3r/GUI/GLSelectionRectangle.hpp" // left-drag rubber band over the committed bodies
|
||||
#include <Eigen/Core>
|
||||
#include <cstddef>
|
||||
#include <wx/event.h>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
class wxMouseEvent;
|
||||
class wxPoint;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class TriangleMesh; // fwd (libslic3r) — solid-pick mesh, non-owning pointer
|
||||
class GLVolumeCollection;
|
||||
|
||||
namespace GUI {
|
||||
|
||||
@@ -50,6 +55,22 @@ inline ColorRGBA design_idle_face_color()
|
||||
{
|
||||
return ColorRGBA(0.72f, 0.76f, 0.80f, 0.14f);
|
||||
}
|
||||
|
||||
// A convex piece of the square drawn on planes[plane], and the segments to outline with it: its share
|
||||
// of the square's border and of the lines where it crosses the other squares.
|
||||
struct PlanePiece
|
||||
{
|
||||
int plane;
|
||||
std::vector<Vec3d> corners;
|
||||
std::vector<std::pair<Vec3d, Vec3d>> lines;
|
||||
};
|
||||
// The squares of half-extent `half` on `planes`, cut where they cross one another and ordered back
|
||||
// to front for an eye at `eye` (perspective) or looking along `forward` (orthographic). Translucent
|
||||
// planes that cross cannot be drawn in any per-plane order: each is partly in front of and partly
|
||||
// behind the others. Drawn piece by piece in this order, each one tints only what is behind it.
|
||||
std::vector<PlanePiece> planes_back_to_front(const std::vector<SketchPlane>& planes, double half,
|
||||
const Vec3d& eye, const Vec3d& forward, bool perspective);
|
||||
|
||||
class DesignSketchTool {
|
||||
public:
|
||||
enum class Mode { Select, Dimension, Polyline, Line, CornerRect, CenterRect, ObliqueRect,
|
||||
@@ -149,10 +170,10 @@ public:
|
||||
// Right-click on a draw tool: true when an in-progress anchor was abandoned, false when
|
||||
// there was nothing to abandon — and false is what lets the offer menu open. ghcz.
|
||||
bool right_abandon();
|
||||
// True if the LAST right-press was consumed as a gesture terminator (end a polyline chain,
|
||||
// abandon an anchor, exit a tool). Read-and-clear: the canvas asks on the matching release to
|
||||
// decide whether that right-click was the user's, in which case it opens the offer.
|
||||
bool take_right_consumed() { const bool b = m_right_consumed; m_right_consumed = false; return b; }
|
||||
// Replays and clears the right press on_mouse kept for the camera: true if the tool used it as
|
||||
// a gesture terminator (end a polyline chain, abandon an anchor), so no offer opens.
|
||||
bool take_right_click(GLCanvas3D& canvas);
|
||||
void drop_right_click() { m_right_press.reset(); }
|
||||
void render(GLCanvas3D& canvas);
|
||||
// The in-canvas value field, drawn by render() before any early return. Owned by
|
||||
// DesignCanvas; null until it sets it. Not a window — see SketchInlineEditor.hpp.
|
||||
@@ -207,6 +228,9 @@ public:
|
||||
// their edges do not float over the preview.
|
||||
void set_body_edges_hidden(bool h) { m_body_edges_hidden = h; }
|
||||
void clear_solid_selection();
|
||||
// Resample the edges of each body whose shape changed and drop those of bodies that are gone.
|
||||
// Also expires a pick or hover on a rebuilt body. The canvas calls it whenever it gets the bodies.
|
||||
void refresh_body_edges();
|
||||
bool has_solid_selection() const { return m_solid_sel != SolidSel::None; }
|
||||
// Every picked edge when the selection is an edge set (Shift/Ctrl+click adds and removes
|
||||
// edges of the same body): the earlier picks first, the last-clicked edge at the end.
|
||||
@@ -221,6 +245,11 @@ public:
|
||||
void set_highlight_faces(const std::vector<std::pair<int, int>>& faces);
|
||||
// Every face drawn as selected, sorted: the committed pick's and the still-valid ones above.
|
||||
std::vector<std::pair<int, int>> selected_faces() const;
|
||||
// What the canvas's Fit button frames: the selection — faces (the Feature tree's included), a
|
||||
// body, edges, a vertex, a sketch region, the live sketch's picked entities — else everything
|
||||
// on show: the visible bodies, the preview of the feature being edited and every sketch.
|
||||
// Undefined when the tab shows none of it. `volumes` are the canvas's: bodies and preview.
|
||||
BoundingBoxf3 fit_box(const GLVolumeCollection& volumes) const;
|
||||
|
||||
// Move-body gizmo (M5): translate a whole body with three world-axis drag arrows
|
||||
// (X red / Y green / Z blue) anchored at the body centroid. Display-only — the host
|
||||
@@ -232,6 +261,9 @@ public:
|
||||
void clear_move_gizmo();
|
||||
bool moving_body() const { return m_mv_active; }
|
||||
int move_body_index() const { return m_mv_body; }
|
||||
// Press, then drag, translate arrow `axis` (0..2 = X/Y/Z) with the mouse ray under the cursor.
|
||||
void grab_move_arrow(int axis, const Linef3& ray);
|
||||
void drag_move_arrow(const Linef3& ray);
|
||||
std::function<void(int body, const Transform3d& xform)> on_body_move_changed;
|
||||
// Fired on each cycle change: (level 0=None/1=Whole/2=Face/3=Edge, body index, face id, edge id).
|
||||
std::function<void(int level, int body, int face, int edge)> on_solid_selection_changed;
|
||||
@@ -1225,19 +1257,20 @@ private:
|
||||
Vec3d body_xform_pt(int body, const Vec3d& p) const; // map an OCCT-shape point through the body xform
|
||||
// The bodies' B-rep edges, drawn as dark lines over the solids so faces and features read
|
||||
// apart. One polyline set per body in its own shape coordinates, resampled only for a body
|
||||
// whose shape changed (keyed by the TShape), since set_solid_pick runs on every recompute.
|
||||
// whose shape changed (see is_current_shape).
|
||||
std::vector<std::vector<std::vector<Vec3d>>> m_body_edges;
|
||||
std::vector<const void*> m_body_edges_key;
|
||||
std::vector<TopoDS_Shape> m_body_edges_shape; // the shape each set was sampled from
|
||||
std::vector<double> m_body_edges_tol; // the chord tolerance they were sampled at
|
||||
bool m_body_edges_hidden{false};
|
||||
void refresh_body_edges();
|
||||
void render_body_edges();
|
||||
const void* body_key(int body) const; // the body's TShape, nullptr when there is none
|
||||
const TopoDS_Shape* body_shape(int body) const; // nullptr when there is no such body or it has no shape
|
||||
bool is_current_shape(int body, const TopoDS_Shape& sampled) const; // `sampled` is still the body's shape
|
||||
void append_ribbons(GLModel::Geometry& g, int body, const std::vector<std::vector<Vec3d>>& polylines,
|
||||
const Vec3d& vd, const Vec3d& pull, double hw) const;
|
||||
bool body_pickable(int b) const; // false when the body is explicitly hidden
|
||||
SolidSel m_solid_sel{SolidSel::None};
|
||||
int m_sel_body{-1}; // which body the face/edge selection is on
|
||||
TopoDS_Shape m_sel_shape; // that body's shape when picked: the ids index into it
|
||||
int m_sel_face{-1};
|
||||
int m_sel_edge{-1};
|
||||
std::vector<Vec3d> m_sel_edge_pts;
|
||||
@@ -1274,7 +1307,7 @@ private:
|
||||
void hit_display_sketch(const DisplaySketch& d, const Vec2d& p, double tol,
|
||||
int& edge_feat, int& edge_reg, int& edge_ent,
|
||||
double& edge_d, int& face_feat, int& face_reg) const;
|
||||
bool m_right_consumed{false}; // last RightDown was a gesture terminator, not a menu
|
||||
std::optional<wxMouseEvent> m_right_press; // right press not yet known to be a click
|
||||
bool m_escalate_repick{true}; // re-picking the same sub-element takes the whole body
|
||||
void render_solid_highlight();
|
||||
// The above's edge and vertex highlight, from explicit arguments, so the committed selection
|
||||
@@ -1282,11 +1315,11 @@ private:
|
||||
void render_solid_sel(SolidSel kind, const std::vector<Vec3d>& edge_pts, const Vec3d& vertex_pt,
|
||||
const ColorRGBA& rgb);
|
||||
// A set of selected faces of one body, with their edges sampled once, keyed by the body's
|
||||
// TShape so a recompute that rebuilt the body retires it.
|
||||
// shape so a recompute that rebuilt the body retires it.
|
||||
struct FaceHighlight {
|
||||
int body{-1};
|
||||
std::vector<int> faces; // sorted
|
||||
const void* key{nullptr};
|
||||
TopoDS_Shape shape;
|
||||
std::vector<std::vector<Vec3d>> edges; // in the body's shape coordinates
|
||||
};
|
||||
FaceHighlight make_face_highlight(int body, std::vector<int> faces) const;
|
||||
@@ -1403,6 +1436,7 @@ private:
|
||||
int m_mv_drag{-1}; // 0..2 = X/Y/Z arrow, 3..5 = X/Y/Z ring, -1 none
|
||||
double m_mv_radius{0.0}; // body bounding-sphere radius (mm); 0 = unknown
|
||||
int m_mv_press_x{0}, m_mv_press_y{0};
|
||||
double m_mv_grab_along{0.0}; // arrow grab point's distance from the anchor; NaN = unknown
|
||||
Transform3d compose_move_xform() const; // T(offset)*T(pivot)*rot*T(-pivot)*base_xform
|
||||
void ring_basis(int axis, Vec3d& e, Vec3d& u, Vec3d& v) const; // world axis + in-plane basis
|
||||
void render_move_gizmo();
|
||||
@@ -1410,7 +1444,6 @@ private:
|
||||
double move_gizmo_arm(const Camera& cam) const;
|
||||
bool hit_test_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int& axis) const;
|
||||
bool hit_test_move_arc(GLCanvas3D& canvas, const wxMouseEvent& evt, int& axis) const;
|
||||
void drag_move_arrow(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis);
|
||||
void drag_move_arc(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis);
|
||||
bool arc_mouse_angle(GLCanvas3D& canvas, const wxMouseEvent& evt, int axis, double& ang) const;
|
||||
void open_move_editor(int axis);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#include "CalibUtils.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "format.hpp"
|
||||
#include "Widgets/Label.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "DeviceCore/DevConfigUtil.h"
|
||||
@@ -68,13 +67,13 @@ static wxString get_default_name(wxString filament_name, CalibMode mode){
|
||||
case Slic3r::CalibMode::Calib_PA_Tower:
|
||||
break;
|
||||
case Slic3r::CalibMode::Calib_Flow_Rate:
|
||||
filament_name = format_wxstr(_L("%1% Flow Rate Calibrated"), filament_name);
|
||||
filament_name += " Flow Rate Calibrated";
|
||||
break;
|
||||
case Slic3r::CalibMode::Calib_Temp_Tower:
|
||||
filament_name = format_wxstr(_L("%1% Temperature Calibrated"), filament_name);
|
||||
filament_name += " Temperature Calibrated";
|
||||
break;
|
||||
case Slic3r::CalibMode::Calib_Vol_speed_Tower:
|
||||
filament_name = format_wxstr(_L("%1% Max Vol Speed Calibrated"), filament_name);
|
||||
filament_name += " Max Vol Speed Calibrated";
|
||||
break;
|
||||
case Slic3r::CalibMode::Calib_VFA_Tower:
|
||||
break;
|
||||
@@ -96,7 +95,7 @@ static wxString get_tray_name_by_tray_id(int tray_id)
|
||||
{
|
||||
wxString tray_name;
|
||||
if (tray_id == VIRTUAL_TRAY_MAIN_ID || tray_id == VIRTUAL_TRAY_DEPUTY_ID) {
|
||||
tray_name = _L("Ext");
|
||||
tray_name = "Ext";
|
||||
}
|
||||
else {
|
||||
int ams_id = tray_id / 4;
|
||||
|
||||
@@ -777,7 +777,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
bool have_infill = config->option<ConfigOptionPercent>("sparse_infill_density")->value > 0;
|
||||
// sparse_infill_filament_id uses the same logic as in Print::extruders()
|
||||
for (auto el : { "sparse_infill_pattern", "infill_combination", "fill_multiline","infill_direction",
|
||||
"infill_complete_top",
|
||||
"minimum_sparse_infill_area", "sparse_infill_filament_id","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"})
|
||||
toggle_line(el, have_infill);
|
||||
|
||||
@@ -832,9 +831,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
|
||||
toggle_line("symmetric_infill_y_axis", is_zig_zag || is_cross_zag || is_locked_zig);
|
||||
|
||||
bool is_3Dhoneycomb = config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value == InfillPattern::ip3DHoneycomb;
|
||||
toggle_line("infill_complete_top", have_infill && is_3Dhoneycomb);
|
||||
|
||||
bool has_spiral_vase = config->opt_bool("spiral_mode");
|
||||
toggle_line("spiral_mode_smooth", has_spiral_vase);
|
||||
toggle_line("spiral_mode_max_xy_smoothing", has_spiral_vase && config->opt_bool("spiral_mode_smooth"));
|
||||
@@ -844,7 +840,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
||||
toggle_line("sparse_infill_smooth_factor", have_infill && is_smoothable_infill_pattern(pattern, config->opt_int("fill_multiline")));
|
||||
toggle_line("sparse_infill_smooth_factor", is_smoothable_infill_pattern(pattern, config->opt_int("fill_multiline")));
|
||||
toggle_field("top_surface_pattern", has_top_shell);
|
||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||
toggle_field("top_surface_density", has_top_shell_layers);
|
||||
|
||||
@@ -53,8 +53,6 @@ wxString get_string_from_enum(const std::string& opt_key, const DynamicPrintConf
|
||||
}
|
||||
return _L("Undefined");
|
||||
}
|
||||
if (val < 0 || val >= int(names.size()))
|
||||
return _L("Undefined");
|
||||
return from_u8(_utf8(names[val]));
|
||||
}
|
||||
|
||||
@@ -217,8 +215,6 @@ wxString get_string_value(const std::string& opt_key, const DynamicPrintConfig&
|
||||
;
|
||||
}
|
||||
case coEnums: {
|
||||
if (!opt_vector || opt_idx >= opt_vector->size())
|
||||
return _L("Undefined");
|
||||
return get_string_from_enum(pure_key, config,
|
||||
pure_key == "top_surface_pattern" ||
|
||||
pure_key == "bottom_surface_pattern" ||
|
||||
|
||||
@@ -1130,7 +1130,7 @@ void PageMaterials::sort_list_data(StringList* list, bool add_All_item, bool mat
|
||||
for (const auto& item : prusa_profiles)
|
||||
list->append(item, &const_cast<std::string&>(item.get()));
|
||||
for (const auto& item : other_profiles)
|
||||
list->append(item.get() == Materials::UNKNOWN ? _L(item.get()) : from_u8(item.get()), &const_cast<std::string&>(item.get()));
|
||||
list->append(item, &const_cast<std::string&>(item.get()));
|
||||
}
|
||||
|
||||
void PageMaterials::sort_list_data(PresetList* list, const std::vector<ProfilePrintData>& data)
|
||||
@@ -1210,13 +1210,13 @@ void PageMaterials::on_activate()
|
||||
}
|
||||
|
||||
|
||||
const char *PageCustom::default_profile_name = L("My Settings");
|
||||
const char *PageCustom::default_profile_name = "My Settings";
|
||||
|
||||
PageCustom::PageCustom(ConfigWizard *parent)
|
||||
: ConfigWizardPage(parent, _L("Custom Printer Setup"), _L("Custom Printer"))
|
||||
{
|
||||
cb_custom = new wxCheckBox(this, wxID_ANY, _L("Define a custom printer profile"));
|
||||
tc_profile_name = new wxTextCtrl(this, wxID_ANY, _L(default_profile_name));
|
||||
tc_profile_name = new wxTextCtrl(this, wxID_ANY, default_profile_name);
|
||||
auto *label = new wxStaticText(this, wxID_ANY, _L("Custom profile name:"));
|
||||
|
||||
wxGetApp().UpdateDarkUI(tc_profile_name);
|
||||
@@ -1224,7 +1224,7 @@ PageCustom::PageCustom(ConfigWizard *parent)
|
||||
tc_profile_name->Enable(false);
|
||||
tc_profile_name->Bind(wxEVT_KILL_FOCUS, [this](wxFocusEvent &evt) {
|
||||
if (tc_profile_name->GetValue().IsEmpty()) {
|
||||
if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(_L(default_profile_name)); }
|
||||
if (profile_name_prev.IsEmpty()) { tc_profile_name->SetValue(default_profile_name); }
|
||||
else { tc_profile_name->SetValue(profile_name_prev); }
|
||||
} else {
|
||||
profile_name_prev = tc_profile_name->GetValue();
|
||||
@@ -1463,7 +1463,7 @@ PageTemperatures::PageTemperatures(ConfigWizard *parent)
|
||||
auto *default_bed = def_bed.get_default_value<ConfigOptionInts>();
|
||||
spin_bed->SetValue(default_bed != nullptr && default_bed->size() > 0 ? default_bed->get_at(0) : 0);
|
||||
|
||||
append_text(_L("Enter the nozzle temperature needed for extruding your filament."));
|
||||
append_text(_L("Enter the nozzle_temperature needed for extruding your filament."));
|
||||
append_text(_L("A rule of thumb is 160 to 230℃ for PLA, and 215 to 250℃ for ABS."));
|
||||
#endif
|
||||
|
||||
@@ -1720,7 +1720,7 @@ void ConfigWizardIndex::msw_rescale()
|
||||
|
||||
// Materials
|
||||
|
||||
const std::string Materials::UNKNOWN = L("(Unknown)");
|
||||
const std::string Materials::UNKNOWN = "(Unknown)";
|
||||
|
||||
void Materials::push(const Preset *preset)
|
||||
{
|
||||
@@ -2188,8 +2188,8 @@ void ConfigWizard::priv::select_default_materials_for_printer_models(Technology
|
||||
}
|
||||
printer_names += "\n\n";
|
||||
std::string message = (technology & T_FFF ?
|
||||
GUI::format(_L("The following printer profiles have no default filament: %1%Please select one manually."), printer_names) :
|
||||
GUI::format(_L("The following printer profiles have no default material: %1%Please select one manually."), printer_names));
|
||||
GUI::format(_L("Following printer profiles has no default filament: %1%Please select one manually."), printer_names) :
|
||||
GUI::format(_L("Following printer profiles has no default material: %1%Please select one manually."), printer_names));
|
||||
MessageDialog msg(q, message, _L("Notice"), wxOK);
|
||||
msg.ShowModal();
|
||||
}
|
||||
@@ -2449,7 +2449,7 @@ bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
||||
}
|
||||
if (!check_unsaved_preset_changes)
|
||||
if ((check_unsaved_preset_changes = install_bundles.size() > 0))
|
||||
header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of their printers will be activated", install_bundles.size());
|
||||
header = _L_PLURAL("A new vendor was installed and one of its printers will be activated", "New vendors were installed and one of theirs printers will be activated", install_bundles.size());
|
||||
|
||||
// Decide whether to create snapshot based on run_reason and the reset profile checkbox
|
||||
bool snapshot = true;
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
#include "GUI.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "Widgets/Button.hpp"
|
||||
#include <wx/types.h>
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
@@ -39,7 +39,7 @@ CrealityDiscoveryDialog::CrealityDiscoveryDialog(wxWindow* parent)
|
||||
m_list->AppendColumn(_L("Hostname"), wxLIST_FORMAT_LEFT, 14 * em);
|
||||
m_list->AppendColumn(_L("IP"), wxLIST_FORMAT_LEFT, 14 * em);
|
||||
|
||||
auto* dlg_btns = new DialogButtons(this, {L("Scan"), "OK", "Cancel"}, "", 1 /*left_aligned*/);
|
||||
auto* dlg_btns = new DialogButtons(this, {"Scan", "OK", "Cancel"}, "", 1 /*left_aligned*/);
|
||||
auto* ok_btn = dlg_btns->GetOK();
|
||||
ok_btn->SetLabel(_L("Use Selected"));
|
||||
ok_btn->Disable();
|
||||
@@ -91,7 +91,7 @@ void CrealityDiscoveryDialog::run_discovery()
|
||||
if (!h.model_name.empty())
|
||||
row.model = h.model_name;
|
||||
else if (h.cfs_capable)
|
||||
row.model = _u8L("(unknown K-series)");
|
||||
row.model = "(unknown K-series)";
|
||||
else
|
||||
row.model = "Creality";
|
||||
m_rows.push_back(std::move(row));
|
||||
|
||||
@@ -652,7 +652,7 @@ DesktopIntegrationDialog::DesktopIntegrationDialog(wxWindow *parent)
|
||||
|
||||
wxString text = _L("Desktop Integration sets this binary to be searchable by the system.\n\nPress \"Perform\" to proceed.");
|
||||
if (can_undo)
|
||||
text += "\n" + _L("Press \"Undo\" to remove previous integration.");
|
||||
text += "\nPress \"Undo\" to remove previous integration.";
|
||||
|
||||
vbox->Add(
|
||||
new wxStaticText( this, wxID_ANY, text),
|
||||
|
||||
@@ -154,7 +154,7 @@ void calib_fail_message(MachineObject* obj, std::string cali_mode, std::string r
|
||||
} else if (reason == "nozzle_diameter is not matched") {
|
||||
info = _L("Selected diameter and machine diameter do not match");
|
||||
} else if (reason == "generate auto filament cali gcode failure") {
|
||||
info = _L("Failed to generate calibration G-code");
|
||||
info = _L("Failed to generate cali gcode");
|
||||
} else {
|
||||
info = wxString(reason);
|
||||
}
|
||||
|
||||
@@ -4313,7 +4313,7 @@ int MachineObject::parse_json(std::string tunnel, std::string payload, bool key_
|
||||
info = _L("Selected diameter and machine diameter do not match");
|
||||
}
|
||||
else if (reason == "generate auto filament cali gcode failure") {
|
||||
info = _L("Failed to generate calibration G-code");
|
||||
info = _L("Failed to generate cali G-code");
|
||||
}
|
||||
else {
|
||||
info = reason;
|
||||
|
||||
@@ -981,7 +981,7 @@ int ReselectMachineDialog::CaculateSwitcherDistribution(MachineObject* obj, cons
|
||||
}
|
||||
if (can.material_state == AMSCanType::AMS_CAN_TYPE_EMPTY)
|
||||
{
|
||||
material = L("Empty");
|
||||
material = "Empty";
|
||||
}
|
||||
|
||||
auto itOK = std::find_if(posOK.begin(), posOK.end(), [&](const trayHelper& tray){
|
||||
|
||||
@@ -60,7 +60,7 @@ void wgtMsgPanelItem::CreateGui()
|
||||
text_sizer->Add(m_text_label, 0, wxALIGN_CENTER_VERTICAL);
|
||||
|
||||
if (!m_wiki_url.IsEmpty()) {
|
||||
m_wiki_link = new wxHyperlinkCtrl(this, wxID_ANY, _L("Wiki->"), m_wiki_url);
|
||||
m_wiki_link = new wxHyperlinkCtrl(this, wxID_ANY, "Wiki->", m_wiki_url);
|
||||
m_wiki_link->SetNormalColour(m_colour);
|
||||
m_wiki_link->SetHoverColour(wxColour(0, 0, 200));
|
||||
m_wiki_link->SetVisitedColour(*wxBLUE);
|
||||
|
||||
@@ -19,10 +19,8 @@
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include <functional>
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/Gizmos/GLGizmoBase.hpp"
|
||||
#include <wx/arrstr.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
@@ -167,7 +165,7 @@ void Downloader::start_download(const std::string& full_url)
|
||||
// Orca: show error
|
||||
NotificationManager* ntf_mngr = wxGetApp().notification_manager();
|
||||
ntf_mngr->push_notification(NotificationType::CustomNotification, NotificationManager::NotificationLevel::ErrorNotificationLevel,
|
||||
_u8L("Could not start download due to malformed URL"));
|
||||
"Could not start download due to malformed URL");
|
||||
return;
|
||||
}
|
||||
size_t id = get_next_id();
|
||||
|
||||
@@ -161,7 +161,7 @@ void FileGet::priv::get_perform()
|
||||
}
|
||||
if (!found) {
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(GUI::format_wxstr(_L("Failed to find suitable filename. Last name: %1%."), (m_dest_folder / final_filename).string()));
|
||||
evt->SetString(GUI::format_wxstr(L"Failed to find suitable filename. Last name: %1%." , (m_dest_folder / final_filename).string()));
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
@@ -367,7 +367,7 @@ void FileGet::priv::get_perform()
|
||||
//TODO: report?
|
||||
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
|
||||
wxCommandEvent* evt = new wxCommandEvent(EVT_DWNLDR_FILE_ERROR);
|
||||
evt->SetString(_L("Failed to write and move."));
|
||||
evt->SetString("Failed to write and move.");
|
||||
evt->SetInt(m_id);
|
||||
m_evt_handler->QueueEvent(evt);
|
||||
return;
|
||||
|
||||