mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-15 20:01:31 +00:00
Merge branch 'main' into feat/generic-n-extruders
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
# Adaptive TPMS infill — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
`tpms_adaptive` grades the sparse infill of the Gyroid, TPMS-D and TPMS-FK
|
||||
patterns inside the object: the cells grow continuously from the surface
|
||||
towards the center of the object. `distance_warp`, `smooth_blend` and
|
||||
`stepped_shells` follow the distance to the nearest surface, including the top
|
||||
and bottom, like concentric shells; `lobes` follows the whole 3D shape towards
|
||||
the center of each lobe of the object; `normal_z`, `normal_y` and `normal_x`
|
||||
follow each section of the object normal to that axis, so the grading does not
|
||||
change along the axis, as suits a profile extruded along it.
|
||||
`sparse_infill_density` is the density at the surface, `tpms_interior_density`
|
||||
the density at the center, and `tpms_adaptive_gradient` picks how the density
|
||||
goes from one to the other. Only internal sparse infill is graded; the Gyroid
|
||||
Z-buckling optimization does not apply to it.
|
||||
|
||||
The design has two parts: a field built once per object, and a pattern made
|
||||
from it, warped around the center of each lobe of a body so that its cell size
|
||||
follows the field, or, in the modes following the distance to the surface,
|
||||
split into shells or blended between densities.
|
||||
|
||||
## Radial field
|
||||
|
||||
`TpmsRadialField` gives every point of an object the center of its lobe and a
|
||||
radial coordinate: 0 at the center, 1 at the surface along the ray from the
|
||||
center. `PrintObject::prepare_tpms_radial_fields()` builds it in
|
||||
`bridge_over_infill()`, next to the adaptive cubic octree, because the anchoring
|
||||
infill generated there has to match the printed infill. A field is built for
|
||||
every mode a region uses, and is shared by the regions using that mode: the
|
||||
field depends on the geometry only, the densities are applied per region in the
|
||||
fill. An object thinner than the grid cells has no body in the field; no field
|
||||
is kept then, and the infill falls back to the regular pattern. In the modes
|
||||
following the distance to the surface, the field also gives the depth of every
|
||||
point (see below).
|
||||
|
||||
A regular 3D grid of cubic cells is rasterized from the `lslices` of the layers,
|
||||
so the field follows what is printed: negative volumes, the union of
|
||||
overlapping parts and holes are taken into account, and the mesh does not need
|
||||
to be closed. A padding node around the grid is always outside. The grid is
|
||||
capped at about a million nodes, with cells no smaller than 0.5 mm.
|
||||
|
||||
- Bodies are the connected inside nodes. Each is graded on its own, so separate
|
||||
parts of one object each get their own sparse center.
|
||||
- A body is split into lobes around the local maxima of the depth, by an exact
|
||||
Euclidean distance transform (Felzenszwalb and Huttenlocher, one pass per
|
||||
axis). Two maxima are in separate lobes when the depth along the segment
|
||||
between them drops below 0.8 of the shallower one, like at the neck between
|
||||
two united spheres; maxima shallower than 0.3 of the deepest one are ignored.
|
||||
A maximum joins the first lobe whose first maximum it sees without a neck.
|
||||
The lobes are made one at a time, the remaining maxima tested against the
|
||||
first one in parallel, as a plate has a whole plane of them.
|
||||
Where the depth ties along a line or a plane, as in a tall box, the lobe's
|
||||
center is the node nearest to the middle of the tied nodes, so the center is
|
||||
in the middle of the height and not a column.
|
||||
- A point belongs to the lobe it is nearest to relative to their depths, so the
|
||||
side between two lobes is nearer to the smaller one. Near that side, within a
|
||||
tenth of that relative distance, the patterns of the lobes morph into each
|
||||
other, so the lines stay continuous. Every lobe in that range takes part, up
|
||||
to four, so the morph is also continuous where three or four lobes meet.
|
||||
- The reach of a lobe is the distance from its center to the first exit along
|
||||
24 x 48 latitude-longitude directions, smoothed twice over neighbouring
|
||||
directions in log space. Towards a neighbouring lobe it stops at twice the
|
||||
distance to the side between them, so that side is graded half way, as deep
|
||||
as a neck is, rather than as sparse as the center or as dense as the surface.
|
||||
Only the lobes whose centers are near enough to be nearer at the current
|
||||
distance are compared along a ray, so many lobes, as in a perforated plate,
|
||||
stay cheap.
|
||||
The radial coordinate of a point is its distance to the center over the reach
|
||||
in its direction. Behind a gap, as across the hole
|
||||
of a ring, the radial coordinate is above 1 and the infill keeps the surface
|
||||
density.
|
||||
- Every outside node belongs to its nearest body, so points near a surface find
|
||||
their body without a search. With a single body, all nodes belong to it.
|
||||
|
||||
In the 2D modes, every plane of nodes normal to the axis is a field of its own:
|
||||
the distance transform skips the axis, bodies, lobes and the nearest body are
|
||||
found within the plane, and the reach is sampled on a circle of 48 directions.
|
||||
A point is looked up in the two planes around it, the weights of their lobes
|
||||
interpolated along the axis, so the grading does not step between planes; a
|
||||
plane without a body uses the nearest one that has one. The planes are a cell
|
||||
apart, not a layer: where the sections change abruptly, as at a step, the
|
||||
patterns of the two planes morph into each other over that cell.
|
||||
|
||||
A distance to the nearest surface would be the obvious field, but no smooth map
|
||||
follows it. By the divergence theorem, the mean scale of a map over a body is
|
||||
fixed by its values on the surface: a map that keeps the full density along the
|
||||
whole surface, as the distance would ask under the top and bottom, has the mean
|
||||
density of the uniform infill, the sparser core being paid for by lines crowding
|
||||
along the walls. The layers of a plate at different depths would also need
|
||||
different line spacings in the same directions, which no continuous map allows
|
||||
without shearing across the plate. Following the distance needs changes of the
|
||||
topology of the lattice (see below). The radial coordinate instead grades what a
|
||||
single map can: towards one point.
|
||||
|
||||
## Warped pattern
|
||||
|
||||
The pattern is evaluated on warped coordinates:
|
||||
|
||||
TPMS(f_surface * m(t) * (p - center))
|
||||
|
||||
where `m` scales the pattern around the center of the lobe: its frequency is
|
||||
`m + t * m'` along the ray and `m` across it. `m(t)` is the mean of the target
|
||||
scale over the ball of radius `t`, `3 / t^3 * integral of s^2 * target(s) ds`, so
|
||||
the mean of the three, and with it the density, follows the gradient. The cells
|
||||
are round at the center; near the surface they are flattened, with the lines
|
||||
running parallel to it. Beyond the surface the target is the surface scale, so
|
||||
the warp extends continuously outside.
|
||||
|
||||
In the 2D modes only the coordinates within the plane are warped, and `m(t)` is
|
||||
the mean over the disc, `2 / t^2 * integral of s * target(s) ds`. Along the axis
|
||||
the pattern keeps the interior frequency: scaling it with `m` would shear the
|
||||
pattern by the distance along the axis times the gradient of `m`, without bound
|
||||
on a long object. The cells are round at the center and stretched along the
|
||||
axis near the surface. With Normal Z the layers are graded exactly, since
|
||||
the lines of a layer follow its in-plane frequencies; normal to X or Y, the
|
||||
layers near the sides are as dense as the larger of the two frequencies in the
|
||||
layer, which is the surface one.
|
||||
|
||||
Evaluating a TPMS at a frequency that varies with the position without such a
|
||||
map distorts it wherever the frequency changes, because the phase also changes
|
||||
with the gradient of the frequency times the distance from the origin. Fitting a
|
||||
smooth map to a varying isotropic scale in the least-squares sense (a Poisson
|
||||
problem per axis) cannot grade strongly: its divergence is the target scale plus
|
||||
a harmonic function pinned by the surface, which keeps the scale in the core
|
||||
near two thirds of the surface one. Following a distance exactly needs the
|
||||
lattice to change its topology, by blending lattices of different densities or
|
||||
filling shells of equal distance with them, as the modes following the distance
|
||||
to the surface do.
|
||||
|
||||
The target scale at depth `d = 1 - t`, with `S` the surface and `I` the interior
|
||||
frequency, both from each pattern's own density calibration:
|
||||
|
||||
| Gradient | Scale |
|
||||
|-------------|------------------------|
|
||||
| Linear | `1 + (I / S - 1) * d` |
|
||||
| Quadratic | `1 + (I / S - 1) * d^2`|
|
||||
| Exponential | `(I / S)^d` |
|
||||
|
||||
With a denser surface, quadratic keeps the surface density deepest and
|
||||
exponential drops fastest. A denser interior works the same way.
|
||||
|
||||
The zero level is extracted with marching squares like the regular TPMS-FK, on
|
||||
a sampling grid fixed in the fill frame like the optimized Gyroid, so that every
|
||||
region of a layer connects its lines the same way at the saddles of the pattern.
|
||||
Loops narrower than two lines (shorter than `2 * PI * spacing`) are dropped, as
|
||||
they would print as blobs. The fill works in a frame rotated by the infill
|
||||
angle, so the radial field is looked up at the point rotated back into the
|
||||
object frame, and the center rotated into the fill frame. Both use the middle of
|
||||
the layer.
|
||||
|
||||
## Modes following the distance to the surface
|
||||
|
||||
`distance_warp`, `smooth_blend` and `stepped_shells` grade by the distance to
|
||||
the nearest surface, including the top and bottom, as concentric shells do. The
|
||||
depth of a point is that distance over the distance of the deepest point of its
|
||||
body, from 0 at the surface to 1; the field keeps it for every node and
|
||||
interpolates it between them. A tall box so keeps its whole axis as sparse as
|
||||
its center, and a plate is graded through its thickness.
|
||||
|
||||
No single smooth pattern follows that depth without distortion (see above), so
|
||||
the three modes trade differently:
|
||||
|
||||
- Distance warp keeps the lobes and the warp of Lobes, but its radial profile
|
||||
comes from the depth. For every direction of a lobe, the mean depth over the
|
||||
ball along the ray is sampled at 33 radii up to the reach, then smoothed over
|
||||
the neighbouring directions like the reach. The radial coordinate is the one
|
||||
of the linear profile with the same mean depth, `t = 4/3 * (1 - mean depth)`,
|
||||
so with a linear gradient the mean cell size follows the depth exactly, and
|
||||
with the others approximately. In a sphere or a cube, where the depth falls
|
||||
linearly along every ray, it is Lobes. Elsewhere the profile changes with the
|
||||
direction, and the warp shears where neighbouring directions differ, as in
|
||||
plates and long bodies; right under the top of a long body the cells are
|
||||
sparser within the layer, the warp moving their density into the height.
|
||||
The profiles are smoothed over the directions like the reach, as sharper
|
||||
ones shear the pattern across the layer, which adds lines. A long body is so
|
||||
graded partly along its length, between Lobes and the distance.
|
||||
- Smooth blend evaluates the regular patterns of the two levels around the
|
||||
target of every point and blends them by a smoothstep over the whole gap
|
||||
between the levels, here at most 2.5 times apart. The density follows the
|
||||
depth without steps, but where two lattices blend, part of their lines run
|
||||
along the blend, so fewer levels print fewer extra lines. From 25% to 5%,
|
||||
three levels print about 0.45 of the uniform infill in a deep core whose
|
||||
levels alone would print 0.3; levels 1.5 times apart print 0.6 to 0.7, and a
|
||||
single blend from the surface to the interior 0.6.
|
||||
- Stepped shells split each region of a layer into shells and fill every shell
|
||||
with the regular pattern at its density. The densities are levels from the
|
||||
surface to the interior density at most 1.5 times apart, five from 25% to 5%,
|
||||
and a point takes the level nearest to its target on that geometric scale.
|
||||
The shells are traced by marching squares of the continuous level over the
|
||||
layer on a 0.5 mm grid fixed in the object, so every region of a layer gets
|
||||
the same shells, and clipped to the region. Each shell is shrunk by half a
|
||||
line, like a filled region, and its regular filler connects its lines along
|
||||
that boundary, so the connections of two neighbouring shells lie side by side
|
||||
instead of on top of each other. The pattern is never
|
||||
distorted, but its lines end at every shell, and thin parts get thin shells.
|
||||
The connections add lines: in the core of a 60 mm cube, about a third more
|
||||
than the target.
|
||||
|
||||
Stepped shells and Smooth blend need neither lobes nor reaches, which are not
|
||||
built for them.
|
||||
|
||||
## Constraints
|
||||
|
||||
- With `tpms_adaptive` disabled, or for other patterns, the fill parameters are reset
|
||||
to their defaults, so they neither change the infill nor split fill batches.
|
||||
- `Layer::get_sparse_infill_max_void_area()` uses the sparser of the two
|
||||
densities, as the voids at the center are that large.
|
||||
- At a sparse infill density of 100% the sparse infill is turned into solid
|
||||
infill, so there is nothing to grade: the options are hidden and no field is
|
||||
built.
|
||||
- The adaptive options invalidate `posPrepareInfill`, which rebuilds the field
|
||||
and the anchoring infill.
|
||||
- An elongated body without a neck has one center, so its far ends are graded
|
||||
as the outer part of the body, and the warp shears where the reach changes
|
||||
quickly with the direction. A concave body, like an L, may be split into lobes
|
||||
where its maxima cannot see each other in a straight line.
|
||||
- Across a ray, the scale is the mean of the gradient from the center, so the
|
||||
layers right under the top and above the bottom are sparser than the surface
|
||||
density in their middle, and a plate is graded from its middle outwards rather
|
||||
than through its thickness.
|
||||
@@ -0,0 +1,172 @@
|
||||
# Center of mass markers — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
The "Center of mass" item of the canvas toolbar menu, in the bottom left corner of the 3D view, marks
|
||||
where the mass of the plate, of each object instance and of each body of an assembly is centered,
|
||||
in Prepare and in Preview. It helps judge how parts will rest on the plate, for instance whether a
|
||||
tall or leaning part could tip. It is a view setting: it changes nothing in the model, the slice or
|
||||
the project file, and it does not reach plate thumbnails. The choice is kept in the app config as
|
||||
`show_center_of_mass`, off by default. The assembly view and the Design tab have no markers.
|
||||
|
||||
Three kinds of marker share one shape, a sphere whose octants alternate between two colors:
|
||||
|
||||
- each plate, black and white, for everything on it;
|
||||
- each object instance, light blue and white;
|
||||
- each body of an assembly, red and yellow;
|
||||
- in Preview, the supports and raft of each object instance, green and black.
|
||||
|
||||
A click on a marker opens a box beside it with the weight and volume of what it stands for, where its
|
||||
center lies in that thing's bounding box and the size of the box, and its moments of inertia about
|
||||
axes through the center parallel to x, y and z.
|
||||
|
||||
An assembly is an object of several parts or with negative volumes. Its bodies are the connected
|
||||
solids its parts make once united, the bodies the separated infills option centers its infill on
|
||||
(see separated-infills.md): parts that overlap or touch are one body, parts apart are separate
|
||||
bodies. An object of one body, and every object of a single part, has no body markers, as its object
|
||||
marker says it all.
|
||||
|
||||
Mass is volume times density, the `filament_density` of the filament that prints it, or 1.245 g/cm³
|
||||
(`DEFAULT_FILAMENT_DENSITY`) for a filament without one. Prepare has the model only, so it takes each
|
||||
part as a solid of the density of its filament, the part's own or else its object's. It reads each
|
||||
filament's density from the filament's selected preset, edits not yet saved included, as slicing does:
|
||||
the plater's own config holds the values of the filament edited last only. Preview has
|
||||
what will be printed, so its markers come from the toolpaths, whose mass depends on walls, infill
|
||||
and flow as well. There the solid markers are for what is printed up to the top layer the layer
|
||||
slider shows: for the plate with brim, raft and supports, where the weight rests at that point of
|
||||
the print; for an object or a body, its own extrusions. Each has a faded twin for the same at the
|
||||
end of the print, so the slider shows the weight moving toward where it ends, and at the top layer
|
||||
the two meet. In Prepare the plate has the model alone, as brim, raft and supports exist only once
|
||||
sliced.
|
||||
|
||||
## Prepare: from the meshes
|
||||
|
||||
An object of one part takes the mass properties of its mesh at unit density, times its density, from
|
||||
`its_mass_properties()`, which handles a mesh in a single pass. Each triangle and a fixed vertex of
|
||||
the mesh span a tetrahedron whose signed volume is `V = a · (b × c) / 6`, with `a`, `b`, `c` taken
|
||||
relative to that vertex. By the divergence theorem these volumes add up to the volume of a closed
|
||||
mesh, their volume-weighted centroids to its center of mass, and their second moments
|
||||
`V (a aᵀ + b bᵀ + c cᵀ + s sᵀ) / 20`, with `s = a + b + c`, to its own. Shells facing inward, such as
|
||||
a cavity, subtract themselves, and flipping every triangle changes nothing. The sums are kept in
|
||||
double precision and relative to a vertex of the mesh rather than the origin, which keeps them exact
|
||||
for meshes far from it. The result keeps the spread of the mass about its center,
|
||||
`(x - c)(x - c)ᵀ` averaged over the mass, from which the moments of inertia follow.
|
||||
|
||||
The CGAL routines that look alike do not compute this. `CGAL::centroid` weighs tetrahedra by their
|
||||
unsigned volume, so it fails on cavities and on any shell that is not star-shaped from the fan's
|
||||
apex; over triangles it returns the centroid of the surface, and over points the average of the
|
||||
vertices, which depends on the tessellation. `CGAL::barycenter` with the signed volumes as weights
|
||||
gives the same answer, but only after copying every tetrahedron into a vector of weighted points,
|
||||
and takes two and a half times as long.
|
||||
|
||||
A mesh's result is in its own coordinates. Each `GLVolume` maps it to the world with its world
|
||||
matrix `M`, and weighs it by the volume times the absolute determinant of that matrix: a center of
|
||||
mass moves with any affine map, and the spread becomes `L S Lᵀ` for the linear part `L` of `M`, so no
|
||||
mesh is ever transformed. The `GLVolume`'s matrices, rather than the
|
||||
model's, let the markers follow an object while it is dragged, before the model is updated. Results
|
||||
are cached by `ModelVolume` id; a `ModelVolume` takes a new id whenever its mesh changes, which is
|
||||
the rule `reload_scene()` relies on to rebuild a `GLVolume`'s geometry, so a cached result never
|
||||
outlives its mesh.
|
||||
|
||||
An assembly's parts overlap or touch, which the mesh formula would count twice, so `solid_bodies()`
|
||||
slices them instead, in the object's coordinates. It cuts the height into 500 slabs, 100 while a part
|
||||
is dragged, slices every part and negative volume at the middle of each slab, unites the parts and
|
||||
cuts the negative volumes away, and links the islands of neighboring slabs that overlap into bodies
|
||||
with `connected_bodies()`. Each island adds a prism of the slab's thickness at the density: its
|
||||
area, and its first and second moments of area, from the same sums over the outline as the area,
|
||||
with the slab's height for z. Where parts of different densities overlap, the later volume of the object counts, as
|
||||
slicing clips every part by the parts after it; each part then weighs the region it prints, which is
|
||||
credited to the island holding it. Each body also keeps the convex hull of its islands and the height
|
||||
they span, whose corners, once transformed, give its bounding box, tight while the instance turns
|
||||
about z only. The object is the sum of its bodies, its box that of its parts, as the object's size
|
||||
shows it, and each plate the sum of the object instances `PartPlateList::find_instance()` puts on it,
|
||||
so that an instance on no plate counts in none. The bodies are cached by `ModelObject` id with the volumes, types, densities and
|
||||
transformations they were sliced from.
|
||||
|
||||
## Preview: from the toolpaths
|
||||
|
||||
`GCodeProcessor` sums the masses while it processes the G-code, in the same pass that builds the
|
||||
moves, and leaves them in `GCodeProcessorResult`; `GCodeViewer` keeps a copy of them when it loads a
|
||||
result. Nothing is stored per move.
|
||||
|
||||
Each extrusion weighs the volume of filament its E extrudes times the density of the filament that
|
||||
extrudes it, so a print of several materials weighs each as it is. Flow ratio, line widths, ironing
|
||||
and purging into infill all count
|
||||
as printed. Its mass spreads evenly along the segment the bead's center runs, half the layer height
|
||||
below the nozzle, in the frame of the stored moves: plate offset added, Z offset removed. Such a
|
||||
segment from `a` to `b` adds `m (a + b) / 2` to the moments and `m (a² + a b + b²) / 3` to the second
|
||||
moments along each axis, and its box widened by half the bead's height to the bounding box; not by
|
||||
half its width, which the processor only estimates, so that a box runs along the walls' center lines. Arcs are already split into segments by the processor. Walls, infill, top and bottom surfaces, ironing
|
||||
and gap fill make the parts. The brim and the support roles, the raft among them, count only in what
|
||||
the plate prints. The skirt, the prime tower and custom G-code count nowhere.
|
||||
|
||||
The plate takes every extrusion, so it needs nothing more. The objects and bodies need the sliced
|
||||
objects, which the G-code does not describe, so the G-code export hands the processor a locator
|
||||
built from the `Print`; G-code opened from a file, or from a project sliced earlier, has no `Print`
|
||||
behind it, and so shows the plate alone. Object labels would not do: profiles turn the four kinds
|
||||
Orca writes on and off in every combination, and none of them tells the bodies apart.
|
||||
|
||||
The locator numbers the object instances and, for each assembly, the bodies of every instance. It
|
||||
takes the bodies `PrintObject::prepare_infill()` found for separated infills, or, when that option
|
||||
did not need them, links the islands (`Layer::lslices`) of neighboring layers into bodies with the
|
||||
same `connected_bodies()`. For each extrusion of a part, it finds the first layer printed at or above
|
||||
its height, as spiral vase rises through each layer, and the island holding it with an
|
||||
`IslandLocator`, the one `solid_bodies()` credits its regions with: by the island's box, widened by
|
||||
1 mm for walls reaching past it, with a polygon test only where boxes overlap, and the nearest
|
||||
outline where none holds the point. The boxes of one layer's islands say nothing of the other
|
||||
instances, so an instance whose widened box reaches another's, as copies placed side by side do,
|
||||
tests the outlines alone, and outside them the nearest outline of all such instances wins. The island
|
||||
gives both the instance and the body. The island found last is tried first, as extrusions mostly follow each other on one
|
||||
island. Brim, raft and supports lie outside the islands. The brim counts in the plate only; a support
|
||||
or raft extrusion goes to the instance whose footprint, the box of its widened islands, holds it, the
|
||||
one whose center is nearest among several, or else the nearest footprint, as supports stand below and
|
||||
around their object.
|
||||
|
||||
Each mass holds, for each layer id, the running total of what is printed up to that layer, the last
|
||||
of which is the faded marker's, so the solid marker for any slider position is a single lookup. The layer ids are those
|
||||
the moves carry, which are also the layers of libvgcode and of the slider; in a print by object they
|
||||
follow the order of printing, so the solid markers show the objects printed so far as they are.
|
||||
|
||||
## Drawing
|
||||
|
||||
`smooth_sphere()` with a resolution divisible by four leaves every triangle within one octant, so it
|
||||
splits into two models drawn with the `gouraud_light` shader in each kind's two colors. The radius is
|
||||
9 pixels for the plate, 7 for the objects, 6 for the supports and 5 for the bodies, scaled like the canvas toolbar for the
|
||||
display's DPI and kept constant on screen through the camera's inverse zoom. They are drawn in that
|
||||
order, so that markers at one place show as rings. The faded markers are the same spheres at 40%
|
||||
opacity, drawn before all the solid ones, which show over them where both meet.
|
||||
|
||||
The centers usually lie inside the objects, so the markers are drawn without the depth test and show
|
||||
through the objects and anything in front of them. Back face culling keeps the far half of a sphere
|
||||
from covering the near one. They are drawn after the ambient occlusion pass, which would otherwise
|
||||
darken them as the surface behind them, and before FXAA, which smooths their edges.
|
||||
|
||||
The markers are part of the cached scene, so toggling them, or changing a filament's density while
|
||||
they are shown, marks the scene dirty, and moving the layer slider redraws the scene with the solid
|
||||
markers where they belong. In Prepare they are hidden while any gizmo other than Move, Rotate, Scale
|
||||
and Lay on face is open, since the others work on the surface a marker would cover, and a hidden
|
||||
object has no markers.
|
||||
|
||||
## Details
|
||||
|
||||
The markers drawn last are kept, and a left click is tested against them before it selects: each
|
||||
center and a point a radius to its right are projected to the screen, and the click hits a marker
|
||||
within that distance. The solid markers are tested before the faded ones and the smaller kinds
|
||||
before the larger, the order in which they cover each other. A hit opens the details of that marker
|
||||
and keeps the click from changing the selection; a click anywhere else closes them. The box is an
|
||||
ImGui window beside the marker, redrawn with the overlay from the markers of the last scene, so it
|
||||
follows a dragged object, and in Preview the layer slider. It closes when its marker is gone, or when
|
||||
the number of markers of its kind changes, as then it may stand for something else.
|
||||
|
||||
Its title says what the marker stands for: the plate, an object, an assembly or a part, the body of
|
||||
an assembly. The G-code export lists the object instances for the processor, marking assemblies, as
|
||||
it hands it the locator.
|
||||
|
||||
Each marker carries its sums: mass, volume, first moments and the second moments about the origin
|
||||
along each axis, `Σ m x²`, `Σ m y²` and `Σ m z²`, which add up from parts to objects to plates. The
|
||||
moment of inertia about the axis through the center parallel to x is then
|
||||
`m (σy² + σz²)`, with `σ² = Σ m x² / m - c²` along each axis, and likewise for y and z. Masses are
|
||||
kept in mg, volume times density in g/cm³, and shown in g, volumes in cm³ and moments of inertia in
|
||||
g·mm². In Preview the box tells the finished print from what is printed up to the layer shown, the
|
||||
two weighing differently, and both are placed in the bounding box of everything the marker holds
|
||||
by the end.
|
||||
@@ -0,0 +1,135 @@
|
||||
# Mesh repair — High Level Design
|
||||
|
||||
## Purpose and scope
|
||||
|
||||
Mesh repair turns a broken triangle mesh into closed, outward oriented shells that bound a solid, so that the
|
||||
mesh slices predictably and the other mesh tools (booleans, cut, painting) get valid input. It runs on demand:
|
||||
the *Fix Model* command of the context menu of an object or part and of the warning icon in the object list, *Fix
|
||||
All* and *Fix All Plates* in the context menu of a plate, the prompt the Cut tool shows when a cut leaves open edges,
|
||||
the *Repair and import* choice of texture import, and the `--repair` option of the command line. It is built on CGAL
|
||||
and is the only repair the application has, on every platform.
|
||||
|
||||
`repair_mesh()` in `src/libslic3r/MeshRepair.cpp` does the work on one `TriangleMesh`, and `ModelVolume::repair()`
|
||||
applies it to a part: it replaces the mesh only when the repair changed it, and remaps or drops the painting of a
|
||||
replaced mesh. The GUI side, `fix_model_with_cgal_gui()` in `src/slic3r/Utils/FixModelByCgal.cpp`, runs it on a
|
||||
worker thread for each volume of an object, or for one volume, behind a progress dialog. `repair_mesh()` reports its
|
||||
progress between stages through a callback, which cancels the repair by throwing.
|
||||
|
||||
## What the repaired mesh is
|
||||
|
||||
The repair keeps what the slicer would print from the input. The slicer fills a layer where the winding number of
|
||||
the input surface is not zero (`MeshSlicingParams::SlicingMode::Regular` uses Clipper's nonzero fill rule), so:
|
||||
|
||||
- Shells crossing each other print as their union. The repair keeps them as separate shells, as the parts of an
|
||||
object are kept, and makes each of them an outward oriented solid.
|
||||
- A shell inside a solid that is oriented like it adds nothing, and the repair removes it.
|
||||
- A shell inside a solid that is oriented inward is a cavity, and stays one.
|
||||
- A whole mesh turned inside out prints as a solid, and the repair turns it outward.
|
||||
|
||||
On top of that, it closes holes and cracks, gives inconsistently oriented triangles the orientation of their
|
||||
neighbours, drops degenerate, duplicate and stray flat geometry, and removes small self-intersections inside a shell,
|
||||
such as slivers and folds. Where none of this applies, it reports the mesh as unmodified and the caller keeps the
|
||||
original mesh, its painting and its object id.
|
||||
|
||||
A volume is repaired in place and is never split into several parts: splitting by connectivity would turn a cavity
|
||||
into a separate solid part and fill it. Each solid of the result is a shell of its own, so *Split to parts* still
|
||||
separates them. Open pieces of the input whose borders meet exactly, which are pieces of one surface rather than
|
||||
solids, come out as the one shell they form.
|
||||
|
||||
## Pipeline
|
||||
|
||||
The input is treated as a triangle soup. Every stage after the first only runs on what the previous ones could
|
||||
not settle, so a valid mesh passes through quickly.
|
||||
|
||||
### Valid parts first
|
||||
|
||||
Parts of the input that are connected by vertex indices and are already closed, consistently oriented 2-manifolds
|
||||
skip the soup stages and go straight to the shell stages. Merging their points with coincident points of other
|
||||
parts would only make non-manifold junctions out of solids that touch, for example parts of an assembly exported
|
||||
together or voxels written as separate cubes.
|
||||
|
||||
### Soup clean-up
|
||||
|
||||
The rest of the soup has its coincident points merged. Triangles with repeated corners are dropped. Coincident
|
||||
triangles of opposite orientation are a wall between two solids touching face to face, so they cancel in pairs;
|
||||
of the remaining copies of a triangle one is kept. A connected region that would vanish entirely through
|
||||
cancelling is a two-sided export instead (every face written in both orientations), and keeps one copy of each
|
||||
triangle.
|
||||
|
||||
Open edges then get their vertices welded within a tolerance of 1e-4 of the bounding box diagonal, which closes the
|
||||
cracks some exporters leave between triangles that should share corners. A vertex is never welded further than a
|
||||
quarter of its shortest edge, so welding cannot collapse fine detail. A soup that stays mostly open after welding has
|
||||
cracks wider than that tolerance, and is welded again with ten and then a hundred times the tolerance.
|
||||
|
||||
### Work units
|
||||
|
||||
The soup is cut into units repaired independently and in parallel: its connected parts, except that open parts whose
|
||||
bounding boxes touch share a unit, so that cracks and T-junctions between separately exported patches still close.
|
||||
|
||||
### Building the mesh
|
||||
|
||||
A unit whose edges are each shared by at most two triangles, running in opposite directions, and whose vertices each
|
||||
have a single fan of triangles, becomes a `Surface_mesh` directly. Any other unit is oriented first. Triangles are
|
||||
flipped so that neighbours across an edge shared by two of them agree, each part keeping the orientation the
|
||||
majority of its area had, which keeps inward oriented cavities inward. Edges where that is impossible or that more
|
||||
than two triangles share are cut, and each fan of triangles around a vertex gets its own copy of the vertex. If that
|
||||
still leaves an invalid mesh, CGAL's `orient_polygon_soup()` orients the unit instead, and the parts it flipped
|
||||
against their majority are reversed back. The linear pass exists because `orient_polygon_soup()` is several times
|
||||
slower on large soups that need only a few fixes.
|
||||
|
||||
### Closing
|
||||
|
||||
Compatible borders are stitched, parts that can be stitched once reversed are merged, and borders still open are
|
||||
snapped onto each other within the weld tolerance, which closes T-junctions. Exactly degenerate faces are removed.
|
||||
|
||||
Holes are filled last. Planar border loops that face each other in one plane, one inside the other, bound a single
|
||||
region, such as a missing annulus at the end of a tube or a cap with features standing on it. Such a group is
|
||||
triangulated at once with a 2D constrained Delaunay triangulation that fills only the faces at odd nesting depth,
|
||||
so the openings inside the region stay open. The patch is used only if it closes every loop with the orientation
|
||||
of the triangles around it. Every other loop is filled on its own with `triangulate_hole()`, which uses a 2D
|
||||
constrained Delaunay triangulation when the loop is planar. Otherwise it searches the best triangulation among all
|
||||
triangles over the loop for loops of up to 100 edges, and among the triangles of a 3D Delaunay triangulation for
|
||||
longer ones, as the full search is cubic in the length of the loop. Beyond 200 edges the full search is not even
|
||||
used as a fallback, and a fan around the loop's centroid closes a loop nothing else could.
|
||||
|
||||
The closed unit is split into its shells. The largest stays in place and the others are copied out, as converting
|
||||
the whole mesh again costs seconds on large models.
|
||||
|
||||
### Shells
|
||||
|
||||
The shells of all units, together with the valid parts set aside at the start, are handled independently and in
|
||||
parallel. Shells enclosing no volume, which hole filling makes out of stray flat patches, are dropped.
|
||||
|
||||
Self-intersections inside a shell are removed locally with `experimental::remove_self_intersections()`, which
|
||||
remeshes the region around them. It is used only for local defects: a shell whose intersections involve more than
|
||||
5 % of its faces is left as it is, and a result that changes the enclosed volume by more than 0.1 % is undone. On
|
||||
heavily overlapping ornamental models the remeshing otherwise cuts parts down to slivers, while on genuine defects
|
||||
it changes the volume by thousandths of a percent. Overlaps left in place slice correctly. Remeshing can cut
|
||||
small pieces off a shell, so a shell it changed is split again and its pieces enclosing no volume are dropped.
|
||||
|
||||
Shells crossing others are found through their bounding boxes and an AABB tree per shell, and are made outward
|
||||
oriented solids. For every other shell the winding number just outside it is summed from the orientation of the
|
||||
shells containing it, tested from a point on its largest face. The shell bounds the solid only if the winding
|
||||
number is zero on exactly one of its sides; it is then oriented so that the solid is inside, or dropped otherwise.
|
||||
|
||||
Crossing shells are not united. Slicing unites them anyway, uniting them exactly costs tens of seconds on models
|
||||
made of many overlapping pieces, and it would fuse parts that *Split to parts* should keep apart.
|
||||
|
||||
## Robustness
|
||||
|
||||
The repair avoids `experimental::autorefine_and_remove_self_intersections()` and corefinement, which can crash on
|
||||
messy input, and uses only steps whose failures it can detect. A step that fails keeps its input. CGAL precondition
|
||||
failures surface as `repair_mesh()` returning false with the message as the error. The output is written in float,
|
||||
as every mesh in the application is.
|
||||
|
||||
The result does not depend on thread scheduling. `libslic3r_cgal` is built with `CGAL_LINKED_WITH_TBB`. Units and
|
||||
shells are processed in parallel, but each on its own data, and their results are gathered in a fixed order;
|
||||
sorts compare complete keys, and the detected self-intersections are put in order before they are used. Snapping
|
||||
stays sequential, as its parallel version gives different results from run to run. Holes are filled with the full
|
||||
search wherever it is affordable, because the search among Delaunay triangles can choose between equally good fills
|
||||
differently from run to run, so only non-planar holes of more than 100 edges may still vary. On some inputs
|
||||
`experimental::remove_self_intersections()` itself places a few vertices differently from run to run.
|
||||
|
||||
## Verification
|
||||
|
||||
`tests/libslic3r/test_mesh_repair.cpp` covers each kind of defect above on small meshes with known volumes.
|
||||
@@ -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 with `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