mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-10 09:11:08 +00:00
Merge branch 'main' into perf/slicing-optimizations
# Conflicts: # src/libslic3r/PerimeterGenerator.cpp # src/libslic3r/PrintObject.cpp # src/libslic3r/Support/TreeSupport.cpp
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,11 @@ no wx dependency and is unit-tested.
|
||||
The notebook needs a page object for a tab to exist and for tabs to be inserted and
|
||||
removed by pointer, and the placeholder is that object. It builds the real panel inside
|
||||
itself the first time it is shown and forwards showing and hiding afterwards, so a panel's
|
||||
own show handling stays its activation hook. Nothing builds while the main window is
|
||||
own show handling stays its activation hook. The build runs before the placeholder shows
|
||||
itself, so a panel built on demand is created in a hidden window as a prebuilt one is: on
|
||||
Windows every control created or moved inside a shown window re-clips and repaints its
|
||||
shown siblings, which makes building a large panel into a shown page many times slower.
|
||||
Nothing builds while the main window is
|
||||
hidden; the window's first show builds the start page. A page that is out of the book is
|
||||
not prebuilt. A panel built while its page is hidden stays hidden, and gets the theming
|
||||
the window applied before the panel existed.
|
||||
|
||||
+183
-16
@@ -13,8 +13,9 @@ known.
|
||||
Its coupling to the rest of the application is deliberately narrow. It adds no stage to the
|
||||
slicing pipeline and touches neither the preset system nor `Tab`. It reaches the rest of Orca
|
||||
in two places: **Commit to Plate**, which hands finished solids to Prepare as ordinary model
|
||||
objects, and one optional 3MF archive entry that carries the recipe. Everything else is
|
||||
contained in `src/libslic3r/CAD/` and `src/slic3r/GUI/CAD/`.
|
||||
objects (by default one assembly object with a part per body, which keeps the bodies' relative
|
||||
placement, or one object per body), and one optional 3MF archive entry that carries the recipe.
|
||||
Everything else is contained in `src/libslic3r/CAD/` and `src/slic3r/GUI/CAD/`.
|
||||
|
||||
The user-facing manual lives in the wiki
|
||||
([Design Tab](https://www.orcaslicer.com/wiki/design_tab)), not here. This document covers the
|
||||
@@ -118,6 +119,9 @@ referencing the source file, so a project opens without the STEP or mesh it was
|
||||
The cost is that saved projects are coupled to an OCCT BRep revision.
|
||||
`tests/data/cad_brep_occt76.brep` holds a solid written by OCCT 7.6, and its test fails if the
|
||||
bundled OCCT can no longer read it.
|
||||
A Text feature follows the same rule: it stores the outlines it was vectorised into alongside
|
||||
its string, font and height, so the project opens identically on a machine that lacks the font;
|
||||
the three parameters are only what an edit reopens the dialog with.
|
||||
|
||||
## The interaction contract
|
||||
|
||||
@@ -131,8 +135,8 @@ 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 |
|
||||
| `Idle` | nothing transient | clears the selection; leaves a sketch session only if it is empty |
|
||||
| `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
|
||||
method on the panel, so the ordering that is the entire contract is checkable without a window,
|
||||
@@ -145,18 +149,176 @@ 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.
|
||||
|
||||
Right-click is read at button-up against two independent budgets — 3 px of drift and 200 ms —
|
||||
because drift alone still popped a menu at the end of a slow, careful orbit. 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.
|
||||
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.
|
||||
|
||||
Entering a sketch changes three things at once so the mode is legible: a banner above the
|
||||
Right-click is read at button-up against one budget, 3 px of drift, applied to the whole press
|
||||
rather than to its end points: a press that wandered past the budget at any moment is
|
||||
navigation, even if it comes back to where it started, which is what stops a slow, careful
|
||||
orbit from ending in a menu. There is no time budget — a gesture that means something different
|
||||
when it is slow is exactly what the interaction charter rules out. The raycast uses the press
|
||||
position, not the release. The sketch tool sees a right press only once the release has shown it
|
||||
was a click: the press itself goes to the camera, which may pan or orbit with that button, and the
|
||||
canvas replays it to the tool on a stationary release. A tool that uses the click (to terminate a
|
||||
chain, say) keeps the menu closed. Past either budget the event is navigation, and navigation
|
||||
does not transition the state machine.
|
||||
|
||||
Navigation itself is Prepare's: the camera reads the drag actions set in Preferences > Control
|
||||
for each button, and in the Touchpad camera style a move with Alt held orbits and one with Shift
|
||||
held pans, whatever tool is armed. The left button is shared with picking and drawing, so a tool
|
||||
handle or a press that draws takes it first, as a gizmo does in Prepare; a whole body is swept
|
||||
with a rectangle on plain left-drag only while no camera action is assigned to the left button,
|
||||
and with Shift+left-drag otherwise — Prepare's own rectangle selection.
|
||||
|
||||
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
|
||||
|
||||
The tab draws its bodies through the same `GLCanvas3D` object path as Prepare, so how they look
|
||||
is decided in the shared object shader, not in the tab. The slicer's two lights both sit near
|
||||
the camera, which leaves the sides of a part in nearly one tone; the Design canvas asks for a
|
||||
studio model instead — a world-space sky/ground hemisphere, a key and a fill light, a
|
||||
plastic-like highlight and a darker silhouette — through `GLCanvas3D::set_studio_lighting()`
|
||||
and the phong shader's `lighting_model` uniform. The program is shared by every canvas, so each
|
||||
use sets the uniform (0 for the slicer's canvases) rather than relying on a default: a canvas
|
||||
that left it alone would inherit whatever the last canvas chose.
|
||||
|
||||
The B-rep edges of every body are drawn over it by the sketch overlay as thin view-facing
|
||||
ribbons, depth tested and pulled a few pixels toward the eye so they win against the faces that
|
||||
meet at them and still hide behind faces in front; lines are not used because they do not
|
||||
rasterise under the software GL context the tab also supports. Seams of closed surfaces and
|
||||
degenerate edges are left out (`GeometryEngine::display_edges`), and the polylines are sampled
|
||||
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
|
||||
selection colour blended into the body's own colour turns a different hue on every body and
|
||||
vanishes on one close to it. Selected faces are split out of their body into a volume of their
|
||||
own, which the canvas draws opaque in the selection colour through the same shader and lighting
|
||||
as the body (`DesignCanvas::rebuild_bodies`); the sketch overlay outlines them with a cased line
|
||||
— a dark band under a selection-coloured one — so the outline still reads on a body that wears
|
||||
the selection colour itself. A body picked whole, a face picked in the viewport and the faces of
|
||||
the Feature tree's selected feature all draw this way. The hover pre-highlight is the outline
|
||||
alone, uncased: it promises a click, it is not one. The automatic body colours keep clear of the
|
||||
selection colour's blues and teals, so no body looks selected before anything is picked; a colour
|
||||
the user sets on a body is theirs, and the cased outline keeps its selection readable.
|
||||
|
||||
Selecting a feature row lights the faces that feature made, not the whole body it sits on, so a
|
||||
fillet row shows its round and the extrude under it keeps the faces the fillet trimmed.
|
||||
`CadDocument::faces_made_by` answers it without per-feature history: it replays the recipe to
|
||||
just before the feature and then the feature alone, and a face of the finished model belongs to
|
||||
the feature when an interior point of it lies on the boundary afterwards and not before, facing
|
||||
the same way — the facing keeps a block stacked on a base the owner of its bottom face. A feature
|
||||
that makes no face of its own, such as a Boolean union, answers with the bodies it changed. The
|
||||
replay costs up to a recompute, so the panel finds the faces once per row and topology
|
||||
generation, off the UI thread, and only while no feature card is open. One selection is live at
|
||||
a time: a viewport pick clears the feature row and a feature row clears the viewport pick, as the
|
||||
Feature tree and Bodies list do between themselves. `Esc`, a click on empty space and an
|
||||
empty rubber band all let go of it, whichever list or pick made it — except while a body Move is
|
||||
open, which holds the selection until it ends (see the interaction contract).
|
||||
|
||||
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
|
||||
|
||||
The tab is a page of Orca's main window and answers to the same settings as Prepare.
|
||||
|
||||
- **Theme.** Its chrome is coloured from a table of light/dark token pairs. A theme switch
|
||||
reaches `DesignPanel::on_sys_color_changed` from `MainFrame`, which moves every colour that is
|
||||
one theme's token onto the other theme's and then runs the app's own dark pass; the icons are
|
||||
Orca's sidebar grey, which the icon cache maps per theme, so they are re-rasterised rather
|
||||
than re-tinted.
|
||||
- **Scale.** Sizes are in DIP, and a DPI change reaches `DesignPanel::msw_rescale`, which
|
||||
re-rasterises every icon (button faces, flyout rows, card headers, the feature and body lists'
|
||||
row icons).
|
||||
- **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 — 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.
|
||||
- **Undo.** The tab keeps its own history (the recipe is not part of Prepare's snapshots), but
|
||||
it has no Undo/Redo of its own: the top bar, `Ctrl+Z` and Edit drive it while the tab is
|
||||
shown, greyed to what an undo would actually do.
|
||||
- **Docking.** The sidebar docks like Prepare's — either side, floating, resized, or collapsed
|
||||
with the canvas's collapse button or `Shift+Tab` — through its own AUI manager under the
|
||||
toolbar, because Prepare's manages the Plater and the Plater is not on this page. The button is
|
||||
the canvas's own toolbar rather than Prepare's, which collapses Prepare's sidebar. The layout,
|
||||
collapse included, is kept apart from Prepare's (`design_window_layout`) and starts where
|
||||
Prepare's sidebar is, at its width, so the canvas edge holds still across the tab switch until
|
||||
the user moves one of them. A floating sidebar is a top-level window, so it is hidden with the
|
||||
tab rather than left over the other pages, and View > Reset Window Layout resets both tabs.
|
||||
|
||||
## The offer is generated, not hand-written
|
||||
|
||||
@@ -164,9 +326,13 @@ Right-clicking geometry opens the *offer*: eight families in a fixed order, each
|
||||
permanent row index, verbs that do not apply shown disabled **in place with their reason**
|
||||
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.
|
||||
position, so the menu is never re-sorted, compacted or adaptively ordered. Above the families
|
||||
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
|
||||
@@ -195,6 +361,7 @@ scripted action and a clicked one cannot diverge. It is off unless the variable
|
||||
| `src/libslic3r/CAD/SketchSolver.*` | constraint solving, over the vendored solver |
|
||||
| `src/libslic3r/slvs/` | vendored 2D constraint solver (GPLv3) |
|
||||
| `src/slic3r/GUI/CAD/DesignPanel.*` | the tab: toolbar, feature cards, tree, key maps |
|
||||
| `src/slic3r/GUI/CAD/DesignRowList.*` | the Feature tree and Bodies lists, with per-row actions |
|
||||
| `src/slic3r/GUI/CAD/DesignCanvas.*` | viewport integration |
|
||||
| `src/slic3r/GUI/CAD/DesignSketchTool.*` | in-canvas sketching |
|
||||
| `src/slic3r/GUI/CAD/DesignInteraction.hpp` | the Esc level contract |
|
||||
|
||||
+529
-183
@@ -2,238 +2,584 @@
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
Precise Seam places the seam where a helper volume intersects the external
|
||||
wall. The user attaches a mesh to an object as a Precise Seam modifier, and on
|
||||
every layer the seam placer reads the modifier's slice to decide where the seam
|
||||
of each external perimeter may, must or must not go. The same mesh keeps
|
||||
working after the model changes, so the seam does not have to be repainted
|
||||
after every design revision, and a swept helper body can guide the seam along
|
||||
any path.
|
||||
Precise Seam lets a helper volume decide where the seam of an object goes. The
|
||||
user attaches a mesh to an object as a Precise Seam modifier. On every layer,
|
||||
the part of the external perimeter that lies inside the modifier's slice
|
||||
determines where the seam must, may or must not be placed. The helper is a
|
||||
persistent model object rather than paint on the surface, so it keeps working
|
||||
when the design changes. A body swept along a path on the surface can guide the
|
||||
seam along any trajectory.
|
||||
|
||||
The modifier is non-printing geometry. It does not take part in slicing, region
|
||||
assignment, filament selection or brim adhesion. It affects only seam
|
||||
placement, which runs during G-code export.
|
||||
The modifier is non-printing geometry. It takes no part in object slicing,
|
||||
region assignment, filament selection or brim adhesion, and it affects only seam
|
||||
placement during G-code export. Objects without Precise Seam volumes follow the
|
||||
regular seam placement unchanged.
|
||||
|
||||
## Volume types and priority
|
||||
Precise Seam does not replace the seam placer. It feeds it: a modifier inserts
|
||||
the points it needs into the perimeter and changes the enforced/blocked type of
|
||||
seam candidates, the same typing mechanism as seam painting, and the configured
|
||||
seam position then chooses among them.
|
||||
|
||||
Precise Seam adds six `ModelVolumeType` values after `SUPPORT_ENFORCER`. The
|
||||
strong types come first and the weak types follow. `is_precise_seam()`,
|
||||
## Modifier types
|
||||
|
||||
Precise Seam adds six `ModelVolumeType` values after `SUPPORT_ENFORCER`, strong
|
||||
types first and weak types after them. `is_precise_seam()`,
|
||||
`is_precise_seam_strong()` and `is_precise_seam_weak()` are range checks that
|
||||
depend on this order.
|
||||
|
||||
| Type | Group | Effect on the perimeter |
|
||||
| Type | Group | Effect on an intersected perimeter |
|
||||
| --- | --- | --- |
|
||||
| `PRECISE_SEAM_CENTER` | strong | seam at the arc-length midpoint of the intersection |
|
||||
| `PRECISE_SEAM_CENTER` | strong | seam at the midpoint, by arc length, of the intersection |
|
||||
| `PRECISE_SEAM_LEFT` | strong | seam at the first point of the intersection |
|
||||
| `PRECISE_SEAM_RIGHT` | strong | seam at the last point of the intersection |
|
||||
| `PRECISE_SEAM_ENFORCED` | weak | intersection marked as enforced |
|
||||
| `PRECISE_SEAM_BLOCKED` | weak | intersection marked as blocked |
|
||||
| `PRECISE_SEAM_NEUTRAL` | weak | intersection reset to neutral |
|
||||
|
||||
A strong modifier fixes one point. A weak modifier only changes the
|
||||
enforced/blocked type of seam candidates, and the configured seam position then
|
||||
chooses among them. First and last are taken along the perimeter made
|
||||
counter-clockwise seen from above. On an outer wall seen from outside, Left is
|
||||
the left end of the intersection. On the wall of a hole seen from inside the
|
||||
hole, the two ends are swapped.
|
||||
A strong modifier fixes a single point. The perimeter gets exactly one enforced
|
||||
seam candidate there, and every other candidate is blocked. A weak modifier
|
||||
retypes, and where needed adds, the candidates inside its intersection, like
|
||||
painting does.
|
||||
|
||||
An **intersection** is a continuous part of the external perimeter's centerline
|
||||
that lies inside the modifier's slice on that layer. It is a portion of the
|
||||
perimeter, never a chord through the object. The centerline lies half an
|
||||
extrusion width inside the model surface and depends on print settings, so a
|
||||
modifier must reach clearly past the surface to cross it unambiguously.
|
||||
|
||||
### Terms
|
||||
|
||||
- **Segment:** an intersection as the code represents it (`PerimeterSegment`).
|
||||
User-facing texts call it an intersection.
|
||||
- **Fragment:** a piece of the perimeter returned by clipping, before it is tied
|
||||
to the source contour.
|
||||
- **Interval:** the bound part of one source edge, given by the edge index and a
|
||||
parameter range on that edge.
|
||||
- **Zone:** a weak segment with its type (Enforced, Blocked or Neutral).
|
||||
- **Boundary:** an end of a zone, inserted into the perimeter polygon.
|
||||
- **Candidate:** a seam candidate of the seam placer, built from the points of
|
||||
the processed perimeter polygon (painted enforcers may add more).
|
||||
|
||||
First and last are taken along the perimeter oriented counter-clockwise as seen
|
||||
from above. On an outer wall seen from outside, Left is therefore the left end
|
||||
of the intersection. On the wall of a hole seen from inside the hole, the two
|
||||
ends are swapped. Mirroring an object does not mirror the mode: perimeters stay
|
||||
counter-clockwise, so Left remains the left end seen from outside, and the seam
|
||||
moves to the other end of the modifier instead of following the mirrored model.
|
||||
|
||||
## Priority
|
||||
|
||||
The order of volumes in the object is the priority order, highest first.
|
||||
`ModelObject::sort_volumes()` keeps every strong modifier before every weak one
|
||||
and preserves the user's order within each group. The object list lets the user
|
||||
drag a modifier only within its own group. A type change that crosses a group
|
||||
boundary moves the volume to the end of its new group, where it has the lowest
|
||||
priority. Strong modifiers are tried in this order, and the first one that
|
||||
yields a seam on a perimeter wins. Weak modifiers are applied from the lowest
|
||||
priority to the highest, so the highest one overwrites any overlapping zone.
|
||||
drag a modifier only within its own group. A type change that crosses the group
|
||||
boundary moves the volume to the end of its new group, with the lowest priority
|
||||
there.
|
||||
|
||||
## Model storage and 3MF compatibility
|
||||
- **Strong:** modifiers are tried in priority order on each perimeter. The first
|
||||
one that yields a usable segment decides the seam. Within that modifier the
|
||||
longest segment wins; lengths are never compared across modifiers. Once a
|
||||
strong point is placed, no later strong modifier and no weak modifier is
|
||||
processed for that perimeter.
|
||||
- **Weak:** every weak modifier applies. They are applied from the lowest
|
||||
priority to the highest, so the highest one overwrites overlapping zones. A
|
||||
Blocked modifier that fully contains a perimeter is the exception: it is
|
||||
skipped there (see [Full containment](#full-containment)).
|
||||
|
||||
Projects must stay readable by earlier releases, and the modifier must not
|
||||
change a print there. Both 3MF writers therefore store a Precise Seam volume as
|
||||
an ordinary parameter modifier: `modifier_part` in the Bambu-format part
|
||||
subtype, and `ParameterModifier` together with the legacy `modifier` flag in
|
||||
the Prusa-format volume metadata. The seam mode is written separately under
|
||||
A strong modifier without a usable segment, even one whose fragments were all
|
||||
discarded, passes the turn to the next one.
|
||||
|
||||
## Data flow
|
||||
|
||||
1. **Invalidation.** `Print::apply()` treats a change of Precise Seam volumes as
|
||||
a change of seam placement and invalidates G-code export; the object is not
|
||||
resliced (see [Print invalidation](#print-invalidation)).
|
||||
2. **Modifier slices.** `SeamPlacer::init()` collects each object's Precise Seam
|
||||
volumes once, slices every volume separately and caches its regions with
|
||||
their bounding boxes.
|
||||
3. **Perimeters.** Seam candidates are gathered in parallel over the layers.
|
||||
For objects with Precise Seam volumes, each external perimeter polygon is
|
||||
normalized and prepared once for all modifiers.
|
||||
4. **Extraction.** For each modifier, the perimeter is clipped against the
|
||||
modifier's regions on that layer. The clipped fragments are bound back to the
|
||||
source edges of the perimeter and assembled into segments.
|
||||
5. **Strong, then weak.** Strong modifiers try to insert one seam point into the
|
||||
perimeter polygon. If none succeeds, weak modifiers insert their zone
|
||||
boundaries and subdivide enforced edges.
|
||||
6. **Candidates.** The seam placer builds candidates from the modified polygon.
|
||||
Painting assigns types first, weak zones overwrite them, and a strong point
|
||||
makes its candidate the only enforced one.
|
||||
7. **Selection and restoration.** The configured seam position chooses the
|
||||
seams and aligns them. Afterwards the exact strong points are restored.
|
||||
8. **Warnings.** After all objects are processed, `SeamPlacer::init()` prepares
|
||||
one combined warning text if any problem was found; G-code export issues it.
|
||||
|
||||
## Modifier slices
|
||||
|
||||
`init_precise_seam_data()` collects the Precise Seam volumes of each object:
|
||||
strong ones in priority order and weak ones in reverse, so that weak zones can
|
||||
be applied with last-write-wins. Each volume is sliced separately with
|
||||
`PrintObject::slice_single_volume_regions()`, at the object's layer heights and
|
||||
with the same centered transformation as the object. The slices keep every
|
||||
region's outer contour together with its holes as an `ExPolygon`. Volumes are
|
||||
not merged, so each keeps its own priority, and a modifier may have several
|
||||
regions on one layer.
|
||||
|
||||
`prepare_modifier_slices()` moves the slices into `ModifierRegionsCache`,
|
||||
pairing each region with the bounding box of its exterior. Empty layers keep
|
||||
their slots, so the cache is indexed by object layer; `Layer::id()` includes raft
|
||||
layers, which are subtracted. The cache is filled before candidates are gathered
|
||||
and is only read afterwards, shared by both modifier kinds and all worker
|
||||
threads without locking.
|
||||
|
||||
## Perimeter preparation
|
||||
|
||||
The seam placer works on external perimeter loops, including the walls of
|
||||
holes. For objects with Precise Seam volumes, consecutive duplicate points and
|
||||
the repeated closing point of each extrusion loop are removed: adjacent
|
||||
extrusion paths share endpoints, and the resulting zero-length edges would
|
||||
prevent point insertion at their junctions. Distinct visits to one point of a
|
||||
self-touching contour are kept. Objects without Precise Seam volumes keep their
|
||||
original points, so ordinary seam candidates are unaffected.
|
||||
|
||||
Each polygon is made counter-clockwise. A single `PreparedPerimeter` is then
|
||||
built for all modifiers of that perimeter. It holds a validity check (at least
|
||||
three points, no consecutive or closing duplicates), the bounding box, and the
|
||||
clipping line: the polygon as an open polyline with its first point repeated at
|
||||
the end. The preparation borrows the polygon and is used only while the polygon
|
||||
is unchanged: strong processing returns immediately after inserting its point,
|
||||
and weak processing collects all segments before it inserts anything. An
|
||||
invalid perimeter receives no Precise Seam processing.
|
||||
|
||||
## Segment extraction
|
||||
|
||||
`extract_perimeter_segments()` turns one modifier's regions on one layer into
|
||||
segments of the perimeter, each with its geometry and its position on the
|
||||
source contour. Both modifier kinds consume these segments; the extractor is
|
||||
told the modifier type so that it prepares only the data that type needs.
|
||||
|
||||
### Clipping
|
||||
|
||||
Regions whose bounding box does not overlap the perimeter's are skipped. The
|
||||
clipping line is intersected with each remaining region by `intersection_pl()`,
|
||||
which clips an open path against an `ExPolygon` with its holes attached, using
|
||||
the nonzero rule. Clipping an open line yields only pieces of the perimeter, so
|
||||
a modifier crossing the whole object produces two separate pieces rather than a
|
||||
chord through the body. Holes in a modifier and several regions of one modifier
|
||||
simply produce more pieces. The line is cut at vertex zero, so a piece crossing
|
||||
that vertex arrives as two fragments. A border that only touches the line can
|
||||
come back as a single point; such fragments carry no coverage and are dropped
|
||||
before binding.
|
||||
|
||||
### Binding fragments to source edges
|
||||
|
||||
Clipper returns coordinates only. Insertion needs the source edge of every
|
||||
point, and coordinates alone are ambiguous where a contour visits the same
|
||||
point twice. Each fragment is therefore bound to the source edges it covers,
|
||||
producing intervals: an edge index with a parameter range on that edge.
|
||||
|
||||
- **Exact path.** For fragments with interior points, the second point is used
|
||||
as an anchor that must equal a source vertex exactly. Clipping keeps the
|
||||
vertices of an open path unchanged, including collinear ones. The following
|
||||
points must match successive source vertices in either direction; later
|
||||
occurrences of the anchor are tried if a sequence does not match. Only the two
|
||||
end cuts are projected onto their edges.
|
||||
- **Projection path.** Two-point fragments, and fragments the exact path cannot
|
||||
match, are bound by projection. The first source edge that holds both points
|
||||
of the first pair, with distinct parameters, establishes the edge and
|
||||
direction. Every following pair must continue on the same edge or cross to the
|
||||
neighboring edge at their actual shared vertex, in the same direction. A pair
|
||||
continuing on the same edge reuses the previous pair's parameter for their
|
||||
shared point, so the two projections of one point cannot differ.
|
||||
- **Failure.** A fragment that cannot be bound continuously is rolled back and
|
||||
discarded. Earlier fragments and other fragments are unaffected. The failure
|
||||
is counted, logged and reported to the user (see
|
||||
[Diagnostics](#diagnostics-and-warnings)).
|
||||
|
||||
Two rare rounding cases are handled only after both paths have failed, so the
|
||||
normal path never pays for them:
|
||||
|
||||
- **Cut beside a vertex.** When a modifier boundary crosses within about one
|
||||
coordinate unit of a source vertex, Clipper can place the cut at the vertex's
|
||||
height but a few units beside it. The end pair then collapses to the vertex's
|
||||
parameter or misses both neighboring edges. An end cut closer than the
|
||||
snapping radius to a vertex of the fragment's own chain is snapped to that
|
||||
vertex: either its neighbor in the fragment (the cut is a rounded copy of it
|
||||
and is dropped) or a vertex that shares a source edge with that neighbor. The
|
||||
neighbor wins whenever it is within the radius. Ends that are themselves source
|
||||
vertices and ambiguous choices are left unchanged. Binding is then retried
|
||||
once with the same strict rules, so a wrong candidate can only fail again.
|
||||
- **Contact.** A fragment that still fails but is shorter than the snapping
|
||||
radius is accepted as a contact and binds nothing. Insertion would collapse it
|
||||
onto one point anyway.
|
||||
|
||||
Both outcomes are recoveries, not failures: they show no user warning but leave
|
||||
a log marker.
|
||||
|
||||
### Assembling segments
|
||||
|
||||
The intervals are sorted by edge and parameter. Intervals on the same occurrence
|
||||
of an edge are united when they overlap or meet, by parameter or at the same
|
||||
integer point; equal coordinates on different edges are never united. A
|
||||
parameter of 1 is stored as parameter 0 of the next edge, so intervals on
|
||||
adjacent edges meet exactly at their shared vertex. Consecutive intervals that
|
||||
meet form one `PerimeterSegment`, and the last segment is joined with the first
|
||||
when they meet at vertex zero, undoing the artificial cut of the clipping line.
|
||||
|
||||
Each segment keeps its polyline, the source edge of every polyline edge, and its
|
||||
begin and end positions on the source contour.
|
||||
|
||||
### Full containment
|
||||
|
||||
A modifier that covers the whole perimeter has no boundaries on it. The policy
|
||||
follows seam painting, where painting a whole perimeter green is a meaningful
|
||||
choice and forbidding the seam all round is not:
|
||||
|
||||
- **Seam Enforced** types the whole perimeter, like a perimeter painted green all
|
||||
round, with subdivision applied as described under [Weak modifiers](#weak-modifiers).
|
||||
- **Seam Neutral** types the whole perimeter Neutral, like an unmarked perimeter,
|
||||
clearing painting and lower zones.
|
||||
- **Seam Blocked** is skipped for the perimeter, with the full-containment
|
||||
warning. The seam cannot avoid the whole perimeter, so the modifier does not
|
||||
override anything below it: lower zones and painting stay in effect.
|
||||
- **Seam Center, Left and Right** are skipped with the same warning: there is no
|
||||
intersection to place the point on.
|
||||
|
||||
Enforced and Neutral take part in the usual priority order (see
|
||||
[Weak modifiers](#weak-modifiers)).
|
||||
|
||||
The perimeter is fully contained when the united intervals cover every source
|
||||
edge from parameter 0 to 1. A modifier boundary that merely touches the
|
||||
perimeter counts as well:
|
||||
|
||||
- At a vertex or on an axis-aligned edge, clipping splits the line exactly at the
|
||||
touch, the pieces meet at one point, and the coverage is complete.
|
||||
- On an inclined edge the touching point is usually not representable on the
|
||||
integer grid. The boundary pokes a few units across and leaves a real gap, so
|
||||
a single segment covers everything except that gap.
|
||||
|
||||
Weak insertion would collapse such a segment's boundaries onto one vertex and
|
||||
turn the intended zone into a single candidate, and strong would put the seam at
|
||||
the touch. A single segment is therefore also full containment in the cases
|
||||
where insertion collapses it, exactly up to edges shorter than 2 µm:
|
||||
|
||||
- the uncovered length from its end to its begin is below 1 µm, or
|
||||
- the gap spans one vertex, or starts at a vertex and ends on the next edge, and
|
||||
both ends lie within 1 µm of the vertex that ends the first gap edge, since
|
||||
each end then snaps onto it from its own edge.
|
||||
|
||||
A cheap filter runs first: both cases bring the segment's ends within 2 µm of
|
||||
each other.
|
||||
|
||||
## Strong modifiers
|
||||
|
||||
For a strong modifier, the extractor prepares each segment's target point
|
||||
before anything is inserted, together with the source edge it lies on:
|
||||
|
||||
- **Left:** the segment's first point.
|
||||
- **Right:** the segment's last point.
|
||||
- **Center:** the point at half the segment's arc length.
|
||||
|
||||
Arc length is the sum of Euclidean edge lengths, not the chord or a vertex count.
|
||||
|
||||
`insert_strong_seam_point()` selects the longest segment of the first modifier
|
||||
that has one. Exactly equal lengths are resolved by the prepared target points:
|
||||
greater bed Y first, then smaller X; a complete tie keeps the first segment.
|
||||
Slice coordinates already include instance rotation and have the bed axes;
|
||||
centering and XY translation do not change this order. Nearly equal lengths are
|
||||
not treated as equal, so exact ties occur mainly on axis-aligned geometry.
|
||||
Geometrically equal segments, such as a symmetric modifier crossing both faces
|
||||
of a thin wall, differ only by rounding noise that varies between layers, so
|
||||
the chosen face may alternate. This is accepted deliberately: such a modifier is
|
||||
ambiguous by itself: more than one segment raises the "multiple intersections"
|
||||
warning. The user should make the modifier cross the perimeter once.
|
||||
|
||||
The selected point is inserted on its source edge. A point within 1 µm of an
|
||||
existing vertex is snapped to that vertex. Helper points are added 1 µm on both
|
||||
sides of it, except on an adjacent edge shorter than 2 µm, which already bounds
|
||||
the distance.
|
||||
|
||||
When the candidates are built, the candidate at the inserted point is the only
|
||||
enforced one and becomes the central enforcer; every other candidate is blocked.
|
||||
Every seam position mode therefore selects it. Alignment and random placement
|
||||
can still move the final position, so after alignment
|
||||
`restore_precise_seam_positions()` writes the exact point and its index back
|
||||
into every perimeter that has a strong seam.
|
||||
|
||||
## Weak modifiers
|
||||
|
||||
`collect_weak_modifier_segments()` extracts the segments of every weak modifier
|
||||
before the polygon is modified, so all positions refer to the same contour. Each
|
||||
segment becomes a zone with a type and two boundaries, kept in application
|
||||
order, lowest priority first. Full containment of an Enforced or Neutral
|
||||
modifier becomes a whole-perimeter zone at its place in that order: it has no
|
||||
boundaries and takes part in no insertion or helper step below. The boundaries
|
||||
carry their positions on the source contour; these remain as provenance after
|
||||
insertion and are not indices into the modified polygon.
|
||||
|
||||
`prepare_weak_modifier_segments()` then changes the polygon:
|
||||
|
||||
1. **Boundary insertion.** Insertion events are sorted by decreasing source edge
|
||||
and parameter, and the polygon is modified from its end towards its start. A
|
||||
pending boundary's source index therefore stays valid. Vertex zero has the
|
||||
canonical position `(0, 0)` and is
|
||||
processed last, and a point on the closing edge is appended rather than
|
||||
inserted at index zero. A boundary within 1 µm of either endpoint of its
|
||||
current edge, an original vertex or a boundary inserted earlier, is snapped to
|
||||
that point, so coincident boundaries share a vertex. A zone narrower than
|
||||
1 µm collapses into a single vertex.
|
||||
2. **Helper points.** A helper point is added 1 µm outside every boundary,
|
||||
unless the edge there is shorter than 2 µm, which already bounds it. The
|
||||
helpers keep the edges at a boundary short, so a seam placed along such an
|
||||
edge stays close to the boundary. Coincident boundaries share their helpers.
|
||||
3. **Enforced subdivision.** Zone types are resolved for the polygon's edges in
|
||||
priority order. The edges of a zone are those from its left boundary up to,
|
||||
but not including, its right boundary; a whole-perimeter zone types every
|
||||
edge. Enforced edges longer than `SeamPlacer::enforcer_oversampling_distance`
|
||||
(0.2 mm) are subdivided into steps of at most that length; shorter edges and
|
||||
existing vertices are kept.
|
||||
The regular seam placer then chooses the seam as for painted seams.
|
||||
|
||||
When candidates are built, painting assigns their types first.
|
||||
`apply_weak_modifiers_to_perimeter()` then overwrites the types of the
|
||||
candidates between the boundaries of each zone, both boundaries included,
|
||||
lowest priority first; a whole-perimeter zone types every candidate. Blocked
|
||||
and Enforced zones therefore take precedence over painting, and Neutral clears
|
||||
painting inside its zone.
|
||||
|
||||
## Numeric tolerances
|
||||
|
||||
Coordinates are integers in scaled units: 1 nm by default, and 10 nm when a bed
|
||||
larger than 2147 mm switches `SCALING_FACTOR`. Both Precise Seam tolerances are
|
||||
deliberately defined in units rather than physical distances. Clipper truncates
|
||||
cuts to whole units at any scale, so the on-edge tolerance must follow the unit; the
|
||||
snapping radius scales with it to keep its margin over single-precision
|
||||
candidate coordinates, which are coarser on large beds. Distances quoted in
|
||||
this document in nanometers and
|
||||
micrometers assume the default unit; on large printers they are ten times
|
||||
larger. The enforced subdivision step is a physical distance and stays 0.2 mm.
|
||||
|
||||
| Value | Role |
|
||||
| --- | --- |
|
||||
| `MACHINE_PRECISION_SQUARED` (2.5 units², about 1.6 nm) | A point lies on an edge if it is this close. It absorbs Clipper's truncation of cuts to whole units (under √2 units from the edge) and never bridges a real gap: a one-unit uncovered gap stays a gap. |
|
||||
| `TOLERANCE_LINEAR` (1000 units, 1 µm) | Insertion snaps points this close to an existing vertex, and helper points are placed this far from boundaries. The same radius bounds the rounding fallback, contacts and the sub-micron full-containment rule, so those decisions match what insertion would produce anyway. |
|
||||
| `enforcer_oversampling_distance` (0.2 mm) | Maximum step of enforced subdivision. |
|
||||
|
||||
Raising the on-edge tolerance would not help with cuts beside a vertex: more
|
||||
points past a vertex would be clamped to its parameter and collapse. Lowering it
|
||||
would reject ordinary rounded cuts. The snapping radius is kept far above
|
||||
clipping precision for robustness: seam candidates hold single-precision
|
||||
coordinates, whose step is about 8 to 15 nm at typical object coordinates
|
||||
(about 0.25 µm 3 m from the object's centre, on large beds only), and
|
||||
weak boundaries and the strong point are located among the candidates by those
|
||||
coordinates, so distinct points must stay clearly distinct. 1 µm is also far
|
||||
below printing precision.
|
||||
|
||||
## Diagnostics and warnings
|
||||
|
||||
One `PreciseSeamWarnings` instance is shared by all objects and layers of a
|
||||
`SeamPlacer::init()` call. After all objects are processed, `SeamPlacer::init()`
|
||||
prepares at most one warning text, available through `precise_seam_warning()`.
|
||||
G-code export issues it as one non-critical warning with the ID
|
||||
`SlicingPreciseSeamWarning`. It is a single line, "Precise Seam: <causes>. Seam
|
||||
placement may differ from expected.", because the export warnings dialog shows
|
||||
only the first line of each warning. Repeated warning events replace the
|
||||
notification instead of appending to it. Except for the "had no effect" cause,
|
||||
the causes name the modifier types involved, as the menu names them, in menu
|
||||
order and each type once, for example "(Seam Left, Seam Enforced)".
|
||||
The causes are:
|
||||
|
||||
- **failed to process some intersections (types):** at least one fragment was
|
||||
discarded by binding. Other segments remain usable.
|
||||
- **multiple intersections with a perimeter, only one was used (types):**
|
||||
a Seam Center, Left or Right modifier had more than one segment on a
|
||||
perimeter (see [Strong modifiers](#strong-modifiers)).
|
||||
- **a perimeter is fully inside a modifier, the modifier was not applied to it
|
||||
(types):** a Seam Center, Left, Right or Blocked modifier was skipped for a
|
||||
perimeter (see [Full containment](#full-containment)).
|
||||
- **modifier "<name>" of "<object>" had no effect on the seam (it might not reach
|
||||
the centerline of the printed perimeter):** a modifier was evaluated on at
|
||||
least one perimeter and never gave a segment, full containment or a discarded
|
||||
fragment. Only the first such modifier in print and volume order is named,
|
||||
followed by "(N in total)" when there are several.
|
||||
|
||||
Only the effect is certain, so the cause is given as a hint. A modifier is
|
||||
evaluated only when its turn comes: on a perimeter where a higher strong
|
||||
modifier placed the seam, lower strong and all weak modifiers are not
|
||||
evaluated. A modifier that was never evaluated is not reported, since nothing
|
||||
is known about it. A point contact gives no segment and does not count as
|
||||
reaching the perimeter.
|
||||
|
||||
The log records the following diagnostic markers:
|
||||
|
||||
- `[PreciseSeamIntersectionFailed]` for a discarded fragment, with object,
|
||||
modifier, layer, height, fragment and failing pair, the failure reason and
|
||||
point counts.
|
||||
- `[PreciseSeamFragmentRecovered]` for a recovery, with `outcome=bound` or
|
||||
`outcome=contact`, the same location fields and the original failure reason.
|
||||
- `[PreciseSeamNoEffect]` for every modifier of the "had no effect" cause, with
|
||||
the object and modifier names. Unlike the user warning, the log lists all of
|
||||
them.
|
||||
|
||||
Failures and recoveries are counted separately. The first 10 of each per
|
||||
`init()` call are logged in detail, in parallel processing order; if a limit is
|
||||
exceeded, one summary marker reports the total and the number omitted.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **The modifier must reach the perimeter centerline.** Contacts are taken as
|
||||
clipping returns them, without offsets or tangency rules, so boundaries that
|
||||
only graze the centerline are the user's responsibility. Several near-touches
|
||||
on inclined edges can leave several segments separated by gaps of a few units;
|
||||
their zones then cover nearly the whole perimeter instead of being treated as
|
||||
full containment.
|
||||
- **Self-touching perimeters.** Extraction keeps distinct visits of one
|
||||
coordinate apart through its source-edge bindings, but the consumers locate
|
||||
inserted points by coordinates. A weak zone is typed and subdivided from the
|
||||
first vertex with its boundary coordinate, while boundary helpers are added at
|
||||
every such vertex. A strong point marks every candidate at its coordinate as
|
||||
enforced, and the last one is restored after alignment. If a boundary or a
|
||||
strong point falls exactly on a repeated coordinate, a zone may therefore start
|
||||
from another visit, or the seam may start at another visit of the same point.
|
||||
Carrying visit identity through insertion, refinement, candidates and
|
||||
restoration would touch the whole pipeline, so it is not done for this rare
|
||||
geometry. Overlapping source visits are likewise outside the binding contract.
|
||||
|
||||
## Integration with the application
|
||||
|
||||
### Other seam settings
|
||||
|
||||
- Precise Seam takes part only in outer and hole perimeter seam placement. In
|
||||
spiral vase mode the seam placer is not used for perimeters, so the modifiers
|
||||
have no effect.
|
||||
- Scarf seams, the seam gap and wiping start from the chosen point exactly as
|
||||
they would from an ordinary seam.
|
||||
- Seam painting acts only from model parts, the volumes the seam gizmo shows and
|
||||
edits, and from negative volumes. Painting retained on a volume after a change
|
||||
from part to a Precise Seam, ordinary or support modifier is ignored. A type
|
||||
change back to a model part reactivates any retained painting.
|
||||
Negative volumes keep it on purpose: painting a
|
||||
part and turning it into a negative volume is the only way to paint the wall
|
||||
of the hole it cuts. That painting still affects the seam but is invisible in
|
||||
the gizmo and cannot be edited there; this is known technical debt.
|
||||
If painting them is ever made editable, G-code invalidation must track it too:
|
||||
`model_custom_seam_data_changed()` checks model parts only.
|
||||
|
||||
### Model storage and 3MF compatibility
|
||||
|
||||
Projects must stay readable by earlier releases, and a Precise Seam volume must
|
||||
not change a print there. Both 3MF writers therefore store it as an ordinary
|
||||
parameter modifier: `modifier_part` in the Bambu-format part subtype, and
|
||||
`ParameterModifier` together with the legacy `modifier` flag in the
|
||||
Prusa-format volume metadata. The seam mode is written separately under
|
||||
`precise_seam_type`, using the names from `ModelVolume::type_to_string()`
|
||||
(`precise_seam_center` and so on).
|
||||
|
||||
On load, the mode applies after all other volume metadata, regardless of XML
|
||||
key order, and only when the base type is a modifier. Missing or unknown modes
|
||||
leave an ordinary modifier. Seam metadata on any other base type is ignored.
|
||||
Files that stored the seam mode directly as the volume type still load.
|
||||
On load, the mode is applied after all other volume metadata, regardless of XML
|
||||
key order, and only when the base type is a modifier. A missing or unknown mode
|
||||
leaves an ordinary modifier, and seam metadata on any other base type is
|
||||
ignored. Files that stored the seam mode directly as the volume type still load.
|
||||
A project saved again by an earlier release loses the seam mode for good: the
|
||||
volumes stay ordinary modifiers without settings.
|
||||
|
||||
A Precise Seam volume keeps any per-volume settings it had as a part or
|
||||
modifier, but they are inactive and the object list shows no settings item for
|
||||
it. The writers prefix these keys with `precise_seam_config:`, so an earlier
|
||||
reader drops them as unknown options. The volume therefore loads there as a
|
||||
modifier without settings and has no effect on the print. The current reader
|
||||
restores the keys only when the volume ends up as a Precise Seam type, so the
|
||||
settings return when the user changes the type back. Configuration values are
|
||||
XML-escaped in both writers, for every volume type.
|
||||
reader drops them as unknown options and loads a modifier without settings,
|
||||
which has no effect on the print. The current reader restores the keys only when
|
||||
the volume ends up as a Precise Seam type, so the settings return when the user
|
||||
changes the type back.
|
||||
|
||||
## Print invalidation
|
||||
### Print invalidation
|
||||
|
||||
`Print::apply()` compares the Precise Seam volumes of each object by type, ID
|
||||
and transformation. Adding, removing, moving, reordering or retyping one
|
||||
cancels background processing and invalidates only `psGCodeExport`; the sliced
|
||||
layers are kept. `model_volume_list_update_supports_and_seams()` then brings
|
||||
the support and Precise Seam volumes of the print's model copy in line with the
|
||||
new model in one pass. A volume may switch between the two families, since
|
||||
neither affects slicing. A conversion to or from a part or ordinary modifier
|
||||
changes the solid and modifier volume lists and reslices as before.
|
||||
and transformation. Adding, removing, moving, reordering or retyping one cancels
|
||||
background processing and invalidates only `psGCodeExport`; the sliced layers
|
||||
are kept. `model_volume_list_update_supports_and_seams()` then brings the
|
||||
support and Precise Seam volumes of the print's model copy in line with the new
|
||||
model in one pass. A volume may switch between these two families, since neither
|
||||
affects object slicing; such a switch also changes the support volumes, so the
|
||||
support step is invalidated as well.
|
||||
|
||||
## Modifier slices
|
||||
A conversion to or from a part or an ordinary modifier changes the solid and
|
||||
modifier volume lists and reslices the object as before. The volume keeps its
|
||||
ID across the type change, so the region cache treats a former support or
|
||||
Precise Seam volume that became a part or modifier as new, since it was never
|
||||
cached.
|
||||
|
||||
`SeamPlacer::init()` collects the Precise Seam volumes of each object once:
|
||||
strong ones in priority order and weak ones reversed. It slices each volume
|
||||
separately with `PrintObject::slice_single_volume()`, which shares
|
||||
`slice_modifier_volumes()` with support blockers and enforcers but does not
|
||||
merge volumes, so each keeps its own priority. The result is cached per volume
|
||||
and indexed by object layer; `Layer::id()` includes raft layers, which are
|
||||
subtracted. Seam candidates are then gathered in parallel over the layers and
|
||||
read the cache without locking.
|
||||
Removing the last helper of a single-part object reslices it, as removing any
|
||||
last modifier would.
|
||||
|
||||
Objects without Precise Seam volumes follow the unchanged seam placement path.
|
||||
For objects that have them, perimeter extraction also removes consecutive
|
||||
duplicate points and the repeated closing point of each extrusion loop.
|
||||
Zero-length edges at path junctions would otherwise prevent point insertion
|
||||
there. Distinct visits to one point of a self-touching contour are kept.
|
||||
|
||||
## Finding the wall segment
|
||||
|
||||
The seam placer works on the external perimeter loops of each layer, both
|
||||
outer contours and holes, each made counter-clockwise. For every modifier
|
||||
polygon on the layer that overlaps the perimeter's bounding box, the region
|
||||
enclosed by the perimeter is clipped against the modifier polygon. The boundary
|
||||
of each intersection polygon alternates between runs that follow the perimeter
|
||||
and runs that follow the modifier outline. The wall segment is the longest
|
||||
continuous run of intersection vertices that lie on the perimeter, measured in
|
||||
vertices.
|
||||
|
||||
The fast path first finds an intersection vertex that exactly matches a
|
||||
perimeter vertex. It then walks forward and backward, expecting the adjacent
|
||||
perimeter vertex and falling back to projection when Clipper has merged or
|
||||
split collinear edges. A vertex counts as on the perimeter when its projection
|
||||
is within about 1.6 nm, which covers Clipper's rounding. If no vertex matches
|
||||
exactly, or every vertex lies on the perimeter, the general path projects all
|
||||
vertices. When every vertex is on the perimeter, the edge midpoints are checked
|
||||
instead: a modifier chord can join two perimeter vertices directly, and the
|
||||
chords split the vertex ring into runs. If no edge leaves the perimeter, the
|
||||
perimeter lies entirely inside the modifier.
|
||||
|
||||
`Polygon::point_projection()` optionally reports the edge that holds the
|
||||
projection, and every point of the segment keeps the index of its perimeter
|
||||
edge. New points are inserted on that edge. A point within 1 µm of an existing
|
||||
vertex snaps to that vertex instead.
|
||||
|
||||
## Strong modifiers
|
||||
|
||||
For a strong modifier, the target is the first point, the last point or the
|
||||
arc-length midpoint of the segment. The midpoint is projected back onto the
|
||||
original perimeter, because Clipper may have merged several perimeter edges
|
||||
into one segment edge. The target is inserted into the perimeter, and a helper
|
||||
point is inserted 1 µm before and after it. Strong modifiers are tried in
|
||||
priority order, the first valid intersection decides the seam, and weak
|
||||
modifiers are not processed for that perimeter.
|
||||
|
||||
When candidates are built, the inserted point is the only enforced candidate
|
||||
and becomes the central enforcer; every other candidate is blocked. The seam
|
||||
position modes then pick that point: Aligned and Aligned Back prefer the central
|
||||
enforcer, while Back, Random and Nearest rank enforced candidates above blocked
|
||||
ones. Alignment and random placement can still move the final position along an
|
||||
edge. After alignment, `restore_precise_seam_positions()` writes the exact point
|
||||
and its index back into every perimeter that has a strong seam. Inner walls take
|
||||
their seam from the external seam as usual, including staggering.
|
||||
|
||||
## Weak modifiers
|
||||
|
||||
Weak modifiers produce one segment per intersection polygon, so one modifier can
|
||||
mark several zones on one perimeter. All segment boundaries are inserted into
|
||||
the perimeter in order of decreasing arc length. Each insertion then leaves the
|
||||
indices of the pending, shorter ones unchanged; a point on the closing edge is
|
||||
appended rather than inserted at index zero. A helper point is added 1 µm
|
||||
outside each boundary. Random placement picks a position along the edge that
|
||||
follows a candidate. These helpers keep that edge 1 µm long at each boundary, so
|
||||
a zone cannot extend or intrude further than that. Boundaries that coincide
|
||||
share their helper points.
|
||||
|
||||
The zone types are then resolved in priority order, and the edges of enforced
|
||||
zones are subdivided into steps of at most
|
||||
`SeamPlacer::enforcer_oversampling_distance` (0.2 mm). The middle candidate of
|
||||
the longest enforced patch is therefore close to the geometric middle of the
|
||||
zone. That patch is measured in candidates, across the closing edge, regardless
|
||||
of where the contour starts; the same rule applies to painted seams.
|
||||
|
||||
Candidates first receive their type from seam painting. The weak zones then
|
||||
overwrite it, lowest priority first. Blocked and Enforced zones therefore take
|
||||
precedence over painting, and Neutral clears painting inside its zone.
|
||||
|
||||
## Unsupported geometry and warnings
|
||||
|
||||
Some modifier shapes cannot be resolved to one seam or one zone per crossing.
|
||||
They are detected cheaply and reported rather than guessed:
|
||||
|
||||
- A strong modifier that crosses a perimeter in more than one place uses only
|
||||
its first valid segment. The other crossings are ignored.
|
||||
- A modifier that crosses the whole region enclosed by the perimeter is
|
||||
detected when the modifier outline minus that region leaves more than one
|
||||
piece, none of them a hole. Its intersection holds two wall runs, and only
|
||||
one of them is used.
|
||||
- A modifier whose slice has a hole on a layer, found as a clockwise polygon in
|
||||
the flattened slice, is skipped on that layer. The flattened slice no longer
|
||||
records which hole belongs to which contour.
|
||||
- A perimeter that lies entirely inside a modifier is ignored by that modifier.
|
||||
|
||||
The conditions are atomic flags shared by all layers and objects. After all
|
||||
objects are processed, `SeamPlacer::init()` issues at most one non-critical
|
||||
warning with the ID `SlicingPreciseSeamWarning`. The warning is a single line
|
||||
that lists every cause found, because the export warnings dialog shows only the
|
||||
first line of each warning. Repeated warning events replace this notification
|
||||
instead of appending text to it.
|
||||
|
||||
## User interface
|
||||
### User interface
|
||||
|
||||
- *Add Precise Seam* in the object menu creates a Center modifier from a
|
||||
primitive or a loaded mesh. Text and SVG volumes cannot become Precise Seam
|
||||
modifiers: the menu does not offer them, and `ObjectList::set_volume_type()`
|
||||
modifiers: the menu does not offer it, and `ObjectList::set_volume_type()`
|
||||
refuses the change.
|
||||
- *Change Type* has a single *Precise Seam* entry. It converts other volumes to
|
||||
Center and keeps the mode of volumes that are already Precise Seam. The
|
||||
*Precise Seam Type* submenu appears only when every selected item is a
|
||||
Precise Seam volume, including settings rows that resolve to one. It sets the
|
||||
chosen mode on all selected volumes.
|
||||
*Precise Seam Type* submenu appears only when every selected item is a Precise
|
||||
Seam volume, including settings rows that resolve to one, and sets the chosen
|
||||
mode on all of them.
|
||||
- Each mode has its own icon in the object list and its own color in the 3D
|
||||
view, at 60% opacity: warm oranges for the strong modes, and green, red and
|
||||
gray for Enforced, Blocked and Neutral.
|
||||
- Object list drops map visible rows to volume indices while skipping hidden
|
||||
cut connectors, and they refresh the row-to-volume map of the object.
|
||||
- Precise Seam volumes have no filament, block pasting into SLA, and are exposed
|
||||
to Python plugins as `ModelVolumeType` values plus the `is_precise_seam*()`
|
||||
methods.
|
||||
view, at 60% opacity: warm orange, gold and dark orange for Center, Left and
|
||||
Right; green, red and gray for Enforced, Blocked and Neutral. The three strong
|
||||
colors are close shades of one orange because all three mark strong
|
||||
modifiers; the object list icons tell the modes apart.
|
||||
- Precise Seam volumes have no filament and cannot be pasted into SLA objects.
|
||||
Python plugins see them as `ModelVolumeType` values and through the
|
||||
`is_precise_seam*()` methods.
|
||||
|
||||
## Implementation and verification
|
||||
|
||||
- [PreciseSeam.cpp](../../src/libslic3r/GCode/PreciseSeam.cpp) implements segment
|
||||
detection, point insertion, weak-zone resolution and position restoration.
|
||||
[SeamPlacer.cpp](../../src/libslic3r/GCode/SeamPlacer.cpp) integrates it into
|
||||
candidate gathering and issues the warning.
|
||||
- [PreciseSeam.cpp](../../src/libslic3r/GCode/PreciseSeam.cpp) implements the
|
||||
modifier cache, perimeter preparation, segment extraction and binding, strong
|
||||
selection and insertion, weak-zone preparation and application, and position
|
||||
restoration. [PreciseSeam.hpp](../../src/libslic3r/GCode/PreciseSeam.hpp)
|
||||
declares the contracts; [PreciseSeamInternal.hpp](../../src/libslic3r/GCode/PreciseSeamInternal.hpp)
|
||||
exposes the binding internals to tests.
|
||||
- [SeamPlacer.cpp](../../src/libslic3r/GCode/SeamPlacer.cpp) fills the cache,
|
||||
normalizes perimeters, calls both consumers while gathering candidates,
|
||||
restores strong positions after alignment and prepares the warning text, which
|
||||
[GCode.cpp](../../src/libslic3r/GCode.cpp) issues during G-code export.
|
||||
- [Model.hpp](../../src/libslic3r/Model.hpp) defines the types and their order,
|
||||
[PrintApply.cpp](../../src/libslic3r/PrintApply.cpp) handles invalidation, and
|
||||
[PrintObjectSlice.cpp](../../src/libslic3r/PrintObjectSlice.cpp) slices the
|
||||
modifiers. [bbs_3mf.cpp](../../src/libslic3r/Format/bbs_3mf.cpp) and
|
||||
[3mf.cpp](../../src/libslic3r/Format/3mf.cpp) store them.
|
||||
[PrintObjectSlice.cpp](../../src/libslic3r/PrintObjectSlice.cpp) slices single
|
||||
volumes into structured regions. [bbs_3mf.cpp](../../src/libslic3r/Format/bbs_3mf.cpp)
|
||||
and [3mf.cpp](../../src/libslic3r/Format/3mf.cpp) store them.
|
||||
- [GUI_Factories.cpp](../../src/slic3r/GUI/GUI_Factories.cpp) and
|
||||
[GUI_ObjectList.cpp](../../src/slic3r/GUI/GUI_ObjectList.cpp) provide the menus,
|
||||
type changes and ordering.
|
||||
type changes and ordering; [3DScene.cpp](../../src/slic3r/GUI/3DScene.cpp)
|
||||
defines the colors.
|
||||
- [Segment extraction tests](../../tests/libslic3r/test_precise_seam.cpp) cover
|
||||
clipping and binding: holes and components, contour origin and reversal,
|
||||
repeated coordinates, collinear vertices and rounding, rollback and the
|
||||
diagnostic limits, the rounding fallback on synthetic and real Clipper
|
||||
fragments, contacts, and full containment including touches and sub-micron
|
||||
gaps on inclined edges and around vertices.
|
||||
- [Precise Seam tests](../../tests/fff_print/test_precise_seam.cpp) cover the
|
||||
strong positions, including a midpoint on an existing vertex or the closing
|
||||
edge. They also cover shared and coincident weak boundaries, every warning,
|
||||
and the priority order.
|
||||
consumers: strong targets in every mode, including a midpoint on an existing
|
||||
vertex or the closing edge, longest-arc selection and tie order in bed axes,
|
||||
priorities, weak boundaries that coincide or share an edge, enforced
|
||||
subdivision, whole-perimeter weak zones with painting and priorities, weak
|
||||
zones over painting's oversampled candidates, the warning type masks, usage
|
||||
tracking for the "had no effect" warning, volume sorting of strong and weak
|
||||
groups, restoration of strong points after alignment, raft layer indexing and
|
||||
structured slices. End-to-end tests slice a real object with Precise Seam
|
||||
volumes and check the outer wall starts in the exported G-code: every strong
|
||||
mode under several seam positions and with a raft, Enforced and Blocked zones,
|
||||
a modifier with a hole, and the user warning.
|
||||
- [Seam placer tests](../../tests/fff_print/test_seam_placer.cpp) cover
|
||||
enforced-patch selection independent of the contour start, fully painted
|
||||
contours, duplicate removal, and `Print::apply()` synchronization through
|
||||
type changes and restored model snapshots.
|
||||
contours, duplicate removal, and `Print::apply()` synchronization through type
|
||||
changes and restored model snapshots. The duplicate-removal test also checks
|
||||
the "had no effect" warning text prepared by `init()` for a helper that never
|
||||
reaches the loop. Further tests check that adding, moving, retyping or
|
||||
removing a Precise Seam volume invalidates only G-code export, and that seam
|
||||
painting acts only from model parts and negative volumes, including after a
|
||||
type change back to part.
|
||||
- [3MF tests](../../tests/libslic3r/test_precise_seam_3mf.cpp) cover the round
|
||||
trip of every mode and of inactive settings, attribute escaping, and which
|
||||
metadata combinations restore a seam mode.
|
||||
trip of every mode and of inactive settings, attribute escaping, and the
|
||||
metadata combinations that restore a seam mode.
|
||||
[Plugin tests](../../tests/slic3rutils/test_precise_seam_plugin.cpp) cover the
|
||||
Python bindings.
|
||||
|
||||
@@ -39,7 +39,7 @@ presets are never serialized — they have their own storage and their own lifec
|
||||
|
||||
| Location | Contents on a shipped build | Role |
|
||||
|---|---|---|
|
||||
| `resources/profiles/` | `<vendor>.opc` alone — the profile and its preset JSONs both pruned | What the app ships with; what installing copies from, and the only thing it is read for |
|
||||
| `resources/profiles/` | `<vendor>.opc` alone — the profile and its preset JSONs both pruned | What the app ships with and what installing copies from; read directly for vendors not installed |
|
||||
| `<data_dir>/system/` | `<vendor>.opc` alone, or `<vendor>.json` + `<vendor>/` after an update | What the user has installed |
|
||||
| `<data_dir>/system/` (dev build) | `<vendor>.json` + `<vendor>/` + `<vendor>.opc` written at runtime | A developer tree caches as it parses |
|
||||
| `<data_dir>/cache/wizard_profile_data.json` | The wizard's derived vendor catalog plus the stamps it was built from | Written and read by the setup wizard only; never shipped (see "The wizard's profile-data cache") |
|
||||
@@ -182,10 +182,11 @@ one startup.
|
||||
|
||||
**A vendor is loaded from where it is installed and nowhere else.** For startup that
|
||||
is `<data_dir>/system/`; resources reaches the app by being *installed* into that
|
||||
directory first, never by being loaded from. (The setup wizard is the one caller with
|
||||
a different notion of "where": it also shows vendors the user has not installed, and
|
||||
loads those from `resources/profiles` — see "The wizard's profile-data cache".) There
|
||||
is one lookup tier and one parse source:
|
||||
directory first, never by being loaded from. (The setup wizard and the Create Printer
|
||||
dialog also offer vendors the user has not installed, and load those from
|
||||
`resources/profiles`; see "The wizard's profile-data cache". The dialog's vendor-only and
|
||||
filament-only scans read a cache only where it is the whole installation, and never write
|
||||
one.) There is one lookup tier and one parse source:
|
||||
|
||||
```
|
||||
load vendor V from <data_dir>/system:
|
||||
|
||||
@@ -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,97 @@
|
||||
# Separated infills — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
An object's infill patterns are laid out from one reference point, the center
|
||||
of the object. When an object groups several parts that do not touch, every
|
||||
part cuts the same object-wide pattern at a different place, so equal parts get
|
||||
different infill. `separated_infills` lays the infill of every connected body
|
||||
out from the center of that body instead, as if the body were sliced on its own.
|
||||
|
||||
The option covers sparse infill, internal solid infill and bridges. Top and
|
||||
bottom surfaces are left to `center_of_surface_pattern`, which centers the
|
||||
Archimedean Chords and Octagram Spiral surface patterns. The option is off by
|
||||
default; with it off, or for an object made of a single body, no fill changes.
|
||||
Adaptive Cubic and Support Cubic do not depend on the option: they always fill
|
||||
each body on its own (see Octree infill).
|
||||
|
||||
## Bodies
|
||||
|
||||
`PrintObject::prepare_infill()` groups the islands of every layer (`lslices`)
|
||||
into 3D connected bodies before bridges are detected, so bridge anchors and
|
||||
printed infill share one origin. Islands on adjacent layers belong to one body
|
||||
when their slices overlap. Parts that touch or overlap form one body. Separate
|
||||
parts, disconnected islands of one mesh, and interleaved parts that never touch,
|
||||
such as chain links, each form their own. Every island stores the index of its
|
||||
body in `Layer::lslices_separated_component_ids`, and
|
||||
`PrintObject::separated_body_bboxes()` holds the bounding box of each body over
|
||||
all its layers.
|
||||
|
||||
The pass runs when a region uses separated infills, per-model surface centering
|
||||
or an octree infill pattern. It is skipped when the object has one model part
|
||||
that cannot be split, since a single body already shares the object center.
|
||||
|
||||
## Centering a fill
|
||||
|
||||
`infill_body()` matches each fill region to the island it overlaps most, among
|
||||
the islands whose bounding boxes overlap it, and the filler takes the bounding
|
||||
box of that island's body instead of the object's. The box covers every layer
|
||||
of the body, which is the box the body gets when sliced alone, so patterns that
|
||||
depend on its extent as well as its center come out the same too. Bridge
|
||||
anchoring (`Layer::generate_sparse_infill_polylines_for_anchoring()`) makes the
|
||||
same choice, so the anchors match the printed infill.
|
||||
|
||||
The patterns follow the body's box in one of two ways:
|
||||
|
||||
- Rectilinear and its variants, Line, Grid, Triangles, Tri-hexagon, Cubic,
|
||||
Quarter Cubic, Lateral Lattice, Lateral Honeycomb and the plane-path patterns
|
||||
(Hilbert Curve, Archimedean Chords, Octagram Spiral) are laid out from the
|
||||
box: they phase their lines through its center, and Hilbert Curve and the Zig
|
||||
Zag links start from its corner. `Fill::extended_object_bounding_box()`
|
||||
extends the box about its center, so it also serves a box that is not
|
||||
centered on the origin.
|
||||
- Honeycomb, 3D Honeycomb, Cross Hatch, Gyroid, TPMS-D and TPMS-FK are laid out
|
||||
from the coordinate origin, which is the object center. They return true from
|
||||
`Fill::aligned_to_origin()`, and `Fill::fill_surface()` moves each region so
|
||||
that the box center lands on the origin, fills it, and moves the paths back.
|
||||
With the default box the center is the origin, so nothing moves.
|
||||
|
||||
`is_separable_infill_pattern()` lists these patterns. The settings show the
|
||||
option only when the sparse infill pattern is one of them.
|
||||
|
||||
## Octree infill
|
||||
|
||||
Adaptive Cubic and Support Cubic take their lines from an octree, laid out from
|
||||
the center of the mesh it is built from and refined near its surfaces. An
|
||||
octree of the whole object would lay every part out from the object's center
|
||||
and refine it near the other parts, so these patterns
|
||||
(`is_octree_infill_pattern()`) always fill each body on its own, and the
|
||||
settings hide the option for them.
|
||||
|
||||
For an object of several bodies, `PrintObject::prepare_adaptive_infill_data()`
|
||||
builds one octree per body (`FillAdaptive::Octrees`) from the triangles of that
|
||||
body only, which is the octree the body gets when sliced alone. Each connected
|
||||
component of the mesh goes to the body that most of a few sampled triangles lie
|
||||
on. A sample is taken a layer height inside the solid, behind the triangle, and
|
||||
looked up in the islands of the nearest layer. Each internal bridge surface goes
|
||||
to the body of its island. The fill takes the octree of the region's body, from
|
||||
the same `infill_body()`. The octree of the whole object is built only for an
|
||||
object of a single body, or when some body received no triangles, which then
|
||||
uses it.
|
||||
|
||||
The line spacing of an octree comes from the density, line width and multiline
|
||||
count of a region, so a modifier or a part with its own density needs octrees of
|
||||
its own. `adaptive_fill_line_spacing()` gives the spacing of each region, and
|
||||
`FillAdaptive::RegionOctrees` holds one set of octrees per distinct spacing,
|
||||
shared by the regions that have it. A set is built only for the bodies its
|
||||
regions fill. The fill takes the set of its region, then the octree of its body.
|
||||
|
||||
## Patterns left out
|
||||
|
||||
Lightning grows its trees over the whole object, so moving a reference point
|
||||
cannot center it on one body. Concentric and Spiral Inset follow the outline of
|
||||
each region and need no centering.
|
||||
|
||||
Solid infill at full density spaces its lines over the extent of each region,
|
||||
so it is already independent of the other bodies. Only bridges, which keep
|
||||
their line spacing, and the plane-path solid patterns depend on the center.
|
||||
Reference in New Issue
Block a user