Merge branch 'main' into exturuder-tab-multi-switch
@@ -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` |
|
||||
|
||||
@@ -135,7 +135,7 @@ contract between them is stated in code rather than spread across handlers.
|
||||
| --- | --- | --- |
|
||||
| `Transient` | a value field or a popup menu | closes it; the tool stays armed |
|
||||
| `Gesture` | an uncommitted delta — an entity being drawn, a body being dragged | reverts it; committed work is untouched |
|
||||
| `Tool` | a feature card, an armed sketch tool, a constrain session | exits it; drawn entities survive |
|
||||
| `Tool` | a feature card, Sketch waiting for its plane, an armed sketch tool, a constrain session | exits it; drawn entities survive |
|
||||
| `Idle` | nothing transient | clears the selection, a Feature tree or Bodies row included; leaves a sketch session only if it is empty |
|
||||
|
||||
`cad_escape_level()` is a `constexpr` free function over a POD of four booleans rather than a
|
||||
@@ -149,26 +149,66 @@ explicit selection, the sketch ribbon's Cancel, which asks first, or `Ctrl+Z`. A
|
||||
*session* is deliberately not a `Tool` level; it is the environment the `Idle` level lives in,
|
||||
which makes the destructive path unrepresentable rather than merely unlikely.
|
||||
|
||||
A body Move is the one `Gesture` that outlives the press: its gizmo stays up between drags until
|
||||
Confirm keeps the placement or `Esc` or Cancel puts the body back. Until then the selection is
|
||||
held — a click off the gizmo only steers the camera — and undo is refused, since the placement
|
||||
is not in the history. Anything that starts another edit (a feature card, a sketch, placing
|
||||
imported art or text, another body's Move, a rebuild) keeps the placement, as switching gizmos
|
||||
keeps a move in Prepare. The panel ends the Move in one place (`DesignPanel::end_body_move`), so
|
||||
the gizmo, the Move / Rotate card and the ✓/✗ cannot outlive one another.
|
||||
|
||||
Right-click is read at button-up against one budget, 3 px of drift, applied to the whole press
|
||||
rather than to its end points: a press that wandered past the budget at any moment is
|
||||
navigation, even if it comes back to where it started, which is what stops a slow, careful
|
||||
orbit from ending in a menu. There is no time budget — a gesture that means something different
|
||||
when it is slow is exactly what the interaction charter rules out. The raycast uses the press
|
||||
position, not the release. An armed sketch tool that already consumed the right
|
||||
button (to terminate a chain, say) declines to also open a menu, through a read-and-clear flag.
|
||||
Past either budget the event is navigation, and navigation does not transition the state
|
||||
machine.
|
||||
position, not the release. The sketch tool sees a right press only once the release has shown it
|
||||
was a click: the press itself goes to the camera, which may pan or orbit with that button, and the
|
||||
canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a
|
||||
chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation
|
||||
does not transition the state machine.
|
||||
|
||||
Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control
|
||||
for each button. The left button is shared with picking, so a whole body is swept with a
|
||||
rectangle on plain left-drag only while no camera action is assigned to it, and with
|
||||
Shift+left-drag otherwise — Prepare's own rectangle selection.
|
||||
for each button, and in the Touchpad camera style a move with Alt held orbits and one with Shift
|
||||
held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool
|
||||
handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept
|
||||
with a rectangle on plain left-drag only while no camera action is assigned to the left button,
|
||||
and with Shift+left-drag otherwise — Prepare's own rectangle selection.
|
||||
|
||||
Entering a sketch changes three things at once so the mode is legible: a banner above the
|
||||
Entering a sketch changes two things at once so the mode is legible: a banner above the
|
||||
canvas (a sibling of the canvas, not a child over it — on GTK a child window over a
|
||||
`wxGLCanvas` is a native window and does not reliably stack over GL), the printer bed muted so
|
||||
a plate grid is never read as a sketch grid, and `N` to look normal to the plane. Code that
|
||||
changes any of the three belongs with a change to this section.
|
||||
`wxGLCanvas` is a native window and does not reliably stack over GL), and `N` to look normal to
|
||||
the plane. The printer bed stays: there is no sketch grid, so the plate grid is the only ground
|
||||
reference a sketch has. Code that changes either belongs with a change to this section.
|
||||
|
||||
Sketch mode is never entered without a plane under it, so the banner, the sketch keys and the
|
||||
sketch offer always have a session to act on. Sketch on a picked flat face or reference plane opens
|
||||
the session on it at once. With nothing picked it stays in Feature mode and waits for one — an
|
||||
armed `Tool`, left with `Esc` or ✗, and ended by anything that starts another edit — and the
|
||||
reference plane or flat face clicked next opens the session. A picked plane is a selection like a
|
||||
face: the sketch on it uses it up, and `Esc` or a click on nothing lets go of it, so a plane that
|
||||
can no longer be seen never decides where the next sketch goes.
|
||||
|
||||
The reference planes — XY, XZ and YZ through the modeling origin, with their half-axes — are
|
||||
drawn on demand, because three translucent squares over every model are noise once they are not
|
||||
the thing being picked. Sketch brings them up while it waits for a plane, which is exactly when
|
||||
they are picked, and the session the pick opens takes them away; a live session draws none. The
|
||||
Feature tree's Origin row keeps them up outside a sketch. Its state is a view preference in
|
||||
AppConfig rather than part of the recipe, so it costs the project format nothing. The Plane tool
|
||||
keeps its own rule: the planes and the datums as Offset bases, and nothing for the other methods,
|
||||
where a click on a plane would rewrite the datum's references. The `P` and `A` keys are a
|
||||
separate, unpickable view helper and do not follow the Origin row.
|
||||
|
||||
The Bed row, under the Origin row, is the printer bed's switch in the same way: it draws or hides
|
||||
the bed and its plate grid in every mode. It is a view preference in AppConfig too, and the bed is
|
||||
shown until it is turned off.
|
||||
|
||||
The two rows are view switches, not history, and the tree says so: they sit unframed on the Feature
|
||||
tree's card, above the features' own framed list, and stay put while the features scroll. A click
|
||||
never selects either row, since a selected Origin or Bed would have nothing to edit, move or
|
||||
delete; the eye and the right-click menu are the only targets, and a row's label dims while its
|
||||
thing is hidden, as a hidden body's does. Because the block never takes the focus, `Ctrl+Shift+O`
|
||||
and `Ctrl+Shift+B` flip the Origin and the Bed from the keyboard.
|
||||
|
||||
## Rendering the bodies
|
||||
|
||||
@@ -189,6 +229,15 @@ degenerate edges are left out (`GeometryEngine::display_edges`), and the polylin
|
||||
once per shape, keyed by its `TShape`, because a recompute that leaves a body unchanged is the
|
||||
common case.
|
||||
|
||||
While a feature card is open, its preview ghost is the whole model the candidate would produce,
|
||||
drawn translucent over the bodies, so every face the feature leaves alone is in both at the same
|
||||
depth. The ghost is drawn with a depth bias that pushes it back (`GLVolume::depth_bias`), so on a shared face
|
||||
the body always wins instead of the two copies z-fighting, and the ghost shows only where the
|
||||
result reaches past the bodies. Material a feature removes lies inside the old solid and would not
|
||||
show at all, so the tools whose result mostly coincides with the body — Fillet/Chamfer, Draft,
|
||||
Hole and the Mate hover — hide the bodies once the preview is valid and draw the result alone,
|
||||
opaque.
|
||||
|
||||
## Showing what is selected
|
||||
|
||||
A selection is drawn on the faces it names, never as a tint over the body: a translucent
|
||||
@@ -214,7 +263,17 @@ replay costs up to a recompute, so the panel finds the faces once per row and to
|
||||
generation, off the UI thread, and only while no feature card is open. One selection is live at
|
||||
a time: a viewport pick clears the feature row and a feature row clears the viewport pick, as the
|
||||
Feature tree and Bodies list do between themselves. `Esc`, a click on empty space and an
|
||||
empty rubber band all let go of it, whichever list or pick made it.
|
||||
empty rubber band all let go of it, whichever list or pick made it — except while a body Move is
|
||||
open, which holds the selection until it ends (see the interaction contract).
|
||||
|
||||
Zoom to selection, on a Feature tree or Bodies row and in its right-click menu, frames one thing
|
||||
along the current view direction, as the canvas's Fit button frames the selection. On a body it
|
||||
frames the body whole, hidden or not, from its display mesh: the viewport never selects a hidden
|
||||
body, so the selection cannot stand in for it. On a sketch it frames the sketch's own geometry,
|
||||
drawn, consumed or suppressed; on any other feature, the faces the feature made, found as the row
|
||||
highlight finds them and from the same cache. From the offer it frames whatever the selection is,
|
||||
a body again included. A feature that is not a sketch and makes no faces, such as a datum plane
|
||||
or a suppressed Extrude, has no Zoom to selection.
|
||||
|
||||
## Following the app
|
||||
|
||||
@@ -231,9 +290,21 @@ The tab is a page of Orca's main window and answers to the same settings as Prep
|
||||
- **Sidebar icons.** Every clickable icon in the sidebar shows a hover chip. The card-header and
|
||||
constraint-row buttons are Orca's self-painted `Button`, because a native button cannot take a
|
||||
hover background on macOS. The Feature tree and Bodies lists are a custom-drawn
|
||||
`DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Edit,
|
||||
Show/hide and Delete on a feature, Move, Show/hide and Delete on a body — and the eye shows
|
||||
whether that row is hidden.
|
||||
`DesignRowList` rather than a `wxTreeCtrl`, so each row carries its own actions — Zoom to
|
||||
selection, Edit, Show/hide and Delete on a feature, Zoom to selection, Move, Show/hide and
|
||||
Delete on a body, and only Show/hide on the Origin and Bed rows, a separate non-selectable list
|
||||
above the features — and the eye shows whether that row is hidden. A feature with nothing to
|
||||
frame keeps a blank cell where Zoom to selection would be, so every icon stays in its column.
|
||||
- **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 from Prepare's camera turned to the tab's iso view: the CAD
|
||||
isometric from the front-right corner that Home returns to, not Prepare's front-left one.
|
||||
- **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.
|
||||
@@ -256,12 +327,12 @@ permanent row index, verbs that do not apply shown disabled **in place with thei
|
||||
rather than removed. The invariant is that a verb's row index is identical in every selection
|
||||
where it appears and that adding a verb never moves an existing one — the hand learns the
|
||||
position, so the menu is never re-sorted, compacted or adaptively ordered. Above the families
|
||||
sits one *flat* row, holding Rename and Color — what a selection is opened for most: its verbs are
|
||||
items of their own at the top of the menu rather than a family's submenu. It is appended after
|
||||
the eight, so it moved no existing index, and it reads the same from the viewport and from a row
|
||||
of the Bodies list.
|
||||
sits one *flat* row, holding Rename, Color and Zoom to selection — what a selection is opened for
|
||||
most: its verbs are items of their own at the top of the menu rather than a family's submenu. It
|
||||
is appended after the eight, so it moved no existing index, and it reads the same from the
|
||||
viewport and from a row of the Bodies list.
|
||||
|
||||
An invariant across 92 verbs and 20 selection kinds does not survive by review, so the map
|
||||
An invariant across 93 verbs and 20 selection kinds does not survive by review, so the map
|
||||
exists once, as data: `scripts/CAD/tool_atlas.json` carries every verb with its row, key, icon,
|
||||
accepted selections, preconditions and refusal string, and `scripts/CAD/gen_offer_table.py`
|
||||
emits `src/slic3r/GUI/CAD/DesignOffer.hpp` from it. The header is checked in and never
|
||||
|
||||
@@ -36,8 +36,13 @@ override is not available in these profiles. The source's `EXCLUDE_E_START` and
|
||||
`EXCLUDE_E_END` internal markers become comments rather than printer commands.
|
||||
|
||||
Pressure-advance restoration and automatic pressure-advance emission use the
|
||||
selected filament preset's settings. These profiles do not impose machine-owned
|
||||
filament overrides. Dock-fan control retains the source's material and layer
|
||||
selected filament preset's settings. The INDX filament presets include
|
||||
`fdm_filament_template_indx`, whose start G-code sets the filament's pressure
|
||||
advance with `M572` and then starts the firmware's automatic calibration with
|
||||
`M573 R`, the commands PrusaSlicer emits after a tool change; the station purge
|
||||
disables pressure advance before it. The template also carries Prusa's
|
||||
multi-tool ramming and a 10 mm³ minimal purge. These profiles do not impose
|
||||
machine-owned filament overrides. Dock-fan control retains the source's material and layer
|
||||
conditions; shutdown parks the tool and turns off the used heaters and dock fan.
|
||||
|
||||
## Configuration boundaries
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M2 9 14 9 22 15 10 15Z"/><path d="M2 9V11L10 17H22V15M10 15V17"/><path d="M6 9 14 15M10 9 18 15M6 12H18"/></svg>
|
||||
|
After Width: | Height: | Size: 301 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M3.15 19.57V4.43A1.28 1.28 0 0 1 4.43 3.15H17.05L20.85 6.95V19.57A1.28 1.28 0 0 1 19.57 20.85H4.43A1.28 1.28 0 0 1 3.15 19.57ZM8.2 3.15V5.68A1.28 1.28 0 0 0 9.48 6.95H14.52A1.28 1.28 0 0 0 15.8 5.68V3.15M17.05 20.85V15.8A1.28 1.28 0 0 0 15.8 14.53H8.2A1.28 1.28 0 0 0 6.95 15.8V20.85"/></svg>
|
||||
|
After Width: | Height: | Size: 481 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.08,10.74 8.08,5.4M8.08,10.74 15.55,4.87M17.15,10.2 15.55,4.87M4.88,8.6 8.62,20.87M17.15,10.2 20.89,16.07M8.08,10.74 12.78,15.54 17.15,10.2M17.15,19.8 12.78,15.54 8.62,20.87M4.88,8.6 8.08,10.74 17.15,10.2 19.82,7M8.08,10.74 8.62,20.87M17.15,10.2 17.15,19.8" fill="none" stroke="#949494" stroke-width="0.6" stroke-linecap="round" stroke-linejoin="round"/><polygon points="4.88,8.6 8.08,5.4 15.55,4.87 19.82,7 20.89,16.07 17.15,19.8 8.62,20.87 4.35,17.14" fill="none" stroke="#949494" stroke-width="0.85" stroke-linejoin="round"/><polygon points="2.84,4.68 7.32,4.68 7.32,2.84 11.29,5.86 7.32,8.88 7.32,7.04 2.84,7.04" style="fill:#009688;stroke:#009688;stroke-width:0.4;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 794 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><rect x="3.15" y="3.15" width="17.7" height="17.7" rx="1.28"/><path d="M7.6 12H16.4M12 7.6V16.4"/></svg>
|
||||
|
After Width: | Height: | Size: 284 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><path d="M5.88 3.88H17.16M3.88 5.88V17.16M19.16 5.88V12.38M5.88 19.16H12.38M9.85 7.85H13.2M7.85 9.85V13.2M14.92 9.85A7.35 7.35 0 0 1 9.85 14.92"/><path d="M2.83 2.83h2.1v2.1h-2.1zM18.11 2.83h2.1v2.1h-2.1zM2.83 18.11h2.1v2.1h-2.1zM6.8 6.8h2.1v2.1h-2.1zM14.15 6.8h2.1v2.1h-2.1zM6.8 14.15h2.1v2.1h-2.1z" fill="#949494" stroke="none"/><polygon points="16.01,13.19 18.3,13.19 18.3,16.01 21.12,16.01 21.12,18.3 18.3,18.3 18.3,21.12 16.01,21.12 16.01,18.3 13.19,18.3 13.19,16.01 16.01,16.01" style="fill:#009688;stroke:#009688;stroke-width:0.4;stroke-linejoin:round"/></svg>
|
||||
|
After Width: | Height: | Size: 747 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M3.0 9.0 L16.0 9.0 L16.0 21.0 L3.0 21.0 ZM3.0 9.0 L7.0 5.0 L20.0 5.0 L16.0 9.0M20.0 5.0 L20.0 17.0 L16.0 21.0" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.168410256410256 13.423897435897436V13.868410256410256Q5.908923076923077 13.744307692307691 5.678769230769231 13.683384615384615Q5.448615384615385 13.622461538461538 5.23425641025641 13.622461538461538Q4.861948717948718 13.622461538461538 4.66 13.766871794871793Q4.458051282051283 13.91128205128205 4.458051282051283 14.177538461538461Q4.458051282051283 14.400923076923076 4.592307692307693 14.514871794871794Q4.726564102564103 14.628820512820512 5.101128205128205 14.69876923076923L5.3764102564102565 14.755179487179486Q5.886358974358975 14.852205128205128 6.128923076923078 15.09702564102564Q6.37148717948718 15.341846153846152 6.37148717948718 15.75251282051282Q6.37148717948718 16.242153846153844 6.043179487179488 16.49487179487179Q5.7148717948717955 16.747589743589742 5.080820512820513 16.747589743589742Q4.841641025641026 16.747589743589742 4.572000000000001 16.693435897435897Q4.302358974358975 16.639282051282052 4.013538461538462 16.53323076923077V16.063897435897434Q4.291076923076924 16.219589743589744 4.557333333333334 16.2985641025641Q4.823589743589745 16.37753846153846 5.080820512820513 16.37753846153846Q5.471179487179487 16.37753846153846 5.683282051282052 16.224102564102562Q5.895384615384616 16.070666666666664 5.895384615384616 15.786358974358974Q5.895384615384616 15.538153846153845 5.743076923076924 15.398256410256408Q5.590769230769231 15.258358974358973 5.243282051282051 15.188410256410256L4.965743589743591 15.134256410256409Q4.455794871794872 15.032717948717949 4.227897435897436 14.816102564102565Q4.000000000000001 14.599487179487179 4.000000000000001 14.213641025641024Q4.000000000000001 13.766871794871793 4.314769230769231 13.509641025641024Q4.629538461538462 13.252410256410256 5.182358974358975 13.252410256410256Q5.419282051282051 13.252410256410256 5.665230769230769 13.29528205128205Q5.911179487179488 13.338153846153846 6.168410256410256 13.423897435897436ZM6.615179487179487 13.313333333333333H9.46502564102564V13.696923076923076H8.269128205128204V16.682153846153845H7.811076923076923V13.696923076923076H6.615179487179487ZM9.905025641025642 13.313333333333333H12.035076923076925V13.696923076923076H10.360820512820514V14.69425641025641H11.965128205128206V15.077846153846153H10.360820512820514V16.2985641025641H12.075692307692309V16.682153846153845H9.905025641025642ZM13.280615384615384 13.687897435897435V14.953743589743588H13.85374358974359Q14.171897435897435 14.953743589743588 14.345641025641026 14.78902564102564Q14.519384615384615 14.624307692307692 14.519384615384615 14.319692307692307Q14.519384615384615 14.017333333333333 14.345641025641026 13.852615384615383Q14.171897435897435 13.687897435897435 13.85374358974359 13.687897435897435ZM12.824820512820512 13.313333333333333H13.85374358974359Q14.420102564102564 13.313333333333333 14.710051282051282 13.569435897435897Q15.0 13.82553846153846 15.0 14.319692307692307Q15.0 14.818358974358974 14.710051282051282 15.073333333333332Q14.420102564102564 15.32830769230769 13.85374358974359 15.32830769230769H13.280615384615384V16.682153846153845H12.824820512820512Z" fill="#949494" stroke="none"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 1.5 23 23"><path d="M3.0 9.0 L16.0 9.0 L16.0 21.0 L3.0 21.0 ZM3.0 9.0 L7.0 5.0 L20.0 5.0 L16.0 9.0M20.0 5.0 L20.0 17.0 L16.0 21.0" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.168410256410256 13.423897435897436V13.868410256410256Q5.908923076923077 13.744307692307691 5.678769230769231 13.683384615384615Q5.448615384615385 13.622461538461538 5.23425641025641 13.622461538461538Q4.861948717948718 13.622461538461538 4.66 13.766871794871793Q4.458051282051283 13.91128205128205 4.458051282051283 14.177538461538461Q4.458051282051283 14.400923076923076 4.592307692307693 14.514871794871794Q4.726564102564103 14.628820512820512 5.101128205128205 14.69876923076923L5.3764102564102565 14.755179487179486Q5.886358974358975 14.852205128205128 6.128923076923078 15.09702564102564Q6.37148717948718 15.341846153846152 6.37148717948718 15.75251282051282Q6.37148717948718 16.242153846153844 6.043179487179488 16.49487179487179Q5.7148717948717955 16.747589743589742 5.080820512820513 16.747589743589742Q4.841641025641026 16.747589743589742 4.572000000000001 16.693435897435897Q4.302358974358975 16.639282051282052 4.013538461538462 16.53323076923077V16.063897435897434Q4.291076923076924 16.219589743589744 4.557333333333334 16.2985641025641Q4.823589743589745 16.37753846153846 5.080820512820513 16.37753846153846Q5.471179487179487 16.37753846153846 5.683282051282052 16.224102564102562Q5.895384615384616 16.070666666666664 5.895384615384616 15.786358974358974Q5.895384615384616 15.538153846153845 5.743076923076924 15.398256410256408Q5.590769230769231 15.258358974358973 5.243282051282051 15.188410256410256L4.965743589743591 15.134256410256409Q4.455794871794872 15.032717948717949 4.227897435897436 14.816102564102565Q4.000000000000001 14.599487179487179 4.000000000000001 14.213641025641024Q4.000000000000001 13.766871794871793 4.314769230769231 13.509641025641024Q4.629538461538462 13.252410256410256 5.182358974358975 13.252410256410256Q5.419282051282051 13.252410256410256 5.665230769230769 13.29528205128205Q5.911179487179488 13.338153846153846 6.168410256410256 13.423897435897436ZM6.615179487179487 13.313333333333333H9.46502564102564V13.696923076923076H8.269128205128204V16.682153846153845H7.811076923076923V13.696923076923076H6.615179487179487ZM9.905025641025642 13.313333333333333H12.035076923076925V13.696923076923076H10.360820512820514V14.69425641025641H11.965128205128206V15.077846153846153H10.360820512820514V16.2985641025641H12.075692307692309V16.682153846153845H9.905025641025642ZM13.280615384615384 13.687897435897435V14.953743589743588H13.85374358974359Q14.171897435897435 14.953743589743588 14.345641025641026 14.78902564102564Q14.519384615384615 14.624307692307692 14.519384615384615 14.319692307692307Q14.519384615384615 14.017333333333333 14.345641025641026 13.852615384615383Q14.171897435897435 13.687897435897435 13.85374358974359 13.687897435897435ZM12.824820512820512 13.313333333333333H13.85374358974359Q14.420102564102564 13.313333333333333 14.710051282051282 13.569435897435897Q15.0 13.82553846153846 15.0 14.319692307692307Q15.0 14.818358974358974 14.710051282051282 15.073333333333332Q14.420102564102564 15.32830769230769 13.85374358974359 15.32830769230769H13.280615384615384V16.682153846153845H12.824820512820512Z" fill="#949494" stroke="none"/><polygon points="2.72,5.99 7.02,5.99 7.02,4.22 10.82,7.12 7.02,10.01 7.02,8.25 2.72,8.25" style="fill:#009688;stroke:#009688;stroke-width:0.38;stroke-linejoin:round"/></svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#949494" stroke-width="0.85" stroke-linecap="round" stroke-linejoin="round"><circle cx="10" cy="10" r="7"/><path d="M6 10a4 4 0 0 1 4-4M15 15 21.5 21.5"/></svg>
|
||||
|
After Width: | Height: | Size: 264 B |
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "Prusa",
|
||||
"version": "02.04.00.15",
|
||||
"version": "02.04.00.16",
|
||||
"force_update": "0",
|
||||
"description": "Prusa configurations",
|
||||
"machine_model_list": [
|
||||
@@ -1573,48 +1573,32 @@
|
||||
],
|
||||
"filament_list": [
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One.json"
|
||||
"name": "Generic PC @Prusa",
|
||||
"sub_path": "filament/Generic PC @Prusa.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_common",
|
||||
"sub_path": "filament/fdm_filament_common.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One 0.6",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One 0.6.json"
|
||||
"name": "fdm_filament_template_indx",
|
||||
"sub_path": "filament/fdm_filament_template_indx.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One 0.8",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One 0.8.json"
|
||||
"name": "fdm_filament_template_nextruder_pet",
|
||||
"sub_path": "filament/fdm_filament_template_nextruder_pet.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.4",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.4.json"
|
||||
"name": "fdm_filament_template_nextruder_pet_hf",
|
||||
"sub_path": "filament/fdm_filament_template_nextruder_pet_hf.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.5",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.5.json"
|
||||
"name": "fdm_filament_template_nextruder_pla",
|
||||
"sub_path": "filament/fdm_filament_template_nextruder_pla.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.6",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.6.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.8",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One 0.6",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One 0.6.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One 0.8",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One 0.8.json"
|
||||
"name": "fdm_filament_template_nextruder_pla_hf",
|
||||
"sub_path": "filament/fdm_filament_template_nextruder_pla_hf.json"
|
||||
},
|
||||
{
|
||||
"name": "fdm_filament_abs",
|
||||
@@ -1664,6 +1648,22 @@
|
||||
"name": "fdm_filament_tpu",
|
||||
"sub_path": "filament/fdm_filament_tpu.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One INDX",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One INDX.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PC @Prusa MK4 MMU3",
|
||||
"sub_path": "filament/Generic PC @Prusa MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ABS @Prusa MK3.5",
|
||||
"sub_path": "filament/Generic ABS @Prusa MK3.5.json"
|
||||
@@ -2160,6 +2160,38 @@
|
||||
"name": "Generic TPU HF @Prusa MK3.5",
|
||||
"sub_path": "filament/Generic TPU HF @Prusa MK3.5.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One 0.6",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One 0.6.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One 0.8",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.4",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.4.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.5",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.5.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.6",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.6.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa CORE One HF 0.8",
|
||||
"sub_path": "filament/Generic ASA @Prusa CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One 0.6",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One 0.6.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One 0.8",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ABS @Prusa MK4S HF0.4",
|
||||
"sub_path": "filament/Generic ABS @Prusa MK4S HF0.4.json"
|
||||
@@ -2212,6 +2244,10 @@
|
||||
"name": "Generic ABS HF @Prusa MINIIS 0.8",
|
||||
"sub_path": "filament/Generic ABS HF @Prusa MINIIS 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa MK4 MMU3",
|
||||
"sub_path": "filament/Generic ASA @Prusa MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa MK4S 0.6",
|
||||
"sub_path": "filament/Generic ASA @Prusa MK4S 0.6.json"
|
||||
@@ -2260,6 +2296,10 @@
|
||||
"name": "Prusament ASA @CORE One HF 0.8",
|
||||
"sub_path": "filament/Prusament ASA @CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament ASA @MK4 MMU3",
|
||||
"sub_path": "filament/Prusament ASA @MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament ASA @MK4S 0.6",
|
||||
"sub_path": "filament/Prusament ASA @MK4S 0.6.json"
|
||||
@@ -2392,6 +2432,10 @@
|
||||
"name": "Prusament PC Blend @CORE One HF 0.8",
|
||||
"sub_path": "filament/Prusament PC Blend @CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament PC Blend @MK4 MMU3",
|
||||
"sub_path": "filament/Prusament PC Blend @MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament PC Blend @MK4S 0.6",
|
||||
"sub_path": "filament/Prusament PC Blend @MK4S 0.6.json"
|
||||
@@ -2532,6 +2576,10 @@
|
||||
"name": "Prusament PETG @CORE One HF 0.8",
|
||||
"sub_path": "filament/Prusament PETG @CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament PETG @MK4 MMU3",
|
||||
"sub_path": "filament/Prusament PETG @MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament PETG @MK4S 0.6",
|
||||
"sub_path": "filament/Prusament PETG @MK4S 0.6.json"
|
||||
@@ -2668,6 +2716,10 @@
|
||||
"name": "Prusament PLA @CORE One HF 0.8",
|
||||
"sub_path": "filament/Prusament PLA @CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament PLA @MK4 MMU3",
|
||||
"sub_path": "filament/Prusament PLA @MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament PLA @MK4S 0.6",
|
||||
"sub_path": "filament/Prusament PLA @MK4S 0.6.json"
|
||||
@@ -2704,6 +2756,10 @@
|
||||
"name": "Prusament rPLA @CORE One 0.8",
|
||||
"sub_path": "filament/Prusament rPLA @CORE One 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament rPLA @MK4 MMU3",
|
||||
"sub_path": "filament/Prusament rPLA @MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Prusament rPLA @MK4S 0.6",
|
||||
"sub_path": "filament/Prusament rPLA @MK4S 0.6.json"
|
||||
@@ -2856,6 +2912,10 @@
|
||||
"name": "Generic ABS @Prusa CORE One HF 0.8",
|
||||
"sub_path": "filament/Generic ABS @Prusa CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ABS @Prusa MK4 MMU3",
|
||||
"sub_path": "filament/Generic ABS @Prusa MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic ASA @Prusa MK4S HF0.5",
|
||||
"sub_path": "filament/Generic ASA @Prusa MK4S HF0.5.json"
|
||||
@@ -2904,6 +2964,10 @@
|
||||
"name": "Generic PETG @Prusa CORE One HF 0.8",
|
||||
"sub_path": "filament/Generic PETG @Prusa CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PETG @Prusa MK4 MMU3",
|
||||
"sub_path": "filament/Generic PETG @Prusa MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA @Prusa MK4S HF0.5",
|
||||
"sub_path": "filament/Generic PLA @Prusa MK4S HF0.5.json"
|
||||
@@ -2948,6 +3012,10 @@
|
||||
"name": "Generic PLA @Prusa CORE One HF 0.8",
|
||||
"sub_path": "filament/Generic PLA @Prusa CORE One HF 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA @Prusa MK4 MMU3",
|
||||
"sub_path": "filament/Generic PLA @Prusa MK4 MMU3.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic TPU @Prusa CORE One 0.6",
|
||||
"sub_path": "filament/Generic TPU @Prusa CORE One 0.6.json"
|
||||
@@ -2955,14 +3023,6 @@
|
||||
{
|
||||
"name": "Generic TPU @Prusa CORE One 0.8",
|
||||
"sub_path": "filament/Generic TPU @Prusa CORE One 0.8.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PC @Prusa",
|
||||
"sub_path": "filament/Generic PC @Prusa.json"
|
||||
},
|
||||
{
|
||||
"name": "Generic PLA Silk @Prusa CORE One INDX",
|
||||
"sub_path": "filament/Generic PLA Silk @Prusa CORE One INDX.json"
|
||||
}
|
||||
],
|
||||
"machine_list": [
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "ASYz8yASE82tQDII",
|
||||
"filament_id": "OFY9muEs",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "mzZCm8XDmAR23YR6",
|
||||
"filament_id": "OFY9muEs",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "gJhX2KBpk4wfXIj0",
|
||||
"filament_id": "OFY9muEs",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "JEatvybG9xIkrPk3",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_indx"
|
||||
],
|
||||
"filament_cost": [
|
||||
"34.65"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "0k31oXq1pLQsHvrj",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"40"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Generic ABS @Prusa MK4 MMU3",
|
||||
"inherits": "Generic ABS @Prusa MK4",
|
||||
"from": "system",
|
||||
"setting_id": "uFmixo2FX1yxNsDB",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"35"
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "eKDfiVRE59itoppm",
|
||||
"filament_id": "OFLPAxz3",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "ZjHf5Vvtf4ESNnWb",
|
||||
"filament_id": "OFLPAxz3",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "aRDgUgQJWdNDJx5c",
|
||||
"filament_id": "OFLPAxz3",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "Q7fgojkHRZI0floy",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"40"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Generic ASA @Prusa MK4 MMU3",
|
||||
"inherits": "Generic ASA @Prusa MK4",
|
||||
"from": "system",
|
||||
"setting_id": "iq9bFkpmAOJ4ae4g",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"35"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Generic PC @Prusa MK4 MMU3",
|
||||
"inherits": "Generic PC @Prusa",
|
||||
"from": "system",
|
||||
"setting_id": "qMawlK9QPI7DcF8I",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "7oUyjvRHLH4bJyX0",
|
||||
"filament_id": "OFYPdQJh",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "6HiIOGURZWwman1l",
|
||||
"filament_id": "OFYPdQJh",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "KKiug7A9NOQPe1m7",
|
||||
"filament_id": "OFYPdQJh",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "HbevqxqvYHuvRc3v",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_indx"
|
||||
],
|
||||
"filament_cost": [
|
||||
"27.82"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "W71PW5bpf7CV16D4",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"35"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Generic PETG @Prusa MK4 MMU3",
|
||||
"inherits": "Generic PETG @Prusa MK4",
|
||||
"from": "system",
|
||||
"setting_id": "lBiZBFpSXZNIp5XG",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"35"
|
||||
]
|
||||
}
|
||||
@@ -8,12 +8,16 @@
|
||||
"filament_id": "OFDSrzZ8",
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One 0.6 nozzle"
|
||||
"Prusa CORE One 0.6 nozzle",
|
||||
"Prusa CORE One MMU3 0.6 nozzle"
|
||||
],
|
||||
"nozzle_temperature": [
|
||||
"210"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"1.5"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "vniEPaoyABoAfLFa",
|
||||
"filament_id": "OFDSrzZ8",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle",
|
||||
"Prusa CORE One L HF 0.4 nozzle"
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "NPEs4gtBlKWupYZX",
|
||||
"filament_id": "OFDSrzZ8",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "SnfULjrWGCiA7ljq",
|
||||
"filament_id": "OFDSrzZ8",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
@@ -24,5 +27,8 @@
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"15"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"1.5"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "4qjcaIf1vk3K1x4Z",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_indx"
|
||||
],
|
||||
"filament_cost": [
|
||||
"25.4"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "r2i4c7DsYXYIMVCX",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"20"
|
||||
],
|
||||
@@ -17,8 +20,7 @@
|
||||
"Prusa CORE One L 0.4 nozzle",
|
||||
"Prusa CORE One MMU3 0.4 nozzle",
|
||||
"Prusa CORE One MMU3 0.25 nozzle",
|
||||
"Prusa CORE One MMU3 0.3 nozzle",
|
||||
"Prusa CORE One MMU3 0.6 nozzle"
|
||||
"Prusa CORE One MMU3 0.3 nozzle"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"40"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Generic PLA @Prusa MK4 MMU3",
|
||||
"inherits": "Generic PLA @Prusa MK4",
|
||||
"from": "system",
|
||||
"setting_id": "kZvIh84niJ487FsY",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
]
|
||||
}
|
||||
@@ -29,5 +29,8 @@
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"1.5"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "eKla6su22rYaL9iz",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_indx"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1",
|
||||
"1",
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"from": "system",
|
||||
"setting_id": "F9lNHyhZwZi7aD74",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"20"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "tXktQXr79am1s5UL",
|
||||
"filament_id": "OFmFwUWM",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "YbyojtjXvEtI4Tt3",
|
||||
"filament_id": "OFmFwUWM",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "35H9TLr1p2Jy767g",
|
||||
"filament_id": "OFmFwUWM",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "UUTjJ4Y8St3c87jD",
|
||||
"filament_id": "OFmFwUWM",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"40"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Prusament ASA @MK4 MMU3",
|
||||
"inherits": "Prusament ASA @MK4",
|
||||
"from": "system",
|
||||
"setting_id": "IRJ2dtBgjCMfmpOH",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"35"
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "sNGgucQjiFXwuFkq",
|
||||
"filament_id": "OF4MZtnE",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"40"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "pEsP537TBR5kozQB",
|
||||
"filament_id": "OFvDbkFq",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "3IfLi1ZVCKKbEBUW",
|
||||
"filament_id": "OFvDbkFq",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "Q8qWw2G7l2UvxGut",
|
||||
"filament_id": "OFvDbkFq",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "x1SkWhSSkVVIPwCF",
|
||||
"filament_id": "OFvDbkFq",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"40"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Prusament PC Blend @MK4 MMU3",
|
||||
"inherits": "Prusament PC Blend @MK4",
|
||||
"from": "system",
|
||||
"setting_id": "gmNuCspHHxgP6zH8",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
]
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "B8Cm9l8i9osp5J2q",
|
||||
"filament_id": "OFdO6vpt",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"40"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "lwT1VxW5OkY36kUX",
|
||||
"filament_id": "OFkR8E2c",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "XAgW7fElXxhaKaBT",
|
||||
"filament_id": "OFkR8E2c",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.5 nozzle"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "6aTGEZj2EmpTR1mu",
|
||||
"filament_id": "OFkR8E2c",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "LSvba9ds3XTwDMCG",
|
||||
"filament_id": "OFkR8E2c",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"35"
|
||||
],
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Prusament PETG @MK4 MMU3",
|
||||
"inherits": "Prusament PETG @MK4",
|
||||
"from": "system",
|
||||
"setting_id": "7DQ6YEKyQKYDTKtE",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pet"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"35"
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "2viuOmBvXuQm2FwV",
|
||||
"filament_id": "OFkR8E2c",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_indx"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1"
|
||||
],
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "mf4Muog4IcsgkYoA",
|
||||
"filament_id": "OFnO4wnf",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla_hf"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One HF 0.4 nozzle",
|
||||
"Prusa CORE One L HF 0.4 nozzle"
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "PuujUMjNKLZvZNAH",
|
||||
"filament_id": "OFnO4wnf",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"20"
|
||||
],
|
||||
@@ -104,5 +107,11 @@
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"100"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"100"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Prusament PLA @MK4 MMU3",
|
||||
"inherits": "Prusament PLA @MK4",
|
||||
"from": "system",
|
||||
"setting_id": "MsDcN4npnmLRXOjm",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"100"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"100"
|
||||
]
|
||||
}
|
||||
@@ -6,6 +6,9 @@
|
||||
"setting_id": "B2UB9QUbbjpn12Ka",
|
||||
"filament_id": "OFnO4wnf",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_indx"
|
||||
],
|
||||
"filament_flow_ratio": [
|
||||
"1"
|
||||
],
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "085dGkrnPhatF07x",
|
||||
"filament_id": "OFh7mvPO",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"20"
|
||||
],
|
||||
@@ -105,5 +108,11 @@
|
||||
],
|
||||
"slow_down_min_speed": [
|
||||
"20"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"60"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"60"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,12 +8,16 @@
|
||||
"instantiation": "true",
|
||||
"compatible_printers": [
|
||||
"Prusa CORE One 0.6 nozzle",
|
||||
"Prusa CORE One HF 0.6 nozzle"
|
||||
"Prusa CORE One HF 0.6 nozzle",
|
||||
"Prusa CORE One MMU3 0.6 nozzle"
|
||||
],
|
||||
"filament_max_volumetric_speed": [
|
||||
"14"
|
||||
],
|
||||
"slow_down_layer_time": [
|
||||
"15"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"1.5"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
"setting_id": "Bw6H4vFnq7YExyLd",
|
||||
"filament_id": "OFWRITFw",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"chamber_temperature": [
|
||||
"20"
|
||||
],
|
||||
@@ -22,8 +25,7 @@
|
||||
"Prusa CORE One HF 0.5 nozzle",
|
||||
"Prusa CORE One MMU3 0.4 nozzle",
|
||||
"Prusa CORE One MMU3 0.25 nozzle",
|
||||
"Prusa CORE One MMU3 0.3 nozzle",
|
||||
"Prusa CORE One MMU3 0.6 nozzle"
|
||||
"Prusa CORE One MMU3 0.3 nozzle"
|
||||
],
|
||||
"fan_cooling_layer_time": [
|
||||
"40"
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "Prusament rPLA @MK4 MMU3",
|
||||
"inherits": "Prusament rPLA @MK4",
|
||||
"from": "system",
|
||||
"setting_id": "wKhvyGpgjmW6glLj",
|
||||
"instantiation": "true",
|
||||
"include": [
|
||||
"fdm_filament_template_nextruder_pla"
|
||||
],
|
||||
"compatible_printers": [
|
||||
"Prusa MK4 MMU3 0.4 nozzle",
|
||||
"Prusa MK4 MMU3 0.6 nozzle",
|
||||
"Prusa MK4 MMU3 HF0.4 nozzle"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "fdm_filament_template_indx",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"filament_multitool_ramming": [
|
||||
"1"
|
||||
],
|
||||
"filament_multitool_ramming_flow": [
|
||||
"30"
|
||||
],
|
||||
"filament_multitool_ramming_volume": [
|
||||
"18"
|
||||
],
|
||||
"filament_minimal_purge_on_wipe_tower": [
|
||||
"10"
|
||||
],
|
||||
"filament_start_gcode": [
|
||||
"M572 S{pressure_advance[filament_extruder_id]} ; Pressure advance\nM573 R"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "fdm_filament_template_nextruder_pet",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"filament_ramming_parameters": [
|
||||
"250 100 42.4194 42.4194 42.4194 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387"
|
||||
],
|
||||
"filament_loading_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_loading_speed_start": [
|
||||
"50"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"60"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"60"
|
||||
],
|
||||
"filament_cooling_moves": [
|
||||
"3"
|
||||
],
|
||||
"filament_cooling_initial_speed": [
|
||||
"5"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"2.5"
|
||||
],
|
||||
"filament_stamping_distance": [
|
||||
"45"
|
||||
],
|
||||
"filament_stamping_loading_speed": [
|
||||
"26.5"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "fdm_filament_template_nextruder_pet_hf",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"filament_ramming_parameters": [
|
||||
"250 100 42.4194 42.4194| 0.05 42.4387 0.45 42.4387 0.95 42.4387 1.45 42.4387 1.95 42.4387 2.45 42.4387 2.95 42.4387 3.45 42.4387 3.95 42.4387 4.45 42.4387 4.95 42.4387"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "fdm_filament_template_nextruder_pla",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"filament_ramming_parameters": [
|
||||
"250 100 40.1613 40.3548 40.4516 40.3548 40.2581| 0.05 40.1483 0.45 40.3419 0.95 40.3419 1.45 40.3419 1.95 40.3419 2.45 40.3419 2.95 40.3419 3.45 40.3419 3.95 40.3419 4.45 40.3419 4.95 40.3419"
|
||||
],
|
||||
"filament_loading_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_loading_speed_start": [
|
||||
"50"
|
||||
],
|
||||
"filament_unloading_speed": [
|
||||
"40"
|
||||
],
|
||||
"filament_unloading_speed_start": [
|
||||
"30"
|
||||
],
|
||||
"filament_cooling_moves": [
|
||||
"2"
|
||||
],
|
||||
"filament_cooling_initial_speed": [
|
||||
"10"
|
||||
],
|
||||
"filament_cooling_final_speed": [
|
||||
"3.5"
|
||||
],
|
||||
"filament_stamping_distance": [
|
||||
"45"
|
||||
],
|
||||
"filament_stamping_loading_speed": [
|
||||
"29"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"type": "filament",
|
||||
"name": "fdm_filament_template_nextruder_pla_hf",
|
||||
"from": "system",
|
||||
"instantiation": "false",
|
||||
"filament_ramming_parameters": [
|
||||
"250 100 39.871 39.871| 0.05 39.9547 0.45 39.9547 0.95 39.9547 1.45 39.9547 1.95 39.9547 2.45 39.9547 2.95 39.9547 3.45 39.9547 3.95 39.9547 4.45 39.9547 4.95 39.9547"
|
||||
]
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"name": "Prusa CORE One MMU3",
|
||||
"bed_model": "coreone_bed.stl",
|
||||
"bed_texture": "coreone.svg",
|
||||
"default_materials": "Generic ABS @Prusa CORE One;Generic ASA @Prusa CORE One;Generic PETG @Prusa CORE One;Generic PLA @Prusa CORE One;Generic PLA Silk @Prusa CORE One;Generic TPU @Prusa CORE One",
|
||||
"default_materials": "Generic ABS @Prusa CORE One;Generic ASA @Prusa CORE One;Generic PETG @Prusa CORE One;Generic PLA @Prusa CORE One;Generic PLA Silk @Prusa CORE One;Generic TPU @Prusa CORE One;Generic PLA @Prusa CORE One 0.6",
|
||||
"family": "Prusa",
|
||||
"hotend_model": "",
|
||||
"machine_tech": "FFF",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"printer_model": "Prusa MK4 MMU3",
|
||||
"default_filament_profile": [
|
||||
"Generic PLA @System"
|
||||
"Generic PLA @Prusa MK4 MMU3"
|
||||
],
|
||||
"default_print_profile": "0.20mm SPEED @MK4 MMU3 0.4",
|
||||
"printer_notes": "Don't remove the following keywords! These keywords are used in the \"compatible printer\" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_MODEL_MK4IS\nPRINTER_MODEL_MK4MMU3\nPG"
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"printer_model": "Prusa MK4 MMU3",
|
||||
"printer_variant": "0.6",
|
||||
"default_filament_profile": [
|
||||
"Generic PLA @System"
|
||||
"Generic PLA @Prusa MK4 MMU3"
|
||||
],
|
||||
"default_print_profile": "0.25mm SPEED @MK4 MMU3 0.6",
|
||||
"printer_notes": "Don't remove the following keywords! These keywords are used in the \"compatible printer\" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_MODEL_MK4IS\nPRINTER_MODEL_MK4MMU3\nPG"
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"bed_model": "mk4_bed.stl",
|
||||
"bed_texture": "mk4is.svg",
|
||||
"hotend_model": "",
|
||||
"default_materials": "Generic PLA @System;Generic PETG @System;Generic ABS @System"
|
||||
"default_materials": "Generic PLA @Prusa MK4 MMU3;Generic PETG @Prusa MK4 MMU3;Generic ABS @Prusa MK4 MMU3"
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
],
|
||||
"printer_model": "Prusa MK4 MMU3 HF",
|
||||
"default_filament_profile": [
|
||||
"Generic PLA @System"
|
||||
"Generic PLA @Prusa MK4 MMU3"
|
||||
],
|
||||
"default_print_profile": "0.20mm SPEED @MK4 MMU3 HF0.4",
|
||||
"printer_notes": "Don't remove the following keywords! These keywords are used in the \"compatible printer\" condition of the print and filament profiles to link the particular print and filament profiles to this printer profile.\nPRINTER_MODEL_MK4IS\nPRINTER_MODEL_MK4MMU3\nPG\nHF_NOZZLE"
|
||||
|
||||
@@ -8,5 +8,5 @@
|
||||
"bed_model": "mk4_bed.stl",
|
||||
"bed_texture": "mk4is.svg",
|
||||
"hotend_model": "",
|
||||
"default_materials": "Generic PLA @System;Generic PETG @System;Generic ABS @System"
|
||||
"default_materials": "Generic PLA @Prusa MK4 MMU3;Generic PETG @Prusa MK4 MMU3;Generic ABS @Prusa MK4 MMU3"
|
||||
}
|
||||
|
||||
@@ -147,6 +147,7 @@ def seed_datadir(datadir):
|
||||
# ~90% case expects. A ladder whose result depends on the developer's own preferences is not
|
||||
# a gate.
|
||||
app["auto_close_sketch_loops"] = True
|
||||
app["design_show_bed"] = True
|
||||
# SILENCE THE NETWORK PLUGIN PROMPT. Without this, GUI_App::post_init() re-raises "Bambu
|
||||
# Network Plug-in Required" from an IDLE event — after any modal sweep this driver does at
|
||||
# startup — and ShowModal() then runs a nested event loop. The app is alive, its window is
|
||||
@@ -480,10 +481,14 @@ def type_into_open_field(value, mark):
|
||||
def enter_sketch(timeout=180):
|
||||
"""Open a real sketch on a real plane, and PROVE it with the socket before drawing anything.
|
||||
|
||||
THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. Shift+S enters sketch MODE and pops the plane
|
||||
offer; the offer must be dismissed; and the plane itself is chosen by clicking it in the
|
||||
viewport BEFORE Shift+S. check-gui-sketching.py has always done all four steps. This ladder
|
||||
did two of them — Design tab, then Shift+S — and went straight to the tool letters.
|
||||
THE SEQUENCE MATTERS AND IT IS NOT OBVIOUS. The reference planes are hidden until Shift+S,
|
||||
with nothing picked, brings them up to choose from, so the plane is clicked AFTER it. That
|
||||
click opens the session on the plane and pops the tool offer, and a click outside a popup
|
||||
menu only closes it, so the spot is clicked twice, apart: the second click closes the offer,
|
||||
and inside the session it is empty canvas. No Escape — with no offer up, Escape in an empty
|
||||
sketch leaves it. Then a tool letter arms the tool. check-gui-sketching.py does the same
|
||||
steps. This ladder once did two of them — Design tab, then Shift+S — and went straight to
|
||||
the tool letters.
|
||||
|
||||
That intermediate state is the trap. `is_sketching` reads 1, every tool key is accepted and
|
||||
traced, and not one click draws anything, because there is no plane under them. The ladder
|
||||
@@ -499,10 +504,10 @@ def enter_sketch(timeout=180):
|
||||
click(132, 53) # Design tab
|
||||
time.sleep(2.0)
|
||||
dismiss_modals()
|
||||
click(*PLANE_PX) # pick the plane IN THE VIEWPORT — before Shift+S
|
||||
key("shift+s", 1.0)
|
||||
key("Escape", 0.5) # entering sketch mode pops the offer; dismiss it
|
||||
key("p", 0.6) # any sketch tool starts the session on that plane
|
||||
key("shift+s", 1.0) # nothing picked: the reference planes come up
|
||||
click(*PLANE_PX, pause=0.8) # picks the plane IN THE VIEWPORT: session open, offer up...
|
||||
click(*PLANE_PX) # ...and this closes the offer
|
||||
key("p", 0.6) # arms Point in the session
|
||||
if try_call("sketch_describe") is not None:
|
||||
# NO Escape here. Every rung already opens with one to drop whatever tool the last
|
||||
# one left armed, and Escape in the Design tab walks a LIFO: first press drops the
|
||||
@@ -511,14 +516,15 @@ def enter_sketch(timeout=180):
|
||||
# reported all eleven checks failed with "nothing opened" — the tools were arming
|
||||
# into an empty Feature-mode document.
|
||||
return
|
||||
die("no sketch opened after plane click + Shift+S within "
|
||||
die("no sketch opened after Shift+S + plane click within "
|
||||
f"{timeout}s — sketch_describe never answered on {A.sock} (trace {TRACE})")
|
||||
|
||||
|
||||
# tool key, the clicks that draw it, and one distinct value per queued field. The values are
|
||||
# deliberately nothing like the as-drawn size, so a committed prefill cannot coincide with them.
|
||||
# Where the plane label sits in the viewport before a sketch is open. Same constant the gesture
|
||||
# ladder uses; it is a label on the 3D view, not a widget, so it moves only if the camera does.
|
||||
# Where the plane label sits in the viewport once Shift+S has brought the planes up. Same
|
||||
# constant the gesture ladder uses; it is a label on the 3D view, not a widget, so it moves only
|
||||
# if the camera does.
|
||||
PLANE_PX = (913, 359)
|
||||
|
||||
# Every coordinate below stays inside 1000..1400 x 500..760 — the box check-gui-sketching.py's
|
||||
|
||||
@@ -229,7 +229,20 @@ CHROME_DY = int(os.environ.get("ORCA_CAD_CHROME_DY", "26"))
|
||||
|
||||
DESIGN_TAB = (128, 29 + CHROME_DY)
|
||||
|
||||
# Feature-tree rows, measured on the rig at 1920x1080: first row centre, then 23 px apart.
|
||||
# The ribbon's document group lost its Bed checkbox and label when the bed toggle moved to the
|
||||
# Feature tree, and every group to its right moved left by their width: wxLEFT 6 + the 18 px
|
||||
# CheckBox + wxLEFT 4 + the "Bed" label (~24 px) + wxRIGHT 4, about 56 px. The x of
|
||||
# CONSTRUCTION_CHECKBOX, CON_BTN and CON_BTN_SKETCH below is the one measured before that, plus
|
||||
# this. DERIVED, NOT MEASURED: re-measure on the rig and fold it in. CONFIRM_BTN sits after the
|
||||
# ribbon's stretch spacer and did not move.
|
||||
RIBBON_DX = -56
|
||||
|
||||
# Feature-tree rows, measured on the rig at 1920x1080: the list's first row is centred at y=215,
|
||||
# then 23 px apart. The first two rows are the Origin and the Bed, unframed on the card above the
|
||||
# features' own framed list, so the first FEATURE row sits two rows and TREE_GAP further down;
|
||||
# reset_document and the reopening double-click both need a feature. TREE_GAP is the list's
|
||||
# 12 px top margin plus its 1 px frame, derived rather than measured -- re-measure on the rig, like
|
||||
# RIBBON_DX.
|
||||
# x=300, not the label: a second click ON the label opens the inline rename, and Delete then
|
||||
# edits the text instead of removing the feature.
|
||||
#
|
||||
@@ -237,7 +250,8 @@ DESIGN_TAB = (128, 29 + CHROME_DY)
|
||||
# unshifted click lands 26 px BELOW the first row -- just past its 23 px height -- so the row is
|
||||
# never selected, Delete does nothing, and reset_document spends 40 rounds on it before dying
|
||||
# with "could not empty the feature tree". That names the feature tree, which is not the fault.
|
||||
TREE_ROW0 = (300, 215 + CHROME_DY)
|
||||
TREE_GAP = 12 + 1
|
||||
TREE_ROW0 = (300, 215 + 2 * 23 + TREE_GAP + CHROME_DY)
|
||||
|
||||
|
||||
def go_design():
|
||||
@@ -266,18 +280,21 @@ def reset_document():
|
||||
def enter_sketch(tool_key, plane_px=(913, 359)):
|
||||
"""Enter a sketch the way the design law says: pick the plane in the viewport, then the tool.
|
||||
|
||||
Shift+S enters sketch MODE and pops the offer; Escape dismisses it; the tool letter then
|
||||
starts the session on the plane the click selected. All four steps are real input — nothing
|
||||
here goes through the socket.
|
||||
leave_sketch()'s Escapes let go of every pick, so Shift+S finds nothing to sketch on and only
|
||||
brings the reference planes up; the click on one opens the session on it and pops the tool
|
||||
offer. A click outside a popup menu only closes it, so the same spot is clicked again, apart:
|
||||
inside the session it is empty canvas, so it selects nothing if the offer is slow to appear.
|
||||
No Escape: with no offer up, Escape in an empty sketch leaves it. The tool letter then arms
|
||||
the tool. All of it is real input — nothing here goes through the socket.
|
||||
"""
|
||||
leave_sketch()
|
||||
click(*plane_px)
|
||||
key("shift+s", 0.8)
|
||||
key("Escape", 0.4) # entering sketch mode pops the offer; dismiss it
|
||||
click(*plane_px, pause=0.8) # picks the plane: the session opens and the offer pops...
|
||||
click(*plane_px) # ...and this closes the offer
|
||||
key("p", 0.6)
|
||||
if try_call("sketch_describe") is None:
|
||||
shot("/shots/gl-enter-failed.png")
|
||||
die("no sketch opened after plane click + Shift+S (see /shots/gl-enter-failed.png)")
|
||||
die("no sketch opened after Shift+S + plane click (see /shots/gl-enter-failed.png)")
|
||||
calibrate_here() # THIS sketch's own camera map, on THIS sketch's own plane
|
||||
key(tool_key, 0.6)
|
||||
|
||||
@@ -737,7 +754,7 @@ def corner_pair(ents):
|
||||
die("no adjacent pair in what should be a rectangle")
|
||||
|
||||
|
||||
CONSTRUCTION_CHECKBOX = (419, 75 + CHROME_DY)
|
||||
CONSTRUCTION_CHECKBOX = (419 + RIBBON_DX, 75 + CHROME_DY)
|
||||
|
||||
|
||||
def draw_line(x0, y0, x1, y1, length, angle):
|
||||
@@ -866,7 +883,7 @@ def rung_extend():
|
||||
# because the ladder only ever clicks "perpendicular" (3) and "equal" (5), both of which sit
|
||||
# before the first insertion. The next rung to use "tangent" would have clicked "collinear".
|
||||
CON_BTN_Y = 76 + CHROME_DY
|
||||
CON_BTN = {n: (449 + 42 * i, CON_BTN_Y) for i, n in enumerate(
|
||||
CON_BTN = {n: (449 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate(
|
||||
["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal",
|
||||
"equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric",
|
||||
"sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])}
|
||||
@@ -880,7 +897,7 @@ CON_BTN = {n: (449 + 42 * i, CON_BTN_Y) for i, n in enumerate(
|
||||
# which already carries CHROME_DY), but the X start depends on how wide the sketch toolbar to the
|
||||
# left of this group renders, and this fork keeps mainline's top row. Re-measure before trusting
|
||||
# D11 here: screenshot in sketch mode and detect the icon columns, do not derive it by offset.
|
||||
CON_BTN_SKETCH = {n: (677 + 42 * i, CON_BTN_Y) for i, n in enumerate(
|
||||
CON_BTN_SKETCH = {n: (677 + RIBBON_DX + 42 * i, CON_BTN_Y) for i, n in enumerate(
|
||||
["horizontal", "vertical", "parallel", "perpendicular", "coincident", "equal",
|
||||
"equal_radius", "collinear", "concentric", "tangent", "midpoint", "symmetric",
|
||||
"sym_v", "sym_h", "angle", "radius", "diameter", "fix", "dist_x", "dist_y"])}
|
||||
|
||||
@@ -229,11 +229,11 @@
|
||||
"none"
|
||||
],
|
||||
"needs": {},
|
||||
"refusal": "Click a face or a reference plane in the viewport, then a sketch tool",
|
||||
"refusal": "Select a flat face or a reference plane to sketch on",
|
||||
"gui": true,
|
||||
"action": "key:S+S",
|
||||
"icon": "design_sketch",
|
||||
"hint": "Click a face or a reference plane, then pick a drawing tool"
|
||||
"hint": "Sketch on the selected flat face or plane, or click one next"
|
||||
},
|
||||
{
|
||||
"id": "extrude",
|
||||
@@ -1113,6 +1113,32 @@
|
||||
"icon": "color_palette",
|
||||
"hint": "Set the selected body's display color"
|
||||
},
|
||||
{
|
||||
"id": "zoom_to",
|
||||
"name": "Zoom to selection",
|
||||
"slot": "top",
|
||||
"key": null,
|
||||
"feature": null,
|
||||
"mcp": null,
|
||||
"mode": "model",
|
||||
"accepts": [
|
||||
"body_solid",
|
||||
"body_sheet",
|
||||
"face_planar",
|
||||
"face_cyl",
|
||||
"face_other",
|
||||
"edge_str",
|
||||
"edge_circ",
|
||||
"vertex",
|
||||
"sk_loop"
|
||||
],
|
||||
"needs": {},
|
||||
"refusal": null,
|
||||
"gui": true,
|
||||
"action": "btn:zoom_to",
|
||||
"icon": "design_zoom",
|
||||
"hint": "Frame the selection in the view, keeping the view direction"
|
||||
},
|
||||
{
|
||||
"id": "delete",
|
||||
"name": "Delete",
|
||||
|
||||
@@ -370,6 +370,10 @@ void AppConfig::set_defaults()
|
||||
// restores the conventional CAD representation for users who expect it (x0kd).
|
||||
if (get("design_connector_face_glyph").empty())
|
||||
set_bool("design_connector_face_glyph", true);
|
||||
|
||||
// Design tab: draw the printer bed and its plate grid (the Feature tree's Bed row).
|
||||
if (get("design_show_bed").empty())
|
||||
set_bool("design_show_bed", true);
|
||||
#endif
|
||||
|
||||
//#ifdef SUPPORT_SHOW_HINTS
|
||||
|
||||
@@ -352,6 +352,16 @@ void FillSpiralInset::_fill_surface_single(const FillParams& params,
|
||||
assert(params.use_arachne);
|
||||
assert(this->print_config != nullptr && this->print_object_config != nullptr);
|
||||
|
||||
// Internal solid infill must not add Arachne's standalone thin walls: one of those walls can
|
||||
// become the extra centre point after the spiral has finished. Top and bottom surfaces keep
|
||||
// Arachne, including the centre plug handled by generate_spiral_insets().
|
||||
if (params.extrusion_role == erSolidInfill) {
|
||||
Polylines polylines;
|
||||
this->_fill_surface_single(params, thickness_layers, direction, expolygon, polylines);
|
||||
append(thick_polylines_out, to_thick_polylines(std::move(polylines), scaled<coord_t>(this->spacing)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Only a solid surface is worth the variable width walls; a sparse one falls back to plain loops.
|
||||
if (params.density <= 0.9999f || params.dont_adjust) {
|
||||
Polylines polylines;
|
||||
|
||||
@@ -61,6 +61,8 @@ public:
|
||||
bool is_smaller_width_perimeter;
|
||||
// Depth in the hierarchy. External perimeter has depth = 0. An external perimeter could be both a contour and a hole.
|
||||
unsigned short depth;
|
||||
// ORCA: an external hole perimeter touching the next outer wall all around, with nothing between them.
|
||||
bool is_thin_wall_hole = false;
|
||||
// Children contour, may be both CCW and CW oriented (outer contours or holes).
|
||||
std::vector<PerimeterGeneratorLoop> children;
|
||||
|
||||
@@ -118,7 +120,7 @@ static bool detect_steep_overhang(const PrintRegionConfig *config,
|
||||
}
|
||||
|
||||
static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perimeter_generator, const PerimeterGeneratorLoops &loops, ThickPolylines &thin_walls,
|
||||
bool &steep_overhang_contour, bool &steep_overhang_hole, bool reverse_thin_wall_hole)
|
||||
bool &steep_overhang_contour, bool &steep_overhang_hole)
|
||||
{
|
||||
// loops is an arrayref of ::Loop objects
|
||||
// turn each one into an ExtrusionLoop object
|
||||
@@ -285,24 +287,17 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
|
||||
} else {
|
||||
const PerimeterGeneratorLoop &loop = loops[idx.first];
|
||||
assert(thin_walls.empty());
|
||||
const bool reverse_children_thin_wall_hole = loops.size() == 1 && loop.is_contour && loop.children.size() == 1 &&
|
||||
(!loop.children.front().is_contour) && loop.children.front().children.empty();
|
||||
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls, steep_overhang_contour,
|
||||
steep_overhang_hole, reverse_children_thin_wall_hole);
|
||||
ExtrusionEntityCollection children = traverse_loops(perimeter_generator, loop.children, thin_walls, steep_overhang_contour, steep_overhang_hole);
|
||||
out.entities.reserve(out.entities.size() + children.entities.size() + 1);
|
||||
ExtrusionLoop *eloop = static_cast<ExtrusionLoop*>(coll.entities[idx.first]);
|
||||
coll.entities[idx.first] = nullptr;
|
||||
|
||||
if ((perimeter_generator.config->wall_direction == WallDirection::CounterClockwise) == (loop.is_contour || reverse_thin_wall_hole))
|
||||
// Orca: holes run against the contour, except thin wall holes, which run with it.
|
||||
if ((perimeter_generator.config->wall_direction == WallDirection::CounterClockwise) == (loop.is_contour || loop.is_thin_wall_hole))
|
||||
eloop->make_counter_clockwise();
|
||||
else
|
||||
eloop->make_clockwise();
|
||||
|
||||
// Orca: Reverse print order for thin wall holes.
|
||||
if (reverse_thin_wall_hole) {
|
||||
std::reverse(out.entities.begin(), out.entities.end());
|
||||
}
|
||||
|
||||
eloop->inset_idx = loop.depth;
|
||||
if (loop.is_contour) {
|
||||
out.append(std::move(children.entities));
|
||||
@@ -759,6 +754,59 @@ static void clip_inner_walls_over_top(std::vector<Arachne::VariableWidthLines> &
|
||||
}
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top - widest bead of the given walls.
|
||||
static coord_t widest_bead(const std::vector<Arachne::VariableWidthLines> &walls)
|
||||
{
|
||||
coord_t widest = 0;
|
||||
for (const Arachne::VariableWidthLines &group : walls)
|
||||
for (const Arachne::ExtrusionLine &el : group)
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
widest = std::max(widest, j.w);
|
||||
return widest;
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top - length of the walls running further than tolerance from the reference walls, outside the
|
||||
// excluded area.
|
||||
static double length_off_reference(const std::vector<Arachne::VariableWidthLines> &walls, const Arachne::VariableWidthLines &reference,
|
||||
const ExPolygons &excluded, coord_t tolerance)
|
||||
{
|
||||
auto append_centerlines = [](const Arachne::VariableWidthLines &lines, Polylines &out) {
|
||||
for (const Arachne::ExtrusionLine &el : lines) {
|
||||
if (el.junctions.size() < 2)
|
||||
continue;
|
||||
Polyline ¢erline = out.emplace_back();
|
||||
centerline.points.reserve(el.junctions.size());
|
||||
for (const Arachne::ExtrusionJunction &j : el.junctions)
|
||||
centerline.points.emplace_back(j.p);
|
||||
}
|
||||
};
|
||||
Polylines wall_centerlines;
|
||||
Polylines reference_centerlines;
|
||||
for (const Arachne::VariableWidthLines &group : walls)
|
||||
append_centerlines(group, wall_centerlines);
|
||||
append_centerlines(reference, reference_centerlines);
|
||||
|
||||
Polylines off_reference = diff_pl(wall_centerlines, offset(reference_centerlines, float(tolerance)));
|
||||
if (! excluded.empty())
|
||||
off_reference = diff_pl(off_reference, excluded);
|
||||
return total_length(off_reference);
|
||||
}
|
||||
|
||||
// ORCA: only_one_wall_top - area covered by the given walls at their local widths.
|
||||
static Polygons walls_footprint(const Arachne::VariableWidthLines &walls)
|
||||
{
|
||||
Polygons footprint;
|
||||
for (const Arachne::ExtrusionLine &el : walls)
|
||||
for (size_t i = 1; i < el.junctions.size(); ++ i) {
|
||||
const Arachne::ExtrusionJunction &a = el.junctions[i - 1];
|
||||
const Arachne::ExtrusionJunction &b = el.junctions[i];
|
||||
const coord_t width = std::max(a.w, b.w);
|
||||
if (width > 0)
|
||||
append(footprint, offset(Polyline(a.p, b.p), float(width) / 2.f));
|
||||
}
|
||||
return union_(footprint);
|
||||
}
|
||||
|
||||
void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExPolygons &top_fills,
|
||||
ExPolygons &non_top_polygons, ExPolygons &fill_clip) const {
|
||||
// other perimeters
|
||||
@@ -1446,6 +1494,8 @@ void PerimeterGenerator::process_classic()
|
||||
coord_t min_spacing = coord_t(perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
coord_t ext_min_spacing = coord_t(ext_perimeter_spacing * (1 - INSET_OVERLAP_TOLERANCE));
|
||||
bool has_gap_fill = this->config->gap_infill_speed.get_at(get_extruder_index(*print_config, this->config->outer_wall_filament_id - 1)) > 0;
|
||||
// ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap
|
||||
const double gap_fill_min_width = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
|
||||
// BBS: this flow is for smaller external perimeter for small area
|
||||
coord_t ext_min_spacing_smaller = coord_t(ext_perimeter_spacing * (1 - SMALLER_EXT_INSET_OVERLAP_TOLERANCE));
|
||||
@@ -1640,6 +1690,15 @@ void PerimeterGenerator::process_classic()
|
||||
|
||||
last = std::move(offsets);
|
||||
|
||||
// ORCA: a thin wall hole has no room for gap fill or an inner wall anywhere beside its outer wall.
|
||||
if (i == 0 && ! holes[0].empty()) {
|
||||
const float room = float(0.5 * (ext_perimeter_spacing2 + gap_fill_min_width));
|
||||
const Polygons reach = to_polygons(offset2_ex(last, -room, room + float(SCALED_EPSILON)));
|
||||
for (PerimeterGeneratorLoop &hole : holes[0])
|
||||
hole.is_thin_wall_hole = intersection_pl(Polylines{ hole.polygon.split_at_first_point() },
|
||||
ClipperUtils::clip_clipper_polygons_with_subject_bbox(reach, get_extents(hole.polygon).inflated(SCALED_EPSILON))).empty();
|
||||
}
|
||||
|
||||
//BBS: refer to superslicer
|
||||
//store surface for top infill if only_one_wall_top
|
||||
if (i == 0 && i!=loop_number && only_one_wall_top && !surface.is_bridge() && this->upper_slices != NULL) {
|
||||
@@ -1766,7 +1825,7 @@ void PerimeterGenerator::process_classic()
|
||||
steep_overhang_contour = true;
|
||||
steep_overhang_hole = true;
|
||||
}
|
||||
ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls, steep_overhang_contour, steep_overhang_hole, false);
|
||||
ExtrusionEntityCollection entities = traverse_loops(*this, contours.front(), thin_walls, steep_overhang_contour, steep_overhang_hole);
|
||||
// All walls are counter-clockwise initially, so we don't need to reorient it if that's what we want
|
||||
if (config->overhang_reverse) {
|
||||
reorient_perimeters(entities, steep_overhang_contour, steep_overhang_hole,
|
||||
@@ -1893,8 +1952,7 @@ void PerimeterGenerator::process_classic()
|
||||
|
||||
// fill gaps
|
||||
if (! gaps.empty()) { // collapse
|
||||
// ORCA: Use the smaller width as the lower bound to avoid overestimating safe overlap
|
||||
double min = 0.2 * std::min(perimeter_width, ext_perimeter_width) * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
double min = gap_fill_min_width;
|
||||
double max = 2. * perimeter_spacing;
|
||||
ExPolygons gaps_ex = diff_ex(
|
||||
//FIXME offset2 would be enough and cheaper.
|
||||
@@ -2546,44 +2604,90 @@ void PerimeterGenerator::process_arachne()
|
||||
if (inner_loop_number >= 0) {
|
||||
assert(upper_slices != nullptr);
|
||||
|
||||
// Infill contour bounding box.
|
||||
BoundingBox infill_contour_bbox = get_extents(infill_contour);
|
||||
infill_contour_bbox.offset(SCALED_EPSILON);
|
||||
|
||||
coord_t perimeter_width = this->perimeter_flow.scaled_width();
|
||||
|
||||
// Get top ExPolygons from current infill contour.
|
||||
Polygons upper_slices_clipped;
|
||||
if (object_config->interface_shells) {
|
||||
auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces);
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, infill_contour_bbox);
|
||||
} else
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, infill_contour_bbox);
|
||||
// Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line.
|
||||
// ORCA: skip if the top surface area is smaller than "min_width_top_surface"
|
||||
const float top_surface_min_width = std::max<float>(float(ext_perimeter_spacing) / 4.f + scaled<float>(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f);
|
||||
|
||||
top_expolygons = diff_ex(infill_contour, upper_slices_clipped);
|
||||
// Get top ExPolygons from the given contour. uncovered reports whether the upper layer leaves any of the
|
||||
// contour uncovered, before bridges and too thin areas are filtered out.
|
||||
auto get_top_expolygons = [&](const ExPolygons &contour, bool &uncovered) {
|
||||
// Contour bounding box.
|
||||
BoundingBox contour_bbox = get_extents(contour);
|
||||
contour_bbox.offset(SCALED_EPSILON);
|
||||
|
||||
Polygons upper_slices_clipped;
|
||||
if (object_config->interface_shells) {
|
||||
auto upper_slicer_same_region = to_expolygons(this->upper_slices_same_region->surfaces);
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(upper_slicer_same_region, contour_bbox);
|
||||
} else
|
||||
upper_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*upper_slices, contour_bbox);
|
||||
|
||||
ExPolygons top = diff_ex(contour, upper_slices_clipped);
|
||||
uncovered = !top.empty();
|
||||
if (top.empty())
|
||||
return top;
|
||||
|
||||
if (!top_expolygons.empty()) {
|
||||
if (lower_slices != nullptr) {
|
||||
const float bridge_offset = float(std::max<coord_t>(ext_perimeter_spacing, perimeter_width));
|
||||
const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, infill_contour_bbox);
|
||||
const ExPolygons current_slices_bridges = offset_ex(diff_ex(top_expolygons, lower_slices_clipped), bridge_offset);
|
||||
const Polygons lower_slices_clipped = ClipperUtils::clip_clipper_polygons_with_subject_bbox(*lower_slices, contour_bbox);
|
||||
const ExPolygons current_slices_bridges = offset_ex(diff_ex(top, lower_slices_clipped), bridge_offset);
|
||||
|
||||
// Remove bridges from top surface polygons.
|
||||
top_expolygons = diff_ex(top_expolygons, current_slices_bridges);
|
||||
top = diff_ex(top, current_slices_bridges);
|
||||
}
|
||||
|
||||
// Filter out areas that are too thin and expand top surface polygons a bit to hide the wall line.
|
||||
// ORCA: skip if the top surface area is smaller than "min_width_top_surface"
|
||||
const float top_surface_min_width = std::max<float>(float(ext_perimeter_spacing) / 4.f + scaled<float>(0.00001), float(scale_(config->min_width_top_surface.get_abs_value(unscale_(perimeter_width)))) / 4.f);
|
||||
// Shrink the polygon to remove the small areas, then expand it back out plus a maragin to hide the wall line a little.
|
||||
// ORCA: Expand the polygon with half the perimeter width in addition to the contracted amount,
|
||||
// not the full perimeter width as PS does, to enable thin lettering to print on the top surface without nozzle collisions
|
||||
// due to thin lines being generated
|
||||
top_expolygons = offset2_ex(top_expolygons, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
|
||||
top = offset2_ex(top, -top_surface_min_width, top_surface_min_width + float(perimeter_width * 0.85));
|
||||
|
||||
// Get final top ExPolygons (bridges were excluded above, so they stay walled).
|
||||
top_expolygons = intersection_ex(top_expolygons, infill_contour);
|
||||
return intersection_ex(top, contour);
|
||||
};
|
||||
|
||||
// Walls with the full count, as generated when the single perimeter feature is disabled. Generated on first use.
|
||||
std::vector<Arachne::VariableWidthLines> full_perimeters;
|
||||
Polygons full_inner_contour;
|
||||
bool full_perimeters_generated = false;
|
||||
auto generate_full_perimeters = [&]() {
|
||||
if (full_perimeters_generated)
|
||||
return;
|
||||
Arachne::WallToolPaths full_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp);
|
||||
full_perimeters = full_tool_paths.getToolPaths();
|
||||
full_inner_contour = full_tool_paths.getInnerContour();
|
||||
full_perimeters_generated = true;
|
||||
};
|
||||
|
||||
// ORCA: the single wall pass allows Arachne 2 beads across a wall, so it fills a wall narrower than 3 outer wall
|
||||
// widths by widening both, where the full pass adds a middle bead. Over the top surface that is the intent;
|
||||
// anywhere else it leaves no room for the inner walls. When the single wall pass's outer walls run away from
|
||||
// the full pass's outside the top surface, take the full pass's outer walls and the area inside them instead.
|
||||
// Walls closer than outer_wall_tolerance count as the same wall: a widened bead's centerline moves by half
|
||||
// the width added, and only beads widened by more than twice the tolerance are looked for.
|
||||
const coord_t outer_wall_tolerance = bead_width_0 / 10;
|
||||
if (widest_bead(perimeters) > bead_width_0 + 2 * outer_wall_tolerance) {
|
||||
// The single wall pass's inner contour where it widens no bead: inside nominal width outer walls.
|
||||
const ExPolygons nominal_infill_contour = offset_ex(last, -float(bead_width_0 + wall_0_inset));
|
||||
bool nominal_uncovered = false;
|
||||
// Grown by an outer wall width to take in the outer walls bordering the top surface.
|
||||
const ExPolygons top_zone = offset_ex(get_top_expolygons(nominal_infill_contour, nominal_uncovered), float(bead_width_0));
|
||||
if (nominal_uncovered) {
|
||||
generate_full_perimeters();
|
||||
if (! full_perimeters.empty() && ! full_perimeters.front().empty() &&
|
||||
length_off_reference(perimeters, full_perimeters.front(), top_zone, outer_wall_tolerance) > double(perimeter_width)) {
|
||||
perimeters = { full_perimeters.front() };
|
||||
infill_contour = diff_ex(nominal_infill_contour, walls_footprint(full_perimeters.front()), ApplySafetyOffset::Yes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool uncovered = false;
|
||||
top_expolygons = get_top_expolygons(infill_contour, uncovered);
|
||||
|
||||
if (uncovered) {
|
||||
// ORCA: onion the real region (inside the outer wall) so the remaining walls follow the actual
|
||||
// geometry, then cut away the parts over the top surface. Re-onioning the non-top complement
|
||||
// instead - the fallback when there is no top fill - walls the top/non-top interface and rings
|
||||
@@ -2612,11 +2716,11 @@ void PerimeterGenerator::process_arachne()
|
||||
perimeters.insert(perimeters.end(), inner_perimeters.begin(), inner_perimeters.end());
|
||||
infill_contour = union_ex(top_expolygons, inner_wall_tool_paths.getInnerContour());
|
||||
} else {
|
||||
// There is no top surface ExPolygon, so we call Arachne again with parameters
|
||||
// like when the single perimeter feature is disabled.
|
||||
Arachne::WallToolPaths no_single_perimeter_tool_paths(last_p, bead_width_0, perimeter_spacing, coord_t(inner_loop_number + 2), wall_0_inset, layer_height, input_params_tmp);
|
||||
perimeters = no_single_perimeter_tool_paths.getToolPaths();
|
||||
infill_contour = union_ex(no_single_perimeter_tool_paths.getInnerContour());
|
||||
// There is no top surface ExPolygon, so use the walls generated like when the single perimeter
|
||||
// feature is disabled.
|
||||
generate_full_perimeters();
|
||||
perimeters = std::move(full_perimeters);
|
||||
infill_contour = union_ex(full_inner_contour);
|
||||
}
|
||||
}
|
||||
//PS
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
@@ -58,6 +59,16 @@ extern wxPopupWindow* wxCurrentPopupWindow;
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
// The Design tab's iso view is the CAD one: a true isometric from the front-right corner, which
|
||||
// shows the navigator's Front, Right and Top faces. Camera's own "iso" looks from the front-left
|
||||
// at 45 degrees and stays Prepare's and the thumbnails'.
|
||||
static void select_iso_view(Camera& camera)
|
||||
{
|
||||
const Vec3d target = camera.get_target();
|
||||
camera.look_at(target + camera.get_distance() * Vec3d(1., -1., 1.).normalized(), target, Vec3d::UnitZ());
|
||||
camera.auto_type(Camera::EType::Perspective);
|
||||
}
|
||||
|
||||
DesignCanvas::DesignCanvas(wxWindow* parent)
|
||||
: wxPanel()
|
||||
{
|
||||
@@ -91,7 +102,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 +201,13 @@ 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 — turned to
|
||||
// the iso view Home returns to, keeping its target and zoom.
|
||||
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());
|
||||
select_iso_view(m_parked_camera);
|
||||
|
||||
// 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 —
|
||||
@@ -345,7 +361,7 @@ void DesignCanvas::repaint_now()
|
||||
m_canvas_widget->Update(); // service the pending paint immediately (a modal popup owns the loop)
|
||||
}
|
||||
|
||||
void DesignCanvas::reload(bool keep_view)
|
||||
void DesignCanvas::reload()
|
||||
{
|
||||
m_canvas->reset_volumes();
|
||||
|
||||
@@ -378,21 +394,19 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
if (!keep_view) {
|
||||
if (m_first_frame && !m_model.objects.empty()) {
|
||||
m_canvas->select_view("iso");
|
||||
m_canvas->zoom_to_volumes();
|
||||
m_first_frame = false;
|
||||
}
|
||||
}
|
||||
|
||||
// The camera stays where the user left it, even for the first body: Home and a double-click
|
||||
// fit on demand.
|
||||
m_canvas->set_as_dirty();
|
||||
if (m_canvas_widget)
|
||||
m_canvas_widget->Refresh();
|
||||
@@ -406,18 +420,21 @@ 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);
|
||||
reload();
|
||||
}
|
||||
|
||||
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);
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,14 +448,14 @@ void DesignCanvas::set_preview_mesh(const TriangleMesh& mesh)
|
||||
obj->add_volume(mesh);
|
||||
obj->add_instance();
|
||||
|
||||
reload(true);
|
||||
reload();
|
||||
}
|
||||
|
||||
void DesignCanvas::clear_preview()
|
||||
{
|
||||
if (m_model.objects.size() > 1) {
|
||||
m_model.delete_object((size_t)1);
|
||||
reload(true);
|
||||
reload();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,10 +469,23 @@ void DesignCanvas::fit_view()
|
||||
}
|
||||
}
|
||||
|
||||
bool DesignCanvas::zoom_to_box(BoundingBoxf3 box)
|
||||
{
|
||||
if (m_canvas == nullptr || !box.defined)
|
||||
return false;
|
||||
DesignSketchTool::pad_box(box);
|
||||
m_canvas->zoom_to_box(box);
|
||||
request_repaint();
|
||||
return true;
|
||||
}
|
||||
|
||||
void DesignCanvas::set_view(const std::string& view_name)
|
||||
{
|
||||
if (m_canvas) {
|
||||
m_canvas->select_view(view_name);
|
||||
if (view_name == "iso")
|
||||
select_iso_view(wxGetApp().plater()->get_camera()); // the Design camera while the tab is shown
|
||||
else
|
||||
m_canvas->select_view(view_name);
|
||||
m_canvas->zoom_to_volumes();
|
||||
m_canvas->set_as_dirty();
|
||||
if (m_canvas_widget)
|
||||
@@ -470,13 +500,6 @@ void DesignCanvas::begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode
|
||||
if (m_canvas_widget) m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void DesignCanvas::set_sketch_plane(const SketchPlane& plane)
|
||||
{
|
||||
m_sketch_tool.set_plane(plane); // keeps the 2D entities; only the carrier plane changes
|
||||
if (m_canvas) m_canvas->set_as_dirty();
|
||||
if (m_canvas_widget) m_canvas_widget->Refresh();
|
||||
}
|
||||
|
||||
void DesignCanvas::edit_sketch(const std::vector<SketchEntity>& entities,
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane)
|
||||
@@ -550,6 +573,7 @@ void DesignCanvas::refresh_bed()
|
||||
double printable_height = 100.0;
|
||||
const auto* ph_opt = config->opt<ConfigOptionFloat>("printable_height");
|
||||
if (ph_opt) printable_height = ph_opt->value;
|
||||
// No position: the Design bed stays at the printer bed's home, whichever plate is current.
|
||||
m_bed.set_shape(bed_shape_opt->values, printable_height, {}, {}, "", false); // mainline added extruder_areas/heights params
|
||||
}
|
||||
|
||||
@@ -731,8 +755,6 @@ void DesignCanvas::clear_move_gizmo()
|
||||
request_repaint();
|
||||
}
|
||||
|
||||
bool DesignCanvas::moving_body() const { return m_sketch_tool.moving_body(); }
|
||||
|
||||
void DesignCanvas::set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb)
|
||||
{
|
||||
m_sketch_tool.on_body_move_changed = std::move(cb);
|
||||
@@ -1025,6 +1047,11 @@ void DesignCanvas::set_on_datum_base_picked(std::function<void(int)> cb)
|
||||
m_sketch_tool.on_datum_base_picked = std::move(cb);
|
||||
}
|
||||
|
||||
void DesignCanvas::set_selected_base(std::function<int()> cb)
|
||||
{
|
||||
m_sketch_tool.selected_base = std::move(cb);
|
||||
}
|
||||
|
||||
void DesignCanvas::set_on_sketch_exit(std::function<void()> cb)
|
||||
{
|
||||
m_sketch_tool.on_exit = std::move(cb);
|
||||
@@ -1047,15 +1074,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 +1096,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 +1198,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 +1232,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)
|
||||
@@ -1241,7 +1272,7 @@ void DesignCanvas::sync_selected_faces()
|
||||
if (m_body_meshes == nullptr || m_model.objects.empty())
|
||||
return;
|
||||
rebuild_bodies();
|
||||
reload(true);
|
||||
reload();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1293,7 +1324,7 @@ void DesignCanvas::set_operand_bodies(int target_body, int tool_body)
|
||||
if (m_hl_body_target == target_body && m_hl_body_tool == tool_body) return;
|
||||
m_hl_body_target = target_body;
|
||||
m_hl_body_tool = tool_body;
|
||||
reload(true); // recolours the body volumes
|
||||
reload(); // recolours the body volumes
|
||||
}
|
||||
|
||||
void DesignCanvas::set_highlight_sketches(std::vector<std::pair<int, ColorRGBA>> hl)
|
||||
@@ -1306,7 +1337,7 @@ void DesignCanvas::set_body_translucent(bool on)
|
||||
{
|
||||
if (m_body_translucent == on) return;
|
||||
m_body_translucent = on;
|
||||
reload(true); // re-applies object-0 alpha so the solid fades for the fillet preview
|
||||
reload(); // re-applies object-0 alpha so the solid fades for the fillet preview
|
||||
}
|
||||
|
||||
void DesignCanvas::set_xray_focus(int body)
|
||||
@@ -1314,7 +1345,7 @@ void DesignCanvas::set_xray_focus(int body)
|
||||
if (m_xray_focus == body) return;
|
||||
m_xray_focus = body;
|
||||
m_sketch_tool.set_pick_only_body(body);
|
||||
reload(true); // re-applies per-body alpha so the non-focused bodies fade
|
||||
reload(); // re-applies per-body alpha so the non-focused bodies fade
|
||||
}
|
||||
|
||||
void DesignCanvas::set_body_hidden(bool on)
|
||||
@@ -1322,7 +1353,7 @@ void DesignCanvas::set_body_hidden(bool on)
|
||||
if (m_body_hidden == on) return;
|
||||
m_body_hidden = on;
|
||||
m_sketch_tool.set_body_edges_hidden(on);
|
||||
reload(true); // hides/show base bodies + flips the ghost opaque/faint for preview-only mode
|
||||
reload(); // hides/show base bodies + flips the ghost opaque/faint for preview-only mode
|
||||
}
|
||||
|
||||
bool DesignCanvas::delete_selected_sketch_entities()
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <vector>
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/BuildVolume.hpp"
|
||||
#include "libslic3r/Color.hpp"
|
||||
#include <utility>
|
||||
#include <wx/colour.h>
|
||||
@@ -48,16 +50,26 @@ 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();
|
||||
|
||||
void fit_view();
|
||||
void set_view(const std::string& view_name);
|
||||
// Frame `box` along the current view direction, as the canvas's Fit button frames a selection.
|
||||
// False, the camera left alone, when there is nothing to frame: an undefined box.
|
||||
bool zoom_to_box(BoundingBoxf3 box);
|
||||
// Boxes to hand zoom_to_box: the selection and body faces (see DesignSketchTool).
|
||||
BoundingBoxf3 selection_box() const { return m_sketch_tool.selection_box(); }
|
||||
BoundingBoxf3 faces_box(std::vector<std::pair<int, int>> faces) const
|
||||
{
|
||||
return m_sketch_tool.faces_box(std::move(faces));
|
||||
}
|
||||
|
||||
void begin_sketch(const SketchPlane& plane, DesignSketchTool::Mode mode);
|
||||
// Re-open a committed entity sketch for full in-canvas editing (load geometry +
|
||||
@@ -66,7 +78,6 @@ public:
|
||||
const std::vector<SketchEntityConstraintDef>& constraints,
|
||||
const SketchPlane& plane);
|
||||
void set_sketch_tool(DesignSketchTool::Mode mode);
|
||||
void set_sketch_plane(const SketchPlane& plane); // re-plane the live sketch when a reference plane is clicked in 3D
|
||||
void set_sketch_construction(bool c);
|
||||
// Flip the sketch selection between construction and real geometry; returns the
|
||||
// number of entities changed (0 = nothing selected, caller falls back to the mode).
|
||||
@@ -83,6 +94,8 @@ public:
|
||||
void finish_sketch();
|
||||
bool is_sketching() const;
|
||||
void refresh_bed(); // re-sync the bed to the current printer (call on tab activation)
|
||||
// Centre of the Design bed: the printer bed at its home position, whichever plate is current.
|
||||
Vec2d bed_center() const { return m_bed.build_volume().bed_center(); }
|
||||
// The Camera is Plater-owned and shared with Prepare/Preview/Assemble; GLCanvas3D has no
|
||||
// per-canvas camera, so every orbit here would otherwise overwrite what the editor tabs
|
||||
// show. Exactly one of the two views is live at a time, so entering and leaving are the
|
||||
@@ -157,7 +170,6 @@ public:
|
||||
void begin_move_body(int body, const Vec3d& pivot, const Transform3d& base_xform,
|
||||
double body_radius);
|
||||
void clear_move_gizmo();
|
||||
bool moving_body() const;
|
||||
void set_on_body_move_changed(std::function<void(int, const Transform3d&)> cb);
|
||||
// Visual Fillet/Chamfer radius gizmo: when a solid edge is picked, anchor a radius arrow on
|
||||
// it; drag/edit fire the radius callback. Returns false if no edge is currently picked.
|
||||
@@ -234,6 +246,7 @@ public:
|
||||
std::vector<std::string> labels = {}); // clickable labelled reference planes
|
||||
void clear_base_pick();
|
||||
void set_on_datum_base_picked(std::function<void(int)> cb);
|
||||
void set_selected_base(std::function<int()> cb); // the reference plane drawn selected, or -1
|
||||
void set_on_sketch_exit(std::function<void()> cb); // Esc -> exit the tool
|
||||
void set_on_sketch_exit_refused(std::function<void()> cb); // Esc declined: sketch has work
|
||||
void set_on_sketch_notice(std::function<void(const std::string&, bool)> cb); // tool refusals/side effects
|
||||
@@ -380,7 +393,7 @@ public:
|
||||
void repaint_now();
|
||||
|
||||
private:
|
||||
void reload(bool keep_view);
|
||||
void reload();
|
||||
void swap_camera(); // enter_viewport / leave_viewport, in the one direction they share
|
||||
|
||||
// Selected faces are filled by the canvas: each body's selected faces become a volume of their
|
||||
@@ -415,7 +428,6 @@ private:
|
||||
Camera m_parked_camera;
|
||||
bool m_camera_swapped{false}; // guards a leave without an enter, and the reverse
|
||||
Model m_model;
|
||||
bool m_first_frame{true};
|
||||
int m_hl_body_target{-1};
|
||||
int m_hl_body_tool{-1};
|
||||
bool m_body_translucent{false};// fillet/chamfer preview → render the body see-through
|
||||
|
||||
@@ -102,7 +102,7 @@ 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")},
|
||||
{"sketch", L_CONTEXT("Sketch", "Design"), 0, "Shift+S", "key:S+S", L("Select a flat face or a reference plane to sketch on"), 0x00000403u, 0, 0, false, false, nullptr, "design_sketch", L("Sketch on the selected flat face or plane, or click one next")},
|
||||
{"extrude", L_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")},
|
||||
@@ -145,6 +145,7 @@ static const OfferVerb kOfferVerbs[] = {
|
||||
{"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")},
|
||||
{"zoom_to", L_CONTEXT("Zoom to selection", "Design"), 8, nullptr, "btn:zoom_to", nullptr, 0x000041feu, 0, 0, false, false, nullptr, "design_zoom", L("Frame the selection in the view, keeping the view direction")},
|
||||
{"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")},
|
||||
@@ -204,7 +205,7 @@ static const OfferVerb kOfferVerbs[] = {
|
||||
{"sk_radius", L_CONTEXT("Radius / diameter…", "Design"), 7, "V", "key:V", nullptr, 0x00020000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the radius of this arc, or the diameter of this circle")},
|
||||
{"sk_angdist", L_CONTEXT("Angle / distance…", "Design"), 7, "V", "key:V", nullptr, 0x00080000u, 0, 0, false, true, nullptr, "design_dimension", L("Type the angle between two lines, or the distance between the two picks")},
|
||||
};
|
||||
static const int kOfferVerbCount = 92;
|
||||
static const int kOfferVerbCount = 93;
|
||||
|
||||
}} // namespace Slic3r::GUI
|
||||
|
||||
|
||||
@@ -383,7 +383,16 @@ private:
|
||||
void update_rib_gizmo(); // in-plane slab footprint + thickness handles (Rib card)
|
||||
void refresh_datum_planes(); // push resolved datum frames + per-plane u/v extents to viewport
|
||||
void refresh_mate_connectors(); // push connector frames so verse + polarity are visible
|
||||
void update_reference_planes(); // persistent XY/XZ/YZ reference planes (fallback when no object)
|
||||
void update_reference_planes(); // the XY/XZ/YZ planes + axes: Origin row, plane choice, Plane tool
|
||||
void toggle_origin(); // the Origin row's eye (and Ctrl+Shift+O): keep the reference planes up
|
||||
void toggle_bed(); // the Bed row's eye (and Ctrl+Shift+B): draw the printer bed, or not
|
||||
void refresh_pinned(); // rebuild the Origin and Bed rows (their eyes and label colours)
|
||||
// Sketch: open a session on the picked face or plane, or with none picked, put the planes up
|
||||
// and wait for one (m_choosing_sketch_plane).
|
||||
void start_sketch();
|
||||
void start_sketch_on_target(bool offer_tools); // a session on sketch_plane_from_selection()
|
||||
void end_sketch_plane_choice(); // leave the plane choice without a sketch; no-op outside it
|
||||
bool sketch_plane_pick_live() const; // may a pick made now still open the sketch?
|
||||
|
||||
CadDocument m_doc;
|
||||
|
||||
@@ -413,7 +422,6 @@ private:
|
||||
ScalableButton* m_commit_btn{nullptr}; // main face: runs the current commit mode
|
||||
DropDown* m_commit_drop{nullptr}; // commit-mode choices, owned via m_flyout_keepalive
|
||||
wxSizer* m_tb_doc{nullptr}; // toolbar document/view actions (new, commit, export, section, place)
|
||||
CheckBox* m_show_bed{nullptr}; // view option: draw the printer bed + plate grid, or not
|
||||
wxSizer* m_box_move{nullptr}; // Move/Rotate numeric options (distance, axis, angle)
|
||||
wxSizer* m_box_sketch{nullptr};
|
||||
wxSizer* m_box_extrude{nullptr};
|
||||
@@ -571,8 +579,20 @@ private:
|
||||
// deliberately no dropdown for it. e1p.
|
||||
int m_ref_plane{0};
|
||||
// m_ref_plane is always a VALID plane, so it cannot itself distinguish "the user chose XY"
|
||||
// from "nobody has chosen anything yet". This does.
|
||||
// from "nobody has chosen anything yet". This does. A selection like a picked face: the sketch
|
||||
// opened on it uses it up, and Esc or a click on nothing lets go of it.
|
||||
bool m_plane_picked{false};
|
||||
// Sketch was pressed with nothing to sketch on: the reference planes are up and the next
|
||||
// reference plane or flat face clicked opens the sketch on it. Still Feature mode — sketch
|
||||
// mode is entered only with the session (start_sketch_on_target).
|
||||
bool m_choosing_sketch_plane{false};
|
||||
// The Feature tree's Origin row: keeps the reference planes and their axes up
|
||||
// (update_reference_planes). A view preference (AppConfig "design_show_origin"), not part of
|
||||
// the recipe.
|
||||
bool m_show_origin{false};
|
||||
// The Feature tree's Bed row: draws the printer bed and its plate grid. A view preference
|
||||
// (AppConfig "design_show_bed"), not part of the recipe.
|
||||
bool m_show_bed{true};
|
||||
ComboBox* m_shape{nullptr};
|
||||
ComboBox* m_mode{nullptr};
|
||||
wxSpinCtrlDouble* m_width{nullptr};
|
||||
@@ -913,12 +933,16 @@ private:
|
||||
std::function<void(double)> m_value_cont; // deferred apply, run on Confirm
|
||||
std::function<void()> m_value_cancel; // optional action when the card is cancelled
|
||||
|
||||
// Feature tree: one row per feature, in feature order, with a per-type icon and the row's
|
||||
// own Edit / Show-hide / Delete icons. Callers use row indices via
|
||||
// tree_selection()/set_tree_selection(); refresh_tree() rebuilds the rows.
|
||||
// Feature tree: the Origin and Bed rows, a fixed block of view switches that never scrolls
|
||||
// and selects nothing, then, in a frame of its own below them, one row per feature, in feature
|
||||
// order, with a per-type icon and the row's own Zoom to selection / Edit / Show-hide / Delete
|
||||
// icons. Callers use row indices via tree_selection()/set_tree_selection(); refresh_tree()
|
||||
// rebuilds the rows.
|
||||
DesignRowList* m_pinned{nullptr};
|
||||
DesignRowList* m_tree{nullptr};
|
||||
// The faces the selected feature row made (CadDocument::faces_made_by), drawn as selected.
|
||||
// Finding them replays the history, so they are kept per row and topology generation.
|
||||
// The faces the selected feature row made (CadDocument::faces_made_by), drawn as selected, and
|
||||
// what Zoom to selection frames for a row. Finding them replays the history, so they are kept
|
||||
// per row and topology generation (feature_faces).
|
||||
// request_feature_highlight() refreshes them after the current event; every change of row,
|
||||
// card or topology calls it.
|
||||
int m_hl_feature{-1}; // the row m_hl_faces were found for...
|
||||
@@ -927,7 +951,10 @@ private:
|
||||
bool m_hl_pending{false};
|
||||
void request_feature_highlight();
|
||||
void update_feature_highlight();
|
||||
// The faces feature `f` made, found again when the cached ones are another row's or topology's.
|
||||
const std::vector<std::pair<int, int>>& feature_faces(int f);
|
||||
bool deselect_rows(); // Esc / a click on nothing: drop the tree and Bodies rows
|
||||
bool drop_plane_pick(); // ...and a picked reference plane; true if one was picked
|
||||
// Bodies list under the feature tree: one row per body (parallel to m_doc.bodies). Selecting
|
||||
// one highlights that body and makes it the target for the next op.
|
||||
DesignRowList* m_parts{nullptr};
|
||||
@@ -964,8 +991,17 @@ private:
|
||||
void rebuild_disp_meshes(); // recompute m_disp_* from m_doc + m_body_xform
|
||||
void feed_bodies(); // push m_disp_* + visibility/xform to the viewport
|
||||
void on_move_body(); // start the move gizmo on the selected body
|
||||
bool body_move_pending() const { return m_move_body >= 0; } // the Move button's session is open
|
||||
void end_body_move(bool keep); // leave it: keep the dragged pose, or put the body back
|
||||
void arm_transform_gizmo(); // arm the move gizmo on the Transform card's body (add mode only)
|
||||
void on_set_body_color(); // Color tool: pick a per-body display colour override
|
||||
// Zoom to selection: frame one thing along the current view, as the canvas's Fit button frames
|
||||
// a selection. A body is framed whole, hidden or not; a feature by its sketch or the faces it
|
||||
// made; anything else the offer is opened on, by what the Fit button frames for it.
|
||||
void zoom_to_body(int b);
|
||||
void zoom_to_feature(int f);
|
||||
void zoom_to_selection();
|
||||
bool can_zoom_to_feature(int f) const; // a sketch, or a shown feature that makes faces
|
||||
void on_boolean_tool(); // Boolean (combine bodies): needs two solids, then opens the tool
|
||||
int tree_selection() const; // selected feature row, or wxNOT_FOUND
|
||||
int tree_body_selection() const; // selected Parts-list body index, or -1
|
||||
|
||||
@@ -29,11 +29,19 @@ static constexpr int kCellDip = 20; // square action cell, hover chip included
|
||||
static constexpr int kPadDip = 4; // row edges, and the gap before the action cells
|
||||
static constexpr int kGapDip = 6; // type icon to label
|
||||
|
||||
DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
|
||||
// wxVListBox defaults to wxBORDER_THEME; the sidebar's lists take a simple frame.
|
||||
: wxVListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBORDER_SIMPLE)
|
||||
DesignRowList::DesignRowList(wxWindow* parent, int max_visible, bool selectable, long style)
|
||||
// wxVListBox defaults to wxBORDER_THEME; `style` defaults to the simple frame the sidebar's
|
||||
// lists take, and a list meant to sit unframed on its card passes wxBORDER_NONE.
|
||||
: wxVListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, style)
|
||||
, m_max_visible(std::max(max_visible, 1))
|
||||
, m_selectable(selectable)
|
||||
{
|
||||
// A non-selectable list takes no focus: from the mouse because it consumes the presses that
|
||||
// would focus it, below, and from the keyboard through AcceptsFocus(). wxGTK reads that once,
|
||||
// while the base class is being built, before the override exists, so it is told here.
|
||||
if (!m_selectable)
|
||||
SetCanFocus(false);
|
||||
|
||||
Bind(wxEVT_LISTBOX, [this](wxCommandEvent&) { if (on_select) on_select(); });
|
||||
Bind(wxEVT_LISTBOX_DCLICK, [this](wxCommandEvent&) { if (on_activate) on_activate(); });
|
||||
|
||||
@@ -46,18 +54,20 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
|
||||
m_pressed = Hit{};
|
||||
e.Skip();
|
||||
});
|
||||
// A press on an action cell is remembered and skipped, so the list still selects the row.
|
||||
// A press on an action cell is remembered and skipped, so the list still selects the row. A
|
||||
// non-selectable list keeps every press from wxVListBox, which would select the row and take
|
||||
// the focus.
|
||||
Bind(wxEVT_LEFT_DOWN, [this](wxMouseEvent& e) {
|
||||
const Hit h = hit_test(e.GetPosition());
|
||||
m_pressed = h.cell >= 0 ? h : Hit{};
|
||||
e.Skip();
|
||||
if (m_selectable) e.Skip();
|
||||
});
|
||||
// The second press of a double-click on a cell is a press too, and is not skipped: the list
|
||||
// would otherwise turn it into a row double-click, so a quick double toggle of the eye would
|
||||
// also open the feature for editing.
|
||||
Bind(wxEVT_LEFT_DCLICK, [this](wxMouseEvent& e) {
|
||||
const Hit h = hit_test(e.GetPosition());
|
||||
if (h.cell < 0) { m_pressed = Hit{}; e.Skip(); return; }
|
||||
if (h.cell < 0) { m_pressed = Hit{}; if (m_selectable) e.Skip(); return; }
|
||||
m_pressed = h;
|
||||
});
|
||||
Bind(wxEVT_LEFT_UP, [this](wxMouseEvent& e) {
|
||||
@@ -69,9 +79,9 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
|
||||
const int id = m_rows[pressed.row].actions[pressed.cell].id;
|
||||
// After the click has finished dispatching: the action may rebuild these rows. The row
|
||||
// must still exist and still be the selected one, or the click is dropped rather than
|
||||
// applied to whatever row took its place.
|
||||
// applied to whatever row took its place. A non-selectable list has no selected row.
|
||||
CallAfter([this, row = pressed.row, id] {
|
||||
if (row < int(GetItemCount()) && row == GetSelection() && on_action)
|
||||
if (row < int(GetItemCount()) && (!m_selectable || row == GetSelection()) && on_action)
|
||||
on_action(row, id);
|
||||
});
|
||||
});
|
||||
@@ -79,7 +89,7 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
|
||||
// press suppresses the wxEVT_CONTEXT_MENU that follows it.
|
||||
Bind(wxEVT_RIGHT_DOWN, [this](wxMouseEvent& e) {
|
||||
const Hit h = hit_test(e.GetPosition());
|
||||
if (h.row != wxNOT_FOUND) select(h.row);
|
||||
if (m_selectable && h.row != wxNOT_FOUND) select(h.row);
|
||||
e.Skip();
|
||||
});
|
||||
Bind(wxEVT_CONTEXT_MENU, [this](wxContextMenuEvent& e) {
|
||||
@@ -95,7 +105,7 @@ DesignRowList::DesignRowList(wxWindow* parent, int max_visible)
|
||||
return;
|
||||
// On MSW the menu comes with the button's release, and the pointer may have moved to
|
||||
// another row since the press selected one: the menu is for the row it opens over.
|
||||
select(row);
|
||||
if (m_selectable) select(row);
|
||||
if (on_menu) on_menu(row, screen);
|
||||
});
|
||||
// wxVListBox takes every wheel event, even with nothing to scroll, so a short list would stop
|
||||
@@ -254,7 +264,7 @@ void DesignRowList::OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) con
|
||||
wxColour bg;
|
||||
if (IsSelected(n))
|
||||
bg = StateColor::darkModeColorFor(wxColour("#BFE1DE"));
|
||||
else if (int(n) == m_hover.row)
|
||||
else if (m_selectable && int(n) == m_hover.row) // a non-selectable row lights only its icons
|
||||
bg = StateColor::darkModeColorFor(wxColour("#E5F0EE"));
|
||||
if (!bg.IsOk())
|
||||
return; // the list already cleared to its background colour
|
||||
@@ -314,7 +324,7 @@ DesignRowList::Hit DesignRowList::hit_test(const wxPoint& pt) const
|
||||
const wxRect rect = GetItemRect(h.row);
|
||||
const auto& acts = m_rows[h.row].actions;
|
||||
for (size_t i = 0; i < acts.size(); ++i)
|
||||
if (cell_rect(rect, acts.size(), i).Contains(pt)) {
|
||||
if (!acts[i].icon.empty() && cell_rect(rect, acts.size(), i).Contains(pt)) {
|
||||
h.cell = int(i);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,16 @@ namespace Slic3r { namespace GUI {
|
||||
// Selection follows wxTreeCtrl's contract, which DesignPanel's mutual exclusion between the two
|
||||
// lists relies on: on_select runs on every change, whether the user or select() made it, and
|
||||
// selecting the row that is already selected changes nothing and notifies nobody.
|
||||
//
|
||||
// A list built non-selectable is a row of switches, not of objects: a click never selects a row,
|
||||
// the pointer highlights only the action icon under it, and the list never takes the focus. Its
|
||||
// action icons and its context menu are the only things that answer.
|
||||
class DesignRowList : public wxVListBox
|
||||
{
|
||||
public:
|
||||
struct Action {
|
||||
int id; // the owner's code for it, handed back to on_action
|
||||
std::string icon; // icon name, e.g. "design_eye"
|
||||
std::string icon; // icon name, e.g. "design_eye"; empty for a blank cell that only holds a column
|
||||
wxString tip;
|
||||
};
|
||||
struct Row {
|
||||
@@ -50,7 +54,8 @@ public:
|
||||
static constexpr const char* hover_chip = "#D4D4D4";
|
||||
|
||||
// The list is as tall as its rows, at least one and at most `max_visible`; past that it scrolls.
|
||||
DesignRowList(wxWindow* parent, int max_visible);
|
||||
// `style` is its frame: wxBORDER_NONE for a list that sits unframed on its card.
|
||||
DesignRowList(wxWindow* parent, int max_visible, bool selectable = true, long style = wxBORDER_SIMPLE);
|
||||
|
||||
// Replace every row. Clears the selection without notifying and cancels a rename in progress;
|
||||
// the owner re-selects the row it keeps, which notifies.
|
||||
@@ -67,15 +72,20 @@ public:
|
||||
|
||||
std::function<void()> on_select; // the selection changed
|
||||
std::function<void()> on_activate; // the selected row was double-clicked
|
||||
// A row's action icon was clicked. Runs after the click has finished dispatching, and only
|
||||
// while that row is still the selected one; the click itself selected it.
|
||||
// A row's action icon was clicked. Runs after the click has finished dispatching, while the
|
||||
// row still exists and — in a selectable list, where the click itself selected it — is still
|
||||
// the selected one.
|
||||
std::function<void(int row, int id)> on_action;
|
||||
// Context menu on a row, at a screen position; the row under the pointer is selected first.
|
||||
// Context menu on a row, at a screen position; the row under the pointer is selected first,
|
||||
// unless the list is non-selectable. Pop the menu from inside the call: wxGTK sends it on the
|
||||
// right press, and a menu deferred past that event closes on the button's release.
|
||||
std::function<void(int row, const wxPoint& screen)> on_menu;
|
||||
// The editor committed `name` for `row`, trimmed and never empty (an empty commit cancels).
|
||||
// Runs after the editor's own events have finished, so the owner may rebuild the rows here.
|
||||
std::function<void(int row, const wxString& name)> on_rename;
|
||||
|
||||
bool AcceptsFocus() const override { return m_selectable && wxVListBox::AcceptsFocus(); }
|
||||
|
||||
protected:
|
||||
void OnDrawItem(wxDC& dc, const wxRect& rect, size_t n) const override;
|
||||
void OnDrawBackground(wxDC& dc, const wxRect& rect, size_t n) const override;
|
||||
@@ -103,6 +113,7 @@ private:
|
||||
std::vector<Row> m_rows;
|
||||
std::map<std::string, ScalableBitmap> m_icons;
|
||||
int m_max_visible;
|
||||
bool m_selectable;
|
||||
int m_row_h{0}; // every row's height, from measure_row()
|
||||
Hit m_hover;
|
||||
Hit m_pressed; // action cell under the last left press
|
||||
|
||||
@@ -2,26 +2,32 @@
|
||||
#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 <array>
|
||||
#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 +56,36 @@ 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].
|
||||
struct PlanePiece
|
||||
{
|
||||
int plane;
|
||||
std::vector<Vec3d> corners;
|
||||
};
|
||||
// The squares of half-extent `half` centred on `planes`' origins, 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);
|
||||
|
||||
// The square a reference plane is drawn as, given as the frame at its centre, half-extent `half`.
|
||||
// The base planes XY, XZ and YZ (`base` 0, 1, 2) sit in the octant (+X, -Y, +Z), the one all three
|
||||
// names face, reference_square_gap(half) clear of the two axes bounding each, so the three never cross
|
||||
// and the axes run between them. Any other base (a datum) stays centred on its own origin. The frame is the same
|
||||
// plane moved within itself, so planes_back_to_front and pick_reference_square take it unchanged.
|
||||
SketchPlane reference_square(const SketchPlane& plane, int base, double half);
|
||||
inline double reference_square_gap(double half) { return 0.25 * half; }
|
||||
// The corners of the box around a base square's label, in the square: inset from its (+x, +y) corner and
|
||||
// written along the square's x axis with its y axis up, so it turns with the plane. The box the hit
|
||||
// test takes for the label: round the strokes render_base_pick draws, from the same layout, with a
|
||||
// margin to aim at.
|
||||
std::array<Vec3d, 4> reference_label_box(const SketchPlane& square, double half, const std::string& text);
|
||||
// The nearest of `squares` (half-extent `half`) the ray from `from` along `dir` crosses, or -1.
|
||||
int pick_reference_square(const std::vector<SketchPlane>& squares, double half, const Vec3d& from, const Vec3d& dir);
|
||||
|
||||
class DesignSketchTool {
|
||||
public:
|
||||
enum class Mode { Select, Dimension, Polyline, Line, CornerRect, CenterRect, ObliqueRect,
|
||||
@@ -137,7 +173,6 @@ public:
|
||||
// Returns false when no session is live, so the caller can fall back to a new feature.
|
||||
bool add_imported_regions(const std::vector<std::vector<std::vector<Vec2d>>>& regions);
|
||||
void set_tool(Mode mode); // switch tool, keep accumulated entities
|
||||
void set_plane(const SketchPlane& plane) { m_plane = plane; } // re-plane a live sketch (a reference plane was clicked mid-session); entities are 2D, re-lifted through the new plane
|
||||
void set_construction(bool c) { m_construction = c; }
|
||||
void set_polygon_sides(int n) { m_polygon_sides = (n < 3 ? 3 : n); }
|
||||
void set_polygon_circumscribed(bool c) { m_polygon_circumscribed = c; }
|
||||
@@ -149,10 +184,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 +242,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 +259,20 @@ 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;
|
||||
// The selection fit_box frames, with no fallback: undefined when nothing is selected.
|
||||
BoundingBoxf3 selection_box() const;
|
||||
// World box of (body, face id) faces on the pick mesh, hidden bodies included.
|
||||
BoundingBoxf3 faces_box(std::vector<std::pair<int, int>> faces) const;
|
||||
// World box of sketch entities drawn on `plane`.
|
||||
static BoundingBoxf3 sketch_box(const std::vector<SketchEntity>& entities, const SketchPlane& plane);
|
||||
// Grow a box the camera is to frame to at least a small extent on every axis: a vertex has no
|
||||
// size, nor has a sketch along its normal.
|
||||
static void pad_box(BoundingBoxf3& box);
|
||||
|
||||
// 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
|
||||
@@ -230,8 +282,10 @@ public:
|
||||
void set_move_gizmo(int body, const Vec3d& pivot, const Transform3d& base_xform,
|
||||
double body_radius = 0.0);
|
||||
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;
|
||||
@@ -316,6 +370,12 @@ public:
|
||||
std::vector<std::string> labels = {});
|
||||
void clear_base_pick();
|
||||
std::function<void(int base)> on_datum_base_picked;
|
||||
// The base drawn as selected, or -1. Asked once a frame rather than set, because what decides it
|
||||
// (the panel's chosen sketch plane, a picked face, the Plane card's base) changes in many places.
|
||||
std::function<int()> selected_base;
|
||||
// The reference planes are drawn this frame, with their own half-axes and origin mark: the canvas
|
||||
// leaves out the bed's axes triad, which would sit on top of them.
|
||||
bool draws_reference_axes() const { return m_dbp_active && !m_active; }
|
||||
|
||||
// Visual Fillet/Chamfer gizmo. The Dressup tool is a DesignPanel docked card, so the sketch
|
||||
// tool is NOT active during it; when a solid EDGE is picked the panel passes the body centroid
|
||||
@@ -971,7 +1031,7 @@ private:
|
||||
bool op_ready() const; // required entities picked -> arrow/ghost live
|
||||
|
||||
// Sample an entity into a 2D polyline for the overlay renderer.
|
||||
std::vector<Vec2d> entity_polyline(const SketchEntity& e, bool& closed) const;
|
||||
static std::vector<Vec2d> entity_polyline(const SketchEntity& e, bool& closed);
|
||||
|
||||
// Closed regions formed by the current (non-construction) entities: each a CCW-
|
||||
// ordered boundary polygon on the plane. A circle is its own region; line/arc
|
||||
@@ -1225,19 +1285,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 +1335,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 +1343,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;
|
||||
@@ -1385,7 +1446,9 @@ private:
|
||||
std::vector<int> m_dbp_base;
|
||||
std::vector<std::string> m_dbp_labels;
|
||||
int m_dbp_hover{-1};
|
||||
double dbp_half_extent() const; // bed-derived: reference planes are larger than the bed
|
||||
double dbp_half_extent() const; // bed-derived square size
|
||||
std::vector<SketchPlane> dbp_squares(double half) const; // reference_square of each entry
|
||||
void render_reference_axes(const Vec3d& origin, double half);
|
||||
void render_base_pick();
|
||||
int hit_test_base_pick(GLCanvas3D& canvas, const wxMouseEvent& evt) const;
|
||||
|
||||
@@ -1403,6 +1466,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 +1474,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);
|
||||
|
||||
@@ -1875,7 +1875,7 @@ BoundingBoxf3 GLCanvas3D::volumes_bounding_box(bool current_plate_only) const
|
||||
bool is_limit = m_canvas_type != ECanvasType::CanvasAssembleView;
|
||||
if (is_limit) {
|
||||
if (current_plate_only) {
|
||||
expand_part_plate_list_box = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_bounding_box();
|
||||
expand_part_plate_list_box = _current_plate_box();
|
||||
} else {
|
||||
auto plate_list_box = wxGetApp().plater()->get_partplate_list().get_bounding_box();
|
||||
auto horizontal_radius = 0.5 * sqrt(std::pow(plate_list_box.min[0] - plate_list_box.max[0], 2) + std::pow(plate_list_box.min[1] - plate_list_box.max[1], 2));
|
||||
@@ -2088,6 +2088,25 @@ float GLCanvas3D::get_collapse_toolbar_height() const
|
||||
return collapse_side() != CollapseSide::None ? collapse_toolbar().get_height() : 0;
|
||||
}
|
||||
|
||||
// The bottom-left corner: the 3D navigator, then the column of round canvas buttons beside it, in
|
||||
// units of the toolbar scale. Shared by _render_3d_navigator, _render_canvas_toolbar and
|
||||
// get_canvas_toolbar_right, so an overlay kept clear of the corner follows its layout.
|
||||
static constexpr float NAVIGATOR_SIZE = 128.f;
|
||||
static constexpr float CANVAS_TOOLBAR_MARGIN = 10.f; // off the canvas edge, when there is no navigator
|
||||
static constexpr float CANVAS_TOOLBAR_PADDING = 2.f;
|
||||
static constexpr float CANVAS_TOOLBAR_BUTTON = 36.f;
|
||||
|
||||
float GLCanvas3D::get_canvas_toolbar_right() const
|
||||
{
|
||||
// The toolbar scale, which on Windows follows the monitor's DPI where the ImGui style does not.
|
||||
float sc = get_scale();
|
||||
#ifdef WIN32
|
||||
sc *= (float) get_dpi_for_window(wxGetApp().GetTopWindow()) / (float) DPI_DEFAULT;
|
||||
#endif // WIN32
|
||||
const float left = wxGetApp().show_3d_navigator() ? NAVIGATOR_SIZE : CANVAS_TOOLBAR_MARGIN;
|
||||
return (left + 2.f * CANVAS_TOOLBAR_PADDING + CANVAS_TOOLBAR_BUTTON) * sc;
|
||||
}
|
||||
|
||||
GLToolbar& GLCanvas3D::collapse_toolbar() const
|
||||
{
|
||||
return m_collapse_toolbar != nullptr ? *m_collapse_toolbar : wxGetApp().plater()->get_collapse_toolbar();
|
||||
@@ -2395,7 +2414,7 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
|
||||
}
|
||||
else if (gizmo_type == GLGizmosManager::BrimEars && !camera.is_looking_downward())
|
||||
show_grid = false;
|
||||
if (m_axes_at_bed_center)
|
||||
if (m_design_canvas)
|
||||
// Design tab: the plate grid is generated from the plate's front-left corner, so it
|
||||
// floats mid-cell under the modeling-origin triad. Suppress it here; a CAD grid centred
|
||||
// on the origin is rendered in its place (see _render_cad_grid).
|
||||
@@ -2406,12 +2425,15 @@ void GLCanvas3D::_render_scene(const Camera& camera, const Size& cnv_size)
|
||||
if (m_canvas_type == ECanvasType::CanvasView3D) {
|
||||
// m_show_bed gates the plate list too: hiding the bed but leaving its grid and outline
|
||||
// floating would read as a rendering fault rather than a deliberate view option.
|
||||
// Design tab: while its reference planes are up they draw their own axes from the modeling
|
||||
// origin, where the bed's triad would otherwise sit on top of them.
|
||||
if (show_bed)
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), m_show_world_axes);
|
||||
_render_bed(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(),
|
||||
m_show_world_axes && !(m_design_sketch_tool != nullptr && m_design_sketch_tool->draws_reference_axes()));
|
||||
m_frame_profiler.mark("bed");
|
||||
if (show_bed) //BBS: add outline logic
|
||||
_render_platelist(camera.get_view_matrix(), camera.get_projection_matrix(), !camera.is_looking_downward(), only_current, only_body, hover_id, true, show_grid);
|
||||
if (m_axes_at_bed_center && show_bed)
|
||||
if (m_design_canvas && show_bed)
|
||||
// Design tab: replace the plate's corner-origin grid with the origin-centred CAD grid.
|
||||
_render_cad_grid(camera.get_view_matrix(), camera.get_projection_matrix());
|
||||
m_frame_profiler.mark("plates");
|
||||
@@ -4311,7 +4333,10 @@ void GLCanvas3D::on_mouse(wxMouseEvent& evt)
|
||||
}
|
||||
if (evt.LeftDown() && m_canvas != nullptr)
|
||||
m_canvas->SetFocus(); // grab keyboard focus so Delete/keys reach this canvas
|
||||
if (m_design_sketch_tool->on_mouse(evt, *this)) {
|
||||
// A Touchpad-style orbit or pan (a plain move with Alt or Shift held) is the camera's,
|
||||
// whatever the tool would make of the move. Only a drag consults the button mappings.
|
||||
const bool camera_move = evt.Moving() && (is_camera_rotate(evt, {}) || is_camera_pan(evt, {}));
|
||||
if (!camera_move && m_design_sketch_tool->on_mouse(evt, *this)) {
|
||||
m_dirty = true;
|
||||
render(); // force an immediate redraw so the sketch overlay updates live
|
||||
return;
|
||||
@@ -6355,7 +6380,7 @@ void GLCanvas3D::_render_3d_navigator()
|
||||
}
|
||||
}
|
||||
|
||||
const float size = 128 * sc;
|
||||
const float size = NAVIGATOR_SIZE * sc;
|
||||
m_canvas_toolbar_pos[0] = size;
|
||||
const auto result = ImGuizmo::ViewManipulate(cameraView, cameraProjection, ImGuizmo::OPERATION::ROTATE, ImGuizmo::MODE::WORLD, nullptr,
|
||||
camDistance, ImVec2(viewManipulateLeft, viewManipulateTop - size), ImVec2(size, size),
|
||||
@@ -7970,9 +7995,16 @@ void GLCanvas3D::_render_fps_overlay(int fps) const
|
||||
const float margin = 10.0f * get_scale();
|
||||
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
|
||||
ImVec2 pos(display_size.x - margin, margin);
|
||||
// Last frame's size; zero until the overlay has been shown once.
|
||||
const ImGuiWindow* self = ImGui::FindWindowByName("###fps_overlay");
|
||||
const float left = pos.x - (self != nullptr ? self->Size.x : 0.0f);
|
||||
// The Preview legend takes the top-right corner.
|
||||
if (const ImGuiWindow* legend = ImGui::FindWindowByName("Legend"); m_canvas_type == ECanvasType::CanvasPreview && legend != nullptr && legend->Active)
|
||||
pos = ImVec2(legend->Pos.x - margin, legend->Pos.y);
|
||||
// The toolbar row can reach the corner on a narrow canvas; stack the overlay below it then.
|
||||
else if (m_main_toolbar.is_enabled() &&
|
||||
get_main_toolbar_offset() + m_main_toolbar.get_width() + m_separator_toolbar.get_width() + m_gizmos.get_scaled_total_width() + m_assemble_view_toolbar.get_width() > left)
|
||||
pos.y = std::max(m_main_toolbar.get_height(), m_gizmos.get_scaled_total_height()) + margin;
|
||||
ImGui::SetNextWindowPos(pos, ImGuiCond_Always, ImVec2(1.0f, 0.0f));
|
||||
ImGui::SetNextWindowBgAlpha(0.35f);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 8.0f * get_scale());
|
||||
@@ -8251,7 +8283,7 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
|
||||
*/
|
||||
//bool show_texture = true;
|
||||
//BBS set axes mode
|
||||
if (m_axes_at_bed_center) {
|
||||
if (m_design_canvas) {
|
||||
// Design tab: triad at the bed centre = modeling origin (set every frame because
|
||||
// set_shape/set_axes_mode otherwise reset it to the bed corner).
|
||||
const Vec2d bc = m_bed.build_volume().bed_center();
|
||||
@@ -8264,7 +8296,30 @@ void GLCanvas3D::_render_bed(const Transform3d& view_matrix, const Transform3d&
|
||||
|
||||
void GLCanvas3D::_render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body, int hover_id, bool render_cali, bool show_grid)
|
||||
{
|
||||
wxGetApp().plater()->get_partplate_list().render(view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled);
|
||||
PartPlateList& plate_list = wxGetApp().plater()->get_partplate_list();
|
||||
// Design tab: its bed stays at the printer bed's home whichever plate is current, so the
|
||||
// current plate's exclude areas are moved from that plate onto it.
|
||||
PartPlate* curr_plate = plate_list.get_curr_plate();
|
||||
const Transform3d plate_view_matrix = m_design_canvas && curr_plate != nullptr ?
|
||||
Transform3d(view_matrix * Geometry::translation_transform(-curr_plate->get_origin())) : view_matrix;
|
||||
plate_list.render(plate_view_matrix, projection_matrix, bottom, only_current, only_body, hover_id, render_cali, show_grid, !m_plate_chrome_enabled);
|
||||
}
|
||||
|
||||
BoundingBoxf3 GLCanvas3D::_current_plate_box() const
|
||||
{
|
||||
// Design tab: its own bed stands in for the current plate (see _render_platelist).
|
||||
const BuildVolume& build_volume = m_bed.build_volume();
|
||||
if (m_design_canvas && build_volume.valid()) {
|
||||
// Flat at z = 0 like the plate's own box. Merged, as PartPlate builds it: the min/max
|
||||
// constructor leaves a flat box undefined.
|
||||
const BoundingBoxf bb = build_volume.bounding_volume2d();
|
||||
BoundingBoxf3 box;
|
||||
box.merge(Vec3d(bb.min.x(), bb.min.y(), 0.));
|
||||
box.merge(Vec3d(bb.max.x(), bb.max.y(), 0.));
|
||||
return box;
|
||||
}
|
||||
PartPlate* curr_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
return curr_plate != nullptr ? curr_plate->get_bounding_box() : BoundingBoxf3();
|
||||
}
|
||||
|
||||
// Design tab: CAD grid on the bed plane, drawn in place of the plate's corner-origin grid.
|
||||
@@ -10038,10 +10093,10 @@ void GLCanvas3D::_render_canvas_toolbar()
|
||||
sc *= (float) dpi / (float) DPI_DEFAULT;
|
||||
#endif // WIN32
|
||||
|
||||
ImVec2 btn_size = ImVec2(36.f, 36.f) * sc;
|
||||
ImVec2 margin = ImVec2(m_canvas_toolbar_pos[0] > 0 ? 0.f : (10.f * sc), 10.f * sc);
|
||||
ImVec2 btn_size = ImVec2(CANVAS_TOOLBAR_BUTTON, CANVAS_TOOLBAR_BUTTON) * sc;
|
||||
ImVec2 margin = ImVec2(m_canvas_toolbar_pos[0] > 0 ? 0.f : (CANVAS_TOOLBAR_MARGIN * sc), CANVAS_TOOLBAR_MARGIN * sc);
|
||||
ImVec2 spacing = ImVec2(6.f, 6.f) * sc;
|
||||
ImVec2 padding = ImVec2(2.f, 2.f) * sc;
|
||||
ImVec2 padding = ImVec2(CANVAS_TOOLBAR_PADDING, CANVAS_TOOLBAR_PADDING) * sc;
|
||||
Vec2i32 pos = {
|
||||
m_canvas_toolbar_pos[0] + margin.x,
|
||||
get_canvas_size().get_height() - margin.y
|
||||
@@ -10099,14 +10154,25 @@ void GLCanvas3D::_render_canvas_toolbar()
|
||||
ImTextureID z_hover_id = m_gizmos.get_icon_texture_id(m_is_dark ? GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_DARK_HOVER : GLGizmosManager::MENU_ICON_NAME::IC_CANVAS_ZOOM_HOVER);
|
||||
|
||||
if (ImGui::ImageButton3(z_normal_id, z_hover_id, btn_size)) {
|
||||
select_view("plate");
|
||||
if (m_selection.is_empty()) {
|
||||
if (m_canvas_type == ECanvasType::CanvasAssembleView)
|
||||
zoom_to_volumes();
|
||||
else
|
||||
zoom_to_bed();
|
||||
} else {
|
||||
zoom_to_selection();
|
||||
#ifdef SLIC3R_CAD
|
||||
// The Design tab selects and sketches outside the canvas's selection and volumes, so
|
||||
// it names what to frame. Framed along the current view, which is often square to a
|
||||
// sketch plane; an empty tab falls through to the bed.
|
||||
const BoundingBoxf3 design_box = m_design_sketch_tool != nullptr ? m_design_sketch_tool->fit_box(m_volumes) : BoundingBoxf3();
|
||||
if (design_box.defined)
|
||||
_zoom_to_box(design_box);
|
||||
else
|
||||
#endif
|
||||
{
|
||||
select_view("plate");
|
||||
if (m_selection.is_empty()) {
|
||||
if (m_canvas_type == ECanvasType::CanvasAssembleView)
|
||||
zoom_to_volumes();
|
||||
else
|
||||
zoom_to_bed();
|
||||
} else {
|
||||
zoom_to_selection();
|
||||
}
|
||||
}
|
||||
} else if (ImGui::IsItemHovered()) {
|
||||
auto tooltip_str_wx = _L("Fit camera to scene or selected object.");
|
||||
@@ -11059,10 +11125,9 @@ std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType n
|
||||
{
|
||||
// Orca: Centralize the pre-existing pivot rules so orbiting and pan fallback cannot
|
||||
// choose different reference depths for the same canvas and active tool.
|
||||
PartPlate* current_plate = wxGetApp().plater()->get_partplate_list().get_curr_plate();
|
||||
const BoundingBoxf3 plate_box = _current_plate_box();
|
||||
if (navigation_type == ECameraNavigationType::Gesture)
|
||||
return current_plate == nullptr ? std::nullopt :
|
||||
std::make_optional(current_plate->get_bounding_box().center());
|
||||
return plate_box.defined ? std::make_optional(plate_box.center()) : std::nullopt;
|
||||
|
||||
const GLGizmosManager::EType gizmo_type = m_gizmos.get_current_type();
|
||||
const bool use_scene_target = m_canvas_type == ECanvasType::CanvasAssembleView ||
|
||||
@@ -11082,15 +11147,15 @@ std::optional<Vec3d> GLCanvas3D::get_camera_orbit_target(ECameraNavigationType n
|
||||
|
||||
Vec3d target = Vec3d::Zero();
|
||||
if (m_canvas_type == ECanvasType::CanvasPreview) {
|
||||
if (current_plate != nullptr)
|
||||
target = current_plate->get_bounding_box().center();
|
||||
if (plate_box.defined)
|
||||
target = plate_box.center();
|
||||
} else if (!m_selection.is_empty()) {
|
||||
target = m_selection.get_bounding_box().center();
|
||||
} else {
|
||||
// Orca: Match regular mouse orbit: objects on the active plate, then the plate itself.
|
||||
BoundingBoxf3 bbox = volumes_bounding_box(true);
|
||||
if (!bbox.defined && current_plate != nullptr)
|
||||
bbox = current_plate->get_bounding_box();
|
||||
if (!bbox.defined)
|
||||
bbox = plate_box;
|
||||
if (bbox.defined)
|
||||
target = bbox.center();
|
||||
}
|
||||
|
||||
@@ -604,9 +604,11 @@ private:
|
||||
GLToolbar* m_collapse_toolbar{nullptr};
|
||||
std::function<CollapseSide()> m_collapse_side;
|
||||
bool m_plate_chrome_enabled{true};
|
||||
// Design tab: render the world-axis triad at the bed centre (= modeling origin) instead of
|
||||
// the bed corner. Default false preserves the main editor's corner triad.
|
||||
bool m_axes_at_bed_center{false};
|
||||
// This canvas is the Design tab's. Its bed stays at the printer bed's home whichever plate is
|
||||
// current, with the world-axis triad and a CAD grid at the bed centre (= modeling origin) in
|
||||
// place of the corner triad and the plate grid, and the plate data it reads (exclude areas,
|
||||
// the current plate's box) moved onto that bed. Default false leaves the editor tabs untouched.
|
||||
bool m_design_canvas{false};
|
||||
// Design tab: draw the printer bed and its plate grid at all. Default true, so the
|
||||
// main editor is untouched; the Design tab lets the user hide it to model without a bed.
|
||||
bool m_show_bed{true};
|
||||
@@ -1025,13 +1027,15 @@ public:
|
||||
// initialized, which loads the toolbar's background.
|
||||
void set_collapse_toolbar(GLToolbar* toolbar, std::function<CollapseSide()> side);
|
||||
void enable_plate_chrome(bool enable);
|
||||
void set_axes_at_bed_center(bool b) { m_axes_at_bed_center = b; }
|
||||
void set_design_canvas(bool b) { m_design_canvas = b; }
|
||||
void set_show_bed(bool b) { m_show_bed = b; }
|
||||
bool get_show_bed() const { return m_show_bed; }
|
||||
void enable_sinking_contours(bool enable) { m_sinking_contours_enabled = enable; }
|
||||
#ifdef SLIC3R_CAD
|
||||
void set_design_sketch_tool(DesignSketchTool* tool) { m_design_sketch_tool = tool; }
|
||||
DesignSketchTool* get_design_sketch_tool() const { return m_design_sketch_tool; }
|
||||
// The Design tab frames what it selects itself (DesignCanvas::zoom_to_box), as the Fit button does.
|
||||
void zoom_to_box(const BoundingBoxf3& box) { _zoom_to_box(box); }
|
||||
#endif
|
||||
void enable_dynamic_background(bool enable) { m_dynamic_background_enabled = enable; }
|
||||
void enable_labels(bool enable) { m_labels.enable(enable); }
|
||||
@@ -1059,6 +1063,9 @@ public:
|
||||
bool is_collapse_toolbar_on_left() const;
|
||||
float get_collapse_toolbar_width() const;
|
||||
float get_collapse_toolbar_height() const;
|
||||
// Right edge, in canvas pixels, of the bottom-left corner the 3D navigator and the round
|
||||
// canvas buttons own. An overlay along the bottom edge starts past it.
|
||||
float get_canvas_toolbar_right() const;
|
||||
|
||||
void update_volumes_colors_by_extruder();
|
||||
|
||||
@@ -1441,9 +1448,11 @@ private:
|
||||
void _render_shadows(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
//BBS: add part plate related logic
|
||||
void _render_platelist(const Transform3d& view_matrix, const Transform3d& projection_matrix, bool bottom, bool only_current, bool only_body = false, int hover_id = -1, bool render_cali = false, bool show_grid = true);
|
||||
// The current plate's box (XY, at z = 0); in the Design tab, its own bed's.
|
||||
BoundingBoxf3 _current_plate_box() const;
|
||||
// Design tab: draw the CAD grid (minor 10 mm + major 50 mm) in place of the plate's
|
||||
// corner-origin grid when the axes sit at the bed centre (modeling origin). Rebuilds its
|
||||
// GLModels lazily, only when the bed shape changed.
|
||||
// corner-origin grid, centred on the modeling origin. Rebuilds its GLModels lazily, only
|
||||
// when the bed shape changed.
|
||||
void _render_cad_grid(const Transform3d& view_matrix, const Transform3d& projection_matrix);
|
||||
//BBS: add outline drawing logic
|
||||
void _render_objects(GLVolumeCollection::ERenderType type, bool with_outline = true);
|
||||
|
||||
@@ -1816,7 +1816,10 @@ void InputIpAddressDialog::set_machine_obj(MachineObject* obj)
|
||||
m_input_printer_name->GetTextCtrl()->SetLabelText(m_obj->get_dev_name());
|
||||
|
||||
std::string img_str = DevPrinterConfigUtil::get_printer_connect_help_img(m_obj->printer_type);
|
||||
auto diagram_bmp = create_scaled_bitmap(img_str + "_en", this, 198);
|
||||
if (img_str.empty()) { img_str = "input_access_code_x1"; }
|
||||
|
||||
std::string language = wxGetApp().app_config->get("language");
|
||||
auto diagram_bmp = create_scaled_bitmap(img_str + (language == "zh_CN" ? "_cn" : "_en"), this, 198);
|
||||
m_img_help->SetBitmap(diagram_bmp);
|
||||
|
||||
|
||||
|
||||
@@ -2966,7 +2966,7 @@ void TabPrint::build()
|
||||
optgroup->append_single_option_line("skin_infill_line_width", "strength_settings_patterns#locked-zag");
|
||||
optgroup->append_single_option_line("skeleton_infill_line_width", "strength_settings_patterns#locked-zag");
|
||||
optgroup->append_single_option_line("symmetric_infill_y_axis", "strength_settings_infill#symmetric-infill-y-axis");
|
||||
optgroup->append_single_option_line("infill_complete_top", "strength_settings_infill#infill-complete-top");
|
||||
optgroup->append_single_option_line("infill_complete_top", "strength_settings_patterns#fill-pattern-tops");
|
||||
optgroup->append_single_option_line("infill_shift_step", "strength_settings_patterns#cross-hatch");
|
||||
optgroup->append_single_option_line("lateral_lattice_angle_1", "strength_settings_patterns#lateral-lattice");
|
||||
optgroup->append_single_option_line("lateral_lattice_angle_2", "strength_settings_patterns#lateral-lattice");
|
||||
|
||||
@@ -456,6 +456,13 @@ wxBitmap create_scaled_bitmap( const std::string& bmp_name_in,
|
||||
const vector<std::string>& array_new_color/* = vector<std::string>*/)//used for semi transparent material)
|
||||
{
|
||||
static Slic3r::GUI::BitmapCache cache;
|
||||
|
||||
// An empty name means the caller's icon lookup failed
|
||||
if (bmp_name_in.empty() || bmp_name_in == ".png") {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": empty bitmap name";
|
||||
return wxNullBitmap;
|
||||
}
|
||||
|
||||
if (bitmap2) {
|
||||
return create_scaled_bitmap2(bmp_name_in, cache, win, px_cnt, grayscale, resize, array_new_color);
|
||||
}
|
||||
|
||||
@@ -19,8 +19,10 @@
|
||||
#include <cstddef>
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/ClipperUtils.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
#include "libslic3r/Polyline.hpp"
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
@@ -275,6 +277,124 @@ TEST_CASE("Only one wall on the first layer needs a bottom shell", "[Perimeters]
|
||||
|
||||
namespace {
|
||||
|
||||
// The last layer of the tab, whose top surface shares an island with the tube walls rising past it.
|
||||
const double tab_top_z = 5.0;
|
||||
|
||||
// With the widths below the tube walls are 1.10mm wide once the precise outer wall offset (0.043mm a side) is
|
||||
// taken off. That is narrower than 3 outer wall spacings (3 x 0.377 = 1.131mm), so an Arachne pass limited to a
|
||||
// single wall fills it by widening its 2 beads, yet wide enough for the full 2 wall pass to add a middle wall
|
||||
// (from 1.062mm).
|
||||
const double narrow_wall = 1.186;
|
||||
|
||||
// A 20x30x10 tube with narrow_wall thick walls, and a 20x8x5 tab against its -Y side.
|
||||
Print &tube_with_tab(Print &print, Model &model, const DynamicPrintConfig &config)
|
||||
{
|
||||
ModelObject *object = model.add_object();
|
||||
object->name = "tube_with_tab.stl";
|
||||
object->add_volume(make_cube(20., 30., 10.), ModelVolumeType::MODEL_PART, false);
|
||||
// Overlaps the tube wall by 0.5mm so the two parts slice as one island.
|
||||
TriangleMesh tab = make_cube(20., 8.5, 5.);
|
||||
tab.translate(0.f, -8.f, 0.f);
|
||||
object->add_volume(std::move(tab), ModelVolumeType::MODEL_PART, false);
|
||||
TriangleMesh bore = make_cube(20. - 2. * narrow_wall, 30. - 2. * narrow_wall, 12.);
|
||||
bore.translate(float(narrow_wall), float(narrow_wall), -1.f);
|
||||
object->add_volume(std::move(bore), ModelVolumeType::NEGATIVE_VOLUME, false);
|
||||
object->add_instance();
|
||||
object->ensure_on_bed();
|
||||
|
||||
print.auto_assign_extruders(object);
|
||||
print.apply(model, config);
|
||||
print.validate();
|
||||
print.set_status_silent();
|
||||
return print;
|
||||
}
|
||||
|
||||
// Every width the narrow_wall arithmetic depends on, so none of them rests on a default.
|
||||
DynamicPrintConfig narrow_wall_config(bool only_one_wall_top, double top_surface_expansion)
|
||||
{
|
||||
DynamicPrintConfig config = base_config("arachne");
|
||||
config.set_deserialize_strict({
|
||||
{ "wall_loops", 2 },
|
||||
{ "nozzle_diameter", "0.4" },
|
||||
{ "line_width", 0.42 },
|
||||
{ "outer_wall_line_width", 0.42 },
|
||||
{ "inner_wall_line_width", 0.45 },
|
||||
{ "min_bead_width", "85%" },
|
||||
{ "precise_outer_wall", true },
|
||||
{ "wall_sequence", "inner wall/outer wall" },
|
||||
{ "only_one_wall_top", only_one_wall_top },
|
||||
{ "top_surface_expansion", top_surface_expansion },
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
// Inner wall length the layer at print_z extrudes within 3mm of its +Y edge: the tube wall facing away from the tab.
|
||||
double far_wall_inner_wall_length(const Print &print, double print_z)
|
||||
{
|
||||
for (const Layer *layer : print.objects().front()->layers()) {
|
||||
if (std::abs(layer->print_z - print_z) > EPSILON)
|
||||
continue;
|
||||
BoundingBox band = get_extents(layer->lslices);
|
||||
band.min.y() = band.max.y() - scaled<coord_t>(3.);
|
||||
|
||||
Polylines inner_walls;
|
||||
auto collect = [&inner_walls](const ExtrusionPaths &paths) {
|
||||
for (const ExtrusionPath &path : paths)
|
||||
if (path.role() == erPerimeter)
|
||||
inner_walls.emplace_back(path.as_polyline());
|
||||
};
|
||||
for (const LayerRegion *region : layer->regions()) {
|
||||
const ExtrusionEntityCollection walls = region->perimeters.flatten();
|
||||
for (const ExtrusionEntity *entity : walls.entities) {
|
||||
if (const auto *loop = dynamic_cast<const ExtrusionLoop*>(entity))
|
||||
collect(loop->paths);
|
||||
else if (const auto *multi_path = dynamic_cast<const ExtrusionMultiPath*>(entity))
|
||||
collect(multi_path->paths);
|
||||
else if (const auto *path = dynamic_cast<const ExtrusionPath*>(entity))
|
||||
collect({ *path });
|
||||
}
|
||||
}
|
||||
return unscaled<double>(total_length(intersection_pl(inner_walls, band.polygon())));
|
||||
}
|
||||
return 0.;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// only_one_wall_top first lays out an island with a single Arachne wall and generates the inner walls inside it.
|
||||
// On a wall narrower than 3 outer wall spacings that single wall pass widens its 2 beads to fill the wall and leaves
|
||||
// no room for the middle wall, which is only intended over the top surface. The tube walls away from the tab are not
|
||||
// under the tab's top surface, so on the tab's last layer they keep the inner wall they get with the option off.
|
||||
TEST_CASE("Only one wall on top surfaces keeps the inner walls of narrow walls away from the top surface", "[Perimeters]")
|
||||
{
|
||||
// 0 re-onions the region beside the top surface, 2 clips the inner walls over it.
|
||||
const double top_surface_expansion = GENERATE(0.0, 2.0);
|
||||
CAPTURE(top_surface_expansion);
|
||||
|
||||
struct TabTopLayer {
|
||||
double perimeters;
|
||||
double far_wall_inner_walls;
|
||||
};
|
||||
auto tab_top_layer_for = [top_surface_expansion](bool only_one_wall_top) {
|
||||
Print print;
|
||||
Model model;
|
||||
tube_with_tab(print, model, narrow_wall_config(only_one_wall_top, top_surface_expansion));
|
||||
print.process();
|
||||
REQUIRE_FALSE(print.objects().empty());
|
||||
return TabTopLayer{ perimeter_length_at(print, tab_top_z), far_wall_inner_wall_length(print, tab_top_z) };
|
||||
};
|
||||
|
||||
const TabTopLayer plain = tab_top_layer_for(false);
|
||||
const TabTopLayer one_wall = tab_top_layer_for(true);
|
||||
|
||||
// The option acts on this layer: the inner walls under the tab's top surface are gone.
|
||||
REQUIRE(plain.far_wall_inner_walls > 10.);
|
||||
CHECK(one_wall.perimeters < plain.perimeters);
|
||||
CHECK_THAT(one_wall.far_wall_inner_walls, Catch::Matchers::WithinAbs(plain.far_wall_inner_walls, 1.0));
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// The layer that closes the cavity of box_over_cavity(), the first one printed over air.
|
||||
const double cavity_ceiling_z = 6.2;
|
||||
|
||||
@@ -736,3 +856,141 @@ TEST_CASE("Fuzzy skin leaves the walls over an empty layer smooth", "[Perimeters
|
||||
CHECK(floating_first_layer >= 0.);
|
||||
CHECK(floating_first_layer < 0.001);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// A 20x20x5mm square tube whose walls are `wall` mm thick, except the far one at `far_wall` mm.
|
||||
Print &square_tube(Print &print, Model &model, const DynamicPrintConfig &config, double wall, double far_wall)
|
||||
{
|
||||
ModelObject *object = model.add_object();
|
||||
object->name = "square_tube.stl";
|
||||
object->add_volume(make_cube(20., 20., 5.), ModelVolumeType::MODEL_PART, false);
|
||||
TriangleMesh cavity = make_cube(20. - 2. * wall, 20. - wall - far_wall, 5.);
|
||||
cavity.translate(float(wall), float(wall), 0.f);
|
||||
object->add_volume(std::move(cavity), ModelVolumeType::NEGATIVE_VOLUME, false);
|
||||
object->add_instance();
|
||||
object->ensure_on_bed();
|
||||
|
||||
print.auto_assign_extruders(object);
|
||||
print.apply(model, config);
|
||||
print.validate();
|
||||
print.set_status_silent();
|
||||
print.process();
|
||||
return print;
|
||||
}
|
||||
|
||||
// Every setting the wall thicknesses below are measured against. With these widths the two outer walls of
|
||||
// a 0.8mm wall touch, a 1mm wall leaves a gap between them for gap fill, and an inner wall needs about 1.5mm.
|
||||
// Both one wall options are on, so the first and the last layer have a single wall whatever wall_loops asks.
|
||||
DynamicPrintConfig hole_direction_config(int wall_loops, const char *wall_direction)
|
||||
{
|
||||
DynamicPrintConfig config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({
|
||||
{ "wall_generator", "classic" },
|
||||
{ "wall_direction", wall_direction },
|
||||
{ "wall_loops", wall_loops },
|
||||
{ "layer_height", 0.2 },
|
||||
{ "initial_layer_print_height", 0.2 },
|
||||
{ "outer_wall_line_width", 0.42 },
|
||||
{ "inner_wall_line_width", 0.45 },
|
||||
{ "detect_thin_wall", false },
|
||||
{ "filter_out_gap_fill", 0 },
|
||||
{ "top_shell_layers", 3 },
|
||||
{ "bottom_shell_layers", 3 },
|
||||
{ "only_one_wall_top", true },
|
||||
{ "only_one_wall_first_layer", true },
|
||||
{ "overhang_reverse", false },
|
||||
{ "sparse_infill_density", "15%" },
|
||||
});
|
||||
return config;
|
||||
}
|
||||
|
||||
// The outer walls of a layer, contours and holes apart, and how many inner walls and gap fills it has.
|
||||
struct WallDirections {
|
||||
std::vector<bool> contours_ccw;
|
||||
std::vector<bool> holes_ccw;
|
||||
int inner_walls = 0;
|
||||
size_t gap_fills = 0;
|
||||
};
|
||||
|
||||
std::vector<WallDirections> wall_directions(const Print &print)
|
||||
{
|
||||
std::vector<WallDirections> out;
|
||||
for (const Layer *layer : print.objects().front()->layers()) {
|
||||
WallDirections &walls = out.emplace_back();
|
||||
for (const LayerRegion *region : layer->regions()) {
|
||||
walls.gap_fills += region->thin_fills.flatten().entities.size();
|
||||
for (const ExtrusionEntity *entity : region->perimeters.flatten().entities) {
|
||||
if (! entity->is_loop())
|
||||
continue;
|
||||
const ExtrusionLoop *loop = static_cast<const ExtrusionLoop*>(entity);
|
||||
if (loop->inset_idx > 0)
|
||||
++ walls.inner_walls;
|
||||
else
|
||||
(loop->loop_role() == elrHole ? walls.holes_ccw : walls.contours_ccw).push_back(loop->polygon().is_counter_clockwise());
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// Holes run against the wall direction, so the inside of a hole keeps its direction on the layers where the
|
||||
// hole opens into the contour. That holds on every layer, the single wall ones included, as soon as anything
|
||||
// fits beside the outer wall of the hole: infill, an inner wall, or only gap fill. The 1mm tube with a 3mm far
|
||||
// side is the shape that used to flip, where the far side has an inner wall on most layers and gap fill runs
|
||||
// around the rest.
|
||||
TEST_CASE("Holes run against the wall direction", "[Perimeters]")
|
||||
{
|
||||
const auto [wall, far_wall] = GENERATE(std::make_pair(7., 7.), std::make_pair(1., 1.), std::make_pair(1., 3.));
|
||||
const int wall_loops = GENERATE(1, 2);
|
||||
const char *wall_direction = GENERATE("ccw", "cw");
|
||||
CAPTURE(wall, far_wall, wall_loops, wall_direction);
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
square_tube(print, model, hole_direction_config(wall_loops, wall_direction), wall, far_wall);
|
||||
const std::vector<WallDirections> layers = wall_directions(print);
|
||||
// 5mm at 0.2mm layers.
|
||||
REQUIRE(layers.size() == 25);
|
||||
// Without gap fill between the outer walls the thin tubes would test the case below instead.
|
||||
if (wall < 2.)
|
||||
REQUIRE(layers[layers.size() / 2].gap_fills > 0);
|
||||
|
||||
const bool ccw = std::string(wall_direction) == "ccw";
|
||||
for (size_t i = 0; i < layers.size(); ++ i) {
|
||||
CAPTURE(i);
|
||||
REQUIRE(layers[i].contours_ccw.size() == 1);
|
||||
REQUIRE(layers[i].holes_ccw.size() == 1);
|
||||
CHECK(layers[i].contours_ccw.front() == ccw);
|
||||
CHECK(layers[i].holes_ccw.front() == ! ccw);
|
||||
}
|
||||
}
|
||||
|
||||
// A hole whose outer wall touches the contour's all around, with nothing between them, runs with the contour
|
||||
// on every layer, so the two walls of a thin tube are laid side by side in the same direction.
|
||||
TEST_CASE("A hole whose outer wall touches the contour's runs with it", "[Perimeters]")
|
||||
{
|
||||
const int wall_loops = GENERATE(1, 2);
|
||||
const char *wall_direction = GENERATE("ccw", "cw");
|
||||
CAPTURE(wall_loops, wall_direction);
|
||||
|
||||
Print print;
|
||||
Model model;
|
||||
square_tube(print, model, hole_direction_config(wall_loops, wall_direction), 0.8, 0.8);
|
||||
const std::vector<WallDirections> layers = wall_directions(print);
|
||||
REQUIRE(layers.size() == 25);
|
||||
// Nothing fits between the two outer walls.
|
||||
REQUIRE(layers[layers.size() / 2].gap_fills == 0);
|
||||
REQUIRE(layers[layers.size() / 2].inner_walls == 0);
|
||||
|
||||
const bool ccw = std::string(wall_direction) == "ccw";
|
||||
for (size_t i = 0; i < layers.size(); ++ i) {
|
||||
CAPTURE(i);
|
||||
REQUIRE(layers[i].contours_ccw.size() == 1);
|
||||
REQUIRE(layers[i].holes_ccw.size() == 1);
|
||||
CHECK(layers[i].contours_ccw.front() == ccw);
|
||||
CHECK(layers[i].holes_ccw.front() == ccw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,10 @@ add_executable(${_TEST_NAME}_tests
|
||||
../fff_print/test_helpers.cpp
|
||||
)
|
||||
|
||||
if (SLIC3R_CAD)
|
||||
target_sources(${_TEST_NAME}_tests PRIVATE test_design_sketch_tool.cpp)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
target_link_libraries(${_TEST_NAME}_tests Setupapi.lib)
|
||||
endif ()
|
||||
|
||||