mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 16:21:14 +00:00
Extend Separated Infills (#16274)
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
# 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.
|
||||
|
||||
## 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.
|
||||
+29
-23
@@ -29,6 +29,7 @@
|
||||
#include "ExtrusionEntity.hpp"
|
||||
#include "Fill.hpp"
|
||||
#include "libslic3r/Fill/FillBase.hpp"
|
||||
#include "FillAdaptive.hpp"
|
||||
#include "FillRectilinear.hpp"
|
||||
#include "FillLightning.hpp"
|
||||
#include "FillConcentricInternal.hpp"
|
||||
@@ -926,7 +927,6 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
params.lateral_lattice_angle_2 = region_config.lateral_lattice_angle_2;
|
||||
params.infill_overhang_angle = region_config.infill_overhang_angle;
|
||||
params.center_of_surface_pattern = region_config.center_of_surface_pattern;
|
||||
params.separated_infills = region_config.separated_infills;
|
||||
if (params.pattern == ipLockedZag) {
|
||||
params.infill_lock_depth = scale_(region_config.infill_lock_depth);
|
||||
params.skin_infill_depth = scale_(region_config.skin_infill_depth);
|
||||
@@ -999,6 +999,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
// (which would unnecessarily split fill batching).
|
||||
// Stored on SurfaceFillParams; copied to FillParams during conversion.
|
||||
params.gyroid_optimized = (params.pattern == ipGyroid) && region_config.gyroid_optimized;
|
||||
// Orca: Likewise separated_infills only where it can move the pattern.
|
||||
params.separated_infills = region_config.separated_infills && is_separable_infill_pattern(params.pattern) &&
|
||||
params.extrusion_role != erTopSolidInfill && params.extrusion_role != erBottomSurface;
|
||||
|
||||
if (params.extrusion_role == erInternalInfill) {
|
||||
params.angle = calculate_infill_rotation_angle(layer.object(), layer.id(), region_config.infill_direction.value,
|
||||
@@ -1271,29 +1274,28 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
|
||||
// Orca: Anchors and printed infill must share the same body origin. Keep the choice
|
||||
// here so per-model surface centering and separated sparse infill cannot drift apart.
|
||||
static BoundingBox infill_bounding_box(const Layer &layer, const SurfaceFill &fill, const ExPolygon &expoly, BoundingBox bbox)
|
||||
// Returns the connected body the fill region is laid out on, or -1 to keep the object's origin.
|
||||
static int infill_body(const Layer &layer, const SurfaceFill &fill, const ExPolygon &expoly)
|
||||
{
|
||||
const auto ¶ms = fill.params;
|
||||
const auto &config = layer.regions()[fill.region_id]->region().config();
|
||||
const bool external = params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface;
|
||||
const bool per_model = external && params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model &&
|
||||
const bool per_model = (params.extrusion_role == erTopSolidInfill || params.extrusion_role == erBottomSurface) &&
|
||||
params.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model &&
|
||||
(params.pattern == ipArchimedeanChords || params.pattern == ipOctagramSpiral);
|
||||
const bool separate = !external && params.separated_infills &&
|
||||
(is_separable_infill_pattern(params.pattern) || !config.solid_infill_rotate_template.value.empty() ||
|
||||
!config.sparse_infill_rotate_template.value.empty());
|
||||
if (per_model || separate) {
|
||||
double best_overlap = 0.;
|
||||
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_bboxes.size(); ++i) {
|
||||
int body = -1;
|
||||
if (per_model || params.separated_infills || is_octree_infill_pattern(params.pattern)) {
|
||||
const BoundingBox box = get_extents(expoly);
|
||||
double best_overlap = 0.;
|
||||
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_ids.size(); ++i) {
|
||||
if (! layer.lslices_bboxes[i].overlap(box))
|
||||
continue;
|
||||
const double overlap = area(intersection_ex(layer.lslices[i], expoly));
|
||||
if (overlap > best_overlap) {
|
||||
best_overlap = overlap;
|
||||
const Point center = layer.lslices_separated_component_bboxes[i].center();
|
||||
bbox = layer.object()->bounding_box();
|
||||
bbox.translate(center.x(), center.y());
|
||||
body = int(layer.lslices_separated_component_ids[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return bbox;
|
||||
return body;
|
||||
}
|
||||
|
||||
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
|
||||
@@ -1318,7 +1320,7 @@ void export_group_fills_to_svg(const char *path, const std::vector<SurfaceFill>
|
||||
#endif
|
||||
|
||||
// friend to Layer
|
||||
void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator)
|
||||
void Layer::make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator)
|
||||
{
|
||||
for (LayerRegion *layerm : m_regions)
|
||||
layerm->fills.clear();
|
||||
@@ -1351,7 +1353,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->fixed_angle = surface_fill.params.fixed_angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
if (surface_fill.params.pattern == ipConcentricInternal) {
|
||||
@@ -1443,8 +1445,10 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
params.can_reverse = false;
|
||||
for (ExPolygon& expoly : surface_fill.expolygons) {
|
||||
|
||||
// Orca: Reuse the body origin used for bridge anchoring, resetting it for each surface.
|
||||
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
|
||||
// Orca: Reuse the body box and octree used for bridge anchoring, resetting them for each surface.
|
||||
const int body = infill_body(*this, surface_fill, expoly);
|
||||
f->set_bounding_box(body >= 0 ? this->object()->separated_body_bboxes()[body] : bbox);
|
||||
f->adapt_fill_octree = octrees ? octrees->get(body) : nullptr;
|
||||
|
||||
f->no_overlap_expolygons = intersection_ex(surface_fill.no_overlap_expolygons, ExPolygons() = {expoly}, ApplySafetyOffset::Yes);
|
||||
if (params.symmetric_infill_y_axis) {
|
||||
@@ -1512,7 +1516,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
|
||||
* - For lightning/adaptive patterns, the respective generators are wired so their
|
||||
* polylines match the final infill layout.
|
||||
*/
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator) const
|
||||
Polylines Layer::generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator) const
|
||||
{
|
||||
LockRegionParam skin_inner_param;
|
||||
std::vector<SurfaceFill> surface_fills = group_fills(*this, skin_inner_param);
|
||||
@@ -1570,7 +1574,7 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
|
||||
f->z = this->print_z;
|
||||
f->angle = surface_fill.params.angle;
|
||||
f->fixed_angle = surface_fill.params.fixed_angle;
|
||||
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
|
||||
const FillAdaptive::Octrees *octrees = surface_fill.params.pattern == ipSupportCubic ? support_fill_octrees : adaptive_fill_octrees;
|
||||
f->print_config = &this->object()->print()->config();
|
||||
f->print_object_config = &this->object()->config();
|
||||
|
||||
@@ -1617,8 +1621,10 @@ Polylines Layer::generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Oc
|
||||
params.extrusion_role = surface_fill.params.extrusion_role;
|
||||
|
||||
for (ExPolygon &expoly : surface_fill.expolygons) {
|
||||
// Orca: Match the per-body origin of make_fills() before generating physical anchors.
|
||||
f->set_bounding_box(infill_bounding_box(*this, surface_fill, expoly, bbox));
|
||||
// Orca: Match the per-body box and octree of make_fills() before generating physical anchors.
|
||||
const int body = infill_body(*this, surface_fill, expoly);
|
||||
f->set_bounding_box(body >= 0 ? this->object()->separated_body_bboxes()[body] : bbox);
|
||||
f->adapt_fill_octree = octrees ? octrees->get(body) : nullptr;
|
||||
// Spacing is modified by the filler to indicate adjustments. Reset it for each expolygon.
|
||||
f->spacing = surface_fill.params.spacing;
|
||||
surface_fill.surface.expolygon = std::move(expoly);
|
||||
|
||||
@@ -25,6 +25,7 @@ public:
|
||||
// pattern is placed on top of previous layers
|
||||
bool use_bridge_flow() const override { return false; }
|
||||
bool is_self_crossing() override { return false; }
|
||||
bool aligned_to_origin() const override { return true; }
|
||||
|
||||
protected:
|
||||
void _fill_surface_single(
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/ExPolygon.hpp"
|
||||
#include "FillBase.hpp"
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <Eigen/Geometry>
|
||||
@@ -37,6 +38,27 @@ struct Octree;
|
||||
struct OctreeDeleter { void operator()(Octree *p); };
|
||||
using OctreePtr = std::unique_ptr<Octree, OctreeDeleter>;
|
||||
|
||||
// Orca: One octree per body (see Layer::lslices_separated_component_ids), and one of the whole object
|
||||
// for objects of a single body or with a body that has none of its own.
|
||||
struct Octrees
|
||||
{
|
||||
OctreePtr object;
|
||||
std::vector<OctreePtr> bodies;
|
||||
|
||||
// A body without an octree, or body -1, uses the object's, or any body's when the object has none.
|
||||
Octree *get(int body) const
|
||||
{
|
||||
if (body >= 0 && size_t(body) < bodies.size() && bodies[body])
|
||||
return bodies[body].get();
|
||||
if (object)
|
||||
return object.get();
|
||||
for (const OctreePtr &octree : bodies)
|
||||
if (octree)
|
||||
return octree.get();
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate line spacing for
|
||||
// 1) adaptive cubic infill
|
||||
// 2) adaptive internal support cubic infill
|
||||
|
||||
@@ -128,6 +128,9 @@ Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
{
|
||||
// Perform offset.
|
||||
Slic3r::ExPolygons expp = offset_ex(surface->expolygon, float(scale_(this->overlap - 0.5 * this->spacing)));
|
||||
// Orca: Separated infills move the box center onto each body; origin-aligned patterns follow it.
|
||||
const Point shift = this->aligned_to_origin() && ! empty(this->bounding_box) ? this->bounding_box.center() : Point::Zero();
|
||||
translate(expp, -shift);
|
||||
// Create the infills for each of the regions.
|
||||
Polylines polylines_out;
|
||||
for (size_t i = 0; i < expp.size(); ++ i)
|
||||
@@ -137,6 +140,8 @@ Polylines Fill::fill_surface(const Surface *surface, const FillParams ¶ms)
|
||||
_infill_direction(surface),
|
||||
std::move(expp[i]),
|
||||
polylines_out);
|
||||
for (Polyline &pl : polylines_out)
|
||||
pl.translate(shift);
|
||||
return polylines_out;
|
||||
}
|
||||
|
||||
@@ -1591,13 +1596,18 @@ BoundaryInfillGraph create_boundary_infill_graph(const Polylines &infill_ordered
|
||||
// The extended bounding box of the whole object that covers any rotation of every layer.
|
||||
BoundingBox Fill::extended_object_bounding_box() const
|
||||
{
|
||||
BoundingBox out = bounding_box;
|
||||
// Orca: Extend about the box center, which separated infills move off the origin.
|
||||
const Point c = this->bounding_box.center();
|
||||
BoundingBox out = this->bounding_box;
|
||||
out.translate(-c.x(), -c.y());
|
||||
out.merge(Point(out.min.y(), out.min.x()));
|
||||
out.merge(Point(out.max.y(), out.max.x()));
|
||||
|
||||
// The bounding box is scaled by sqrt(2.) to ensure that the bounding box
|
||||
// covers any possible rotations.
|
||||
return out.scaled(sqrt(2.));
|
||||
out = out.scaled(sqrt(2.));
|
||||
out.translate(c.x(), c.y());
|
||||
return out;
|
||||
}
|
||||
|
||||
void Fill::connect_infill(Polylines &&infill_ordered, const std::vector<const Polygon*> &boundary_src, const BoundingBox &bbox, Polylines &polylines_out, const double spacing, const FillParams ¶ms)
|
||||
|
||||
@@ -188,6 +188,9 @@ public:
|
||||
// Return true if infill has a consistent pattern between layers.
|
||||
virtual bool has_consistent_pattern() const { return false; }
|
||||
|
||||
// Orca: Is the pattern laid out from the origin instead of the bounding box center?
|
||||
virtual bool aligned_to_origin() const { return false; }
|
||||
|
||||
// Perform the fill.
|
||||
virtual Polylines fill_surface(const Surface *surface, const FillParams ¶ms);
|
||||
virtual ThickPolylines fill_surface_arachne(const Surface* surface, const FillParams& params);
|
||||
|
||||
@@ -19,6 +19,7 @@ public:
|
||||
Fill *clone() const override { return new FillCrossHatch(*this); };
|
||||
~FillCrossHatch() override {}
|
||||
bool is_self_crossing() override { return false; }
|
||||
bool aligned_to_origin() const override { return true; }
|
||||
|
||||
protected:
|
||||
void _fill_surface_single(
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
// require bridge flow since most of this pattern hangs in air
|
||||
bool use_bridge_flow() const override { return false; }
|
||||
bool is_self_crossing() override { return false; }
|
||||
bool aligned_to_origin() const override { return true; }
|
||||
|
||||
// Correction applied to regular infill angle to maximize printing
|
||||
// speed in default configuration (degrees)
|
||||
|
||||
@@ -20,6 +20,7 @@ class FillHoneycomb : public Fill
|
||||
public:
|
||||
~FillHoneycomb() override {}
|
||||
bool is_self_crossing() override { return false; }
|
||||
bool aligned_to_origin() const override { return true; }
|
||||
|
||||
protected:
|
||||
Fill* clone() const override { return new FillHoneycomb(*this); };
|
||||
|
||||
@@ -2750,23 +2750,6 @@ static void polylines_from_paths(const std::vector<MonotonicRegionLink> &path, c
|
||||
}
|
||||
}
|
||||
|
||||
// The extended bounding box of the whole object that covers any rotation of every layer.
|
||||
BoundingBox FillRectilinear::extended_object_bounding_box() const {
|
||||
// Build the extension around the box center. The transpose merge and the sqrt(2.) scaling
|
||||
// (which covers any possible rotation) are both defined about the origin, so a box that is not
|
||||
// origin-centered — e.g. a separated-infill box re-centered on a single assembly part — would be
|
||||
// distorted. Shift to the origin first and back afterwards; for the default origin-centered box
|
||||
// the two translations cancel and this is identical to the original behavior.
|
||||
const Point c = this->bounding_box.center();
|
||||
BoundingBox out = this->bounding_box;
|
||||
out.translate(-c.x(), -c.y());
|
||||
out.merge(Point(out.min.y(), out.min.x()));
|
||||
out.merge(Point(out.max.y(), out.max.x()));
|
||||
out = out.scaled(sqrt(2.));
|
||||
out.translate(c.x(), c.y());
|
||||
return out;
|
||||
}
|
||||
|
||||
bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillParams ¶ms, float angleBase, float pattern_shift, Polylines &polylines_out)
|
||||
{
|
||||
// At the end, only the new polylines will be rotated back.
|
||||
@@ -2801,7 +2784,13 @@ bool FillRectilinear::fill_surface_by_lines(const Surface *surface, const FillPa
|
||||
// For infill that needs to be consistent between layers (like Zig Zag),
|
||||
// we use bounding box of whole object to match vertical lines between layers.
|
||||
BoundingBox bounding_box_src = poly_with_offset.bounding_box_src();
|
||||
BoundingBox bounding_box = this->has_consistent_pattern() ? this->extended_object_bounding_box() : bounding_box_src;
|
||||
BoundingBox bounding_box = bounding_box_src;
|
||||
if (this->has_consistent_pattern()) {
|
||||
// Orca: The polygons are rotated about the origin, so follow the box center to where it was rotated.
|
||||
const Point c = this->bounding_box.center();
|
||||
bounding_box = this->extended_object_bounding_box();
|
||||
bounding_box.translate(c.rotated(- rotate_vector.first) - c);
|
||||
}
|
||||
|
||||
// define flow spacing according to requested density
|
||||
if (params.full_infill() && !params.dont_adjust) {
|
||||
|
||||
@@ -42,9 +42,6 @@ protected:
|
||||
};
|
||||
bool fill_surface_by_multilines(const Surface *surface, FillParams params, const std::initializer_list<SweepParams> &sweep_params, Polylines &polylines_out);
|
||||
bool fill_surface_trapezoidal(const Surface *surface, FillParams params, Polylines &polylines_out, int Pattern_type);
|
||||
|
||||
// The extended bounding box of the whole object that covers any rotation of every layer.
|
||||
BoundingBox extended_object_bounding_box() const;
|
||||
};
|
||||
|
||||
class FillAlignedRectilinear : public FillRectilinear
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
Polylines& polylines_out) override;
|
||||
|
||||
bool is_self_crossing() override { return false; }
|
||||
bool aligned_to_origin() const override { return true; }
|
||||
|
||||
// Density adjustment to have a good %of weight.
|
||||
static constexpr double DensityAdjust = 2.1;
|
||||
|
||||
@@ -31,6 +31,7 @@ public:
|
||||
Polylines& polylines_out) override;
|
||||
|
||||
bool is_self_crossing() override { return false; }
|
||||
bool aligned_to_origin() const override { return true; }
|
||||
|
||||
};
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class PrintObject;
|
||||
class Print;
|
||||
|
||||
namespace FillAdaptive {
|
||||
struct Octree;
|
||||
struct Octrees;
|
||||
};
|
||||
|
||||
namespace FillLightning {
|
||||
@@ -170,10 +170,10 @@ public:
|
||||
ExPolygons lslices;
|
||||
ExPolygons lslices_extrudable; // BBS: the extrudable part of lslices used for tree support
|
||||
std::vector<BoundingBox> lslices_bboxes;
|
||||
// Orca: for separated infills / per-model centering. Aligned with lslices: for each island, the
|
||||
// full bounding box of the 3D connected body (across all layers) it belongs to. Populated by
|
||||
// PrintObject::infill() only when the feature is used; empty otherwise.
|
||||
std::vector<BoundingBox> lslices_separated_component_bboxes;
|
||||
// Orca: for separated infills / per-model centering / octree infills. Aligned with lslices: for each
|
||||
// island, the 3D connected body (across all layers) it belongs to, indexing
|
||||
// PrintObject::separated_body_bboxes(). Populated by PrintObject::prepare_infill() only when needed.
|
||||
std::vector<size_t> lslices_separated_component_ids;
|
||||
|
||||
// BBS
|
||||
ExPolygons loverhangs;
|
||||
@@ -208,9 +208,9 @@ public:
|
||||
void make_perimeters();
|
||||
// Phony version of make_fills() without parameters for Perl integration only.
|
||||
void make_fills() { this->make_fills(nullptr, nullptr); }
|
||||
void make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive::Octree* support_fill_octree, FillLightning::Generator* lightning_generator = nullptr);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring(FillAdaptive::Octree *adaptive_fill_octree,
|
||||
FillAdaptive::Octree *support_fill_octree,
|
||||
void make_fills(const FillAdaptive::Octrees* adaptive_fill_octrees, const FillAdaptive::Octrees* support_fill_octrees, FillLightning::Generator* lightning_generator = nullptr);
|
||||
Polylines generate_sparse_infill_polylines_for_anchoring(const FillAdaptive::Octrees *adaptive_fill_octrees,
|
||||
const FillAdaptive::Octrees *support_fill_octrees,
|
||||
FillLightning::Generator* lightning_generator) const;
|
||||
void make_ironing();
|
||||
// Returns the filament id (1-based) the region is ironed with, or -1 when the
|
||||
|
||||
@@ -1696,6 +1696,9 @@ indexed_triangle_set ModelObject::raw_indexed_triangle_set() const
|
||||
size_t j = out.indices.size();
|
||||
append(out.vertices, v->mesh().its.vertices);
|
||||
append(out.indices, v->mesh().its.indices);
|
||||
// Orca: Point the volume's triangles at its own vertices, which follow those of the volumes before it.
|
||||
for (size_t k = j; k < out.indices.size(); ++ k)
|
||||
out.indices[k] += stl_triangle_vertex_indices::Constant(int(i));
|
||||
const Transform3d& m = v->get_matrix();
|
||||
for (; i < out.vertices.size(); ++ i)
|
||||
out.vertices[i] = (m * out.vertices[i].cast<double>()).cast<float>().eval();
|
||||
|
||||
@@ -375,6 +375,8 @@ public:
|
||||
Transform3d trafo_centered() const
|
||||
{ Transform3d t = this->trafo(); t.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0)); return t; }
|
||||
const PrintInstances& instances() const { return m_instances; }
|
||||
// Orca: Bounding box of each connected body, indexed by Layer::lslices_separated_component_ids.
|
||||
const std::vector<BoundingBox>& separated_body_bboxes() const { return m_separated_body_bboxes; }
|
||||
PrintInstances &instances() { return m_instances; }
|
||||
|
||||
// Whoever will get a non-const pointer to PrintObject will be able to modify its layers.
|
||||
@@ -581,8 +583,8 @@ private:
|
||||
void discover_horizontal_shells();
|
||||
void combine_infill();
|
||||
void _generate_support_material();
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface*, float>>& surfaces_w_bottom_z) const;
|
||||
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface*, const Layer*>>& surfaces_w_layer) const;
|
||||
FillLightning::GeneratorPtr prepare_lightning_infill_data();
|
||||
|
||||
// BBS
|
||||
@@ -614,7 +616,8 @@ private:
|
||||
// so that next call to make_perimeters() performs a union() before computing loops
|
||||
bool m_typed_slices = false;
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> m_adaptive_fill_octrees;
|
||||
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> m_adaptive_fill_octrees;
|
||||
std::vector<BoundingBox> m_separated_body_bboxes;
|
||||
FillLightning::GeneratorPtr m_lightning_generator;
|
||||
|
||||
std::vector < VolumeSlices > firstLayerObjSliceByVolume;
|
||||
|
||||
@@ -7611,8 +7611,8 @@ void PrintConfigDef::init_fff_params()
|
||||
"whole assembly. Parts that touch or overlap are treated as one body and share a center; separate parts "
|
||||
"(or distinct 3D objects) each get their own.\n"
|
||||
"Useful when an assembly groups several objects that should each keep a consistent, self-centered infill.\n"
|
||||
"Affects line and grid patterns and rotation-template infills.\n"
|
||||
"Patterns locked to global coordinates (Gyroid, Honeycomb, TPMS, ...) are unaffected.");
|
||||
"Adaptive Cubic and Support Cubic always center each part on itself, and Lightning infill is generated for "
|
||||
"the whole object and is unaffected.");
|
||||
def->mode = comExpert;
|
||||
def->set_default_value(new ConfigOptionBool(false));
|
||||
|
||||
|
||||
@@ -136,25 +136,31 @@ enum InfillPattern : int {
|
||||
ipCount,
|
||||
};
|
||||
|
||||
// Orca: Infill patterns whose alignment origin follows the fill bounding box, so the
|
||||
// "separated_infills" option can re-center them per connected body. Patterns evaluated in
|
||||
// absolute/global coordinates (Gyroid, TPMS, Honeycomb, CrossHatch, ...) or that are shape-relative
|
||||
// (Concentric) ignore that bounding box and are therefore excluded.
|
||||
// Orca: Infill patterns that the "separated_infills" option can center on each connected body.
|
||||
inline bool is_separable_infill_pattern(InfillPattern pattern)
|
||||
{
|
||||
switch (pattern) {
|
||||
case ipMonotonic:
|
||||
case ipMonotonicLine:
|
||||
case ipRectilinear:
|
||||
case ipAlignedRectilinear:
|
||||
case ipZigZag:
|
||||
case ipCrossZag:
|
||||
case ipLockedZag:
|
||||
case ipLine:
|
||||
case ipGrid:
|
||||
case ipTriangles:
|
||||
case ipStars: // tri-hexagon
|
||||
case ipCubic:
|
||||
case ipQuarterCubic:
|
||||
case ipHoneycomb:
|
||||
case ip3DHoneycomb:
|
||||
case ipLateralHoneycomb:
|
||||
case ipLateralLattice:
|
||||
case ipCrossHatch:
|
||||
case ipTpmsD:
|
||||
case ipTpmsFK:
|
||||
case ipGyroid:
|
||||
case ipHilbertCurve:
|
||||
case ipArchimedeanChords:
|
||||
case ipOctagramSpiral:
|
||||
@@ -164,6 +170,9 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// Orca: Infill patterns laid out by an octree, which each connected body always gets of its own.
|
||||
inline bool is_octree_infill_pattern(InfillPattern pattern) { return pattern == ipAdaptiveCubic || pattern == ipSupportCubic; }
|
||||
|
||||
// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
|
||||
// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
|
||||
// than one line per infill wall; a single line makes them plain crossing lines with nothing to round.
|
||||
|
||||
+123
-32
@@ -67,6 +67,7 @@
|
||||
#include <utility>
|
||||
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <Eigen/Core>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
#include <tbb/spin_mutex.h>
|
||||
@@ -719,7 +720,8 @@ void PrintObject::prepare_infill()
|
||||
bool needs_separated_components = false;
|
||||
for (size_t i = 0; i < this->num_printing_regions(); ++ i) {
|
||||
const PrintRegionConfig &rc = this->printing_region(i).config();
|
||||
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model) {
|
||||
if (rc.separated_infills || rc.center_of_surface_pattern == CenterOfSurfacePattern::Each_Model ||
|
||||
(rc.sparse_infill_density > 0 && is_octree_infill_pattern(rc.sparse_infill_pattern))) {
|
||||
needs_separated_components = true;
|
||||
break;
|
||||
}
|
||||
@@ -736,8 +738,9 @@ void PrintObject::prepare_infill()
|
||||
if (parts <= 1 && ! (first_part != nullptr && first_part->is_splittable()))
|
||||
needs_separated_components = false;
|
||||
}
|
||||
m_separated_body_bboxes.clear();
|
||||
for (Layer *layer : m_layers)
|
||||
layer->lslices_separated_component_bboxes.clear();
|
||||
layer->lslices_separated_component_ids.clear();
|
||||
if (needs_separated_components) {
|
||||
const size_t nl = m_layers.size();
|
||||
std::vector<size_t> offset(nl + 1, 0); // Orca: flat index of the first island of each layer
|
||||
@@ -788,17 +791,20 @@ void PrintObject::prepare_infill()
|
||||
});
|
||||
}
|
||||
}
|
||||
// Orca: Full bounding box of each body, indexed by its union-find root.
|
||||
std::vector<BoundingBox> body_bbox(nreg);
|
||||
for (size_t i = 0; i < nl; ++ i)
|
||||
for (size_t a = 0; a < m_layers[i]->lslices.size(); ++ a)
|
||||
body_bbox[find(offset[i] + a)].merge(m_layers[i]->lslices_bboxes[a]);
|
||||
// Orca: Store the body bbox for every island.
|
||||
// Orca: Number the bodies by their first island and merge the bounding boxes of their islands.
|
||||
std::vector<size_t> body_of_root(nreg, size_t(-1));
|
||||
for (size_t i = 0; i < nl; ++ i) {
|
||||
Layer *layer = m_layers[i];
|
||||
layer->lslices_separated_component_bboxes.resize(layer->lslices.size());
|
||||
for (size_t a = 0; a < layer->lslices.size(); ++ a)
|
||||
layer->lslices_separated_component_bboxes[a] = body_bbox[find(offset[i] + a)];
|
||||
layer->lslices_separated_component_ids.resize(layer->lslices.size());
|
||||
for (size_t a = 0; a < layer->lslices.size(); ++ a) {
|
||||
size_t &body = body_of_root[find(offset[i] + a)];
|
||||
if (body == size_t(-1)) {
|
||||
body = m_separated_body_bboxes.size();
|
||||
m_separated_body_bboxes.emplace_back();
|
||||
}
|
||||
m_separated_body_bboxes[body].merge(layer->lslices_bboxes[a]);
|
||||
layer->lslices_separated_component_ids[a] = body;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,16 +842,13 @@ void PrintObject::infill()
|
||||
if (this->set_started(posInfill)) {
|
||||
m_print->set_status(35, L("Generating infill toolpath"));
|
||||
|
||||
const auto& adaptive_fill_octree = this->m_adaptive_fill_octrees.first;
|
||||
const auto& support_fill_octree = this->m_adaptive_fill_octrees.second;
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Filling layers in parallel - start";
|
||||
tbb::parallel_for(
|
||||
tbb::blocked_range<size_t>(0, m_layers.size()),
|
||||
[this, &adaptive_fill_octree = adaptive_fill_octree, &support_fill_octree = support_fill_octree](const tbb::blocked_range<size_t>& range) {
|
||||
[this](const tbb::blocked_range<size_t>& range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
|
||||
m_print->throw_if_canceled();
|
||||
m_layers[layer_idx]->make_fills(adaptive_fill_octree.get(), support_fill_octree.get(), this->m_lightning_generator.get());
|
||||
m_layers[layer_idx]->make_fills(&m_adaptive_fill_octrees.first, &m_adaptive_fill_octrees.second, this->m_lightning_generator.get());
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1110,14 +1113,69 @@ void PrintObject::simplify_extrusion_path()
|
||||
}
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface *, float>> &surfaces_w_bottom_z) const
|
||||
// Orca: Separated body of the island containing a point of a layer, else of the island outline nearest within 1 mm, or -1.
|
||||
static int separated_body_at(const Layer &layer, const Point &point)
|
||||
{
|
||||
int body = -1;
|
||||
double best = scaled<double>(1.);
|
||||
for (size_t i = 0; i < layer.lslices.size() && i < layer.lslices_separated_component_ids.size() && best > 0.; ++ i) {
|
||||
BoundingBox bbox = layer.lslices_bboxes[i];
|
||||
bbox.offset(coord_t(best));
|
||||
if (! bbox.contains(point))
|
||||
continue;
|
||||
const double dist = layer.lslices[i].contains(point) ? 0. : (layer.lslices[i].point_projection(point) - point).cast<double>().norm();
|
||||
if (dist < best) {
|
||||
best = dist;
|
||||
body = int(layer.lslices_separated_component_ids[i]);
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
// Orca: The object mesh in the octree frame split by separated body. Each connected component goes to the body
|
||||
// most of its sampled triangles lie on, sampled a layer height inside the solid at the layer nearest to them.
|
||||
static std::vector<indexed_triangle_set> split_mesh_by_body(const PrintObject &object, const indexed_triangle_set &mesh, size_t num_bodies)
|
||||
{
|
||||
const Eigen::Matrix3d to_object = FillAdaptive::transform_to_world().toRotationMatrix();
|
||||
const double inset = object.config().layer_height.value;
|
||||
std::vector<indexed_triangle_set> bodies(num_bodies);
|
||||
for (const indexed_triangle_set &component : its_split(mesh)) {
|
||||
std::vector<size_t> votes(num_bodies, 0);
|
||||
const size_t step = std::max<size_t>(1, component.indices.size() / 8);
|
||||
for (size_t i = 0; i < component.indices.size(); i += step) {
|
||||
const stl_triangle_vertex_indices &tri = component.indices[i];
|
||||
const Vec3d a = component.vertices[tri[0]].cast<double>(), b = component.vertices[tri[1]].cast<double>(),
|
||||
d = component.vertices[tri[2]].cast<double>();
|
||||
const Vec3d normal = (b - a).cross(d - a);
|
||||
const double area2 = normal.norm();
|
||||
const Vec3d c = to_object * ((a + b + d) / 3. - (area2 > 0. ? Vec3d(normal * (inset / area2)) : Vec3d::Zero()));
|
||||
size_t lo = 0, hi = object.layer_count();
|
||||
while (lo < hi) {
|
||||
const size_t mid = (lo + hi) / 2;
|
||||
if (object.get_layer(int(mid))->slice_z < c.z())
|
||||
lo = mid + 1;
|
||||
else
|
||||
hi = mid;
|
||||
}
|
||||
if (lo == object.layer_count() || (lo > 0 && c.z() - object.get_layer(int(lo) - 1)->slice_z < object.get_layer(int(lo))->slice_z - c.z()))
|
||||
-- lo;
|
||||
if (const int body = separated_body_at(*object.get_layer(int(lo)), Point(scaled<coord_t>(c.x()), scaled<coord_t>(c.y()))); body >= 0)
|
||||
++ votes[body];
|
||||
}
|
||||
if (const auto best = std::max_element(votes.begin(), votes.end()); *best > 0)
|
||||
its_merge(bodies[best - votes.begin()], component);
|
||||
}
|
||||
return bodies;
|
||||
}
|
||||
|
||||
std::pair<FillAdaptive::Octrees, FillAdaptive::Octrees> PrintObject::prepare_adaptive_infill_data(
|
||||
const std::vector<std::pair<const Surface *, const Layer *>> &surfaces_w_layer) const
|
||||
{
|
||||
using namespace FillAdaptive;
|
||||
|
||||
auto [adaptive_line_spacing, support_line_spacing] = adaptive_fill_line_spacing(*this);
|
||||
if ((adaptive_line_spacing == 0. && support_line_spacing == 0.) || this->layers().empty())
|
||||
return std::make_pair(OctreePtr(), OctreePtr());
|
||||
return {};
|
||||
|
||||
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
|
||||
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
|
||||
@@ -1125,27 +1183,60 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
|
||||
its_transform(mesh, to_octree * this->trafo_centered(), true);
|
||||
|
||||
// Triangulate internal bridging surfaces.
|
||||
std::vector<std::vector<Vec3d>> overhangs(std::max(surfaces_w_bottom_z.size(), size_t(1)));
|
||||
std::vector<std::vector<Vec3d>> overhangs(std::max(surfaces_w_layer.size(), size_t(1)));
|
||||
// ^ make sure vector is not empty, even with no briding surfaces we still want to build the adaptive trees later, some continue normally
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, surfaces_w_bottom_z.size()),
|
||||
[this, &to_octree, &overhangs, &surfaces_w_bottom_z](const tbb::blocked_range<int> &range) {
|
||||
tbb::parallel_for(tbb::blocked_range<int>(0, surfaces_w_layer.size()),
|
||||
[this, &to_octree, &overhangs, &surfaces_w_layer](const tbb::blocked_range<int> &range) {
|
||||
PRINT_OBJECT_TIME_LIMIT_MILLIS(PRINT_OBJECT_TIME_LIMIT_DEFAULT);
|
||||
for (int surface_idx = range.begin(); surface_idx < range.end(); ++surface_idx) {
|
||||
std::vector<Vec3d> &out = overhangs[surface_idx];
|
||||
m_print->throw_if_canceled();
|
||||
append(out, triangulate_expolygon_3d(surfaces_w_bottom_z[surface_idx].first->expolygon,
|
||||
surfaces_w_bottom_z[surface_idx].second));
|
||||
append(out, triangulate_expolygon_3d(surfaces_w_layer[surface_idx].first->expolygon,
|
||||
float(surfaces_w_layer[surface_idx].second->bottom_z())));
|
||||
for (Vec3d &p : out)
|
||||
p = (to_octree * p).eval();
|
||||
}
|
||||
});
|
||||
|
||||
// Orca: Each body gets the octree it has when sliced on its own, from its own triangles.
|
||||
std::pair<Octrees, Octrees> octrees;
|
||||
const size_t num_bodies = m_separated_body_bboxes.size();
|
||||
bool need_object = num_bodies <= 1;
|
||||
if (num_bodies > 1) {
|
||||
const std::vector<indexed_triangle_set> body_meshes = split_mesh_by_body(*this, mesh, num_bodies);
|
||||
need_object = std::any_of(body_meshes.begin(), body_meshes.end(), [](const indexed_triangle_set &its) { return its.indices.empty(); });
|
||||
std::vector<std::vector<Vec3d>> body_overhangs(num_bodies);
|
||||
for (size_t i = 0; i < surfaces_w_layer.size(); ++ i)
|
||||
if (const int body = separated_body_at(*surfaces_w_layer[i].second, surfaces_w_layer[i].first->expolygon.contour.points.front()); body >= 0)
|
||||
append(body_overhangs[body], overhangs[i]);
|
||||
if (adaptive_line_spacing)
|
||||
octrees.first.bodies.resize(num_bodies);
|
||||
if (support_line_spacing)
|
||||
octrees.second.bodies.resize(num_bodies);
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_bodies), [&, adaptive_spacing = adaptive_line_spacing, support_spacing = support_line_spacing](
|
||||
const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t body = range.begin(); body < range.end(); ++ body) {
|
||||
m_print->throw_if_canceled();
|
||||
if (body_meshes[body].indices.empty())
|
||||
continue;
|
||||
if (adaptive_spacing)
|
||||
octrees.first.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], adaptive_spacing, false);
|
||||
if (support_spacing)
|
||||
octrees.second.bodies[body] = build_octree(body_meshes[body], body_overhangs[body], support_spacing, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// and gather them.
|
||||
for (size_t i = 1; i < overhangs.size(); ++ i)
|
||||
append(overhangs.front(), std::move(overhangs[i]));
|
||||
|
||||
return std::make_pair(
|
||||
adaptive_line_spacing ? build_octree(mesh, overhangs.front(), adaptive_line_spacing, false) : OctreePtr(),
|
||||
support_line_spacing ? build_octree(mesh, overhangs.front(), support_line_spacing, true) : OctreePtr());
|
||||
// Orca: The object's octree only serves bodies that have none of their own.
|
||||
if (need_object && adaptive_line_spacing)
|
||||
octrees.first.object = build_octree(mesh, overhangs.front(), adaptive_line_spacing, false);
|
||||
if (need_object && support_line_spacing)
|
||||
octrees.second.object = build_octree(mesh, overhangs.front(), support_line_spacing, true);
|
||||
return octrees;
|
||||
}
|
||||
|
||||
FillLightning::GeneratorPtr PrintObject::prepare_lightning_infill_data()
|
||||
@@ -2963,14 +3054,14 @@ void PrintObject::bridge_over_infill()
|
||||
std::map<size_t, Polylines> infill_lines;
|
||||
// SECTION to generate infill polylines
|
||||
{
|
||||
std::vector<std::pair<const Surface *, float>> surfaces_w_bottom_z;
|
||||
std::vector<std::pair<const Surface *, const Layer *>> surfaces_w_layer;
|
||||
for (const auto &pair : surfaces_by_layer) {
|
||||
for (const CandidateSurface &c : pair.second) {
|
||||
surfaces_w_bottom_z.emplace_back(c.original_surface, c.region->m_layer->bottom_z());
|
||||
surfaces_w_layer.emplace_back(c.original_surface, c.region->m_layer);
|
||||
}
|
||||
}
|
||||
|
||||
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_bottom_z);
|
||||
this->m_adaptive_fill_octrees = this->prepare_adaptive_infill_data(surfaces_w_layer);
|
||||
|
||||
std::vector<size_t> layers_to_generate_infill;
|
||||
for (const auto &pair : surfaces_by_layer) {
|
||||
@@ -2986,8 +3077,8 @@ void PrintObject::bridge_over_infill()
|
||||
for (size_t job_idx = r.begin(); job_idx < r.end(); job_idx++) {
|
||||
size_t lidx = layers_to_generate_infill[job_idx];
|
||||
infill_lines.at(
|
||||
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(po->m_adaptive_fill_octrees.first.get(),
|
||||
po->m_adaptive_fill_octrees.second.get(),
|
||||
lidx) = po->get_layer(lidx)->generate_sparse_infill_polylines_for_anchoring(&po->m_adaptive_fill_octrees.first,
|
||||
&po->m_adaptive_fill_octrees.second,
|
||||
po->m_lightning_generator.get());
|
||||
}
|
||||
});
|
||||
|
||||
@@ -871,10 +871,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
||||
toggle_line("center_of_surface_pattern", has_centered_surface);
|
||||
|
||||
// Orca: separate infills
|
||||
bool is_internal_infill_separable = is_separable_infill_pattern(config->option<ConfigOptionEnum<InfillPattern>>("sparse_infill_pattern")->value) ||
|
||||
config->opt_string("sparse_infill_rotate_template") != "" ||
|
||||
config->opt_string("solid_infill_rotate_template") != "";
|
||||
toggle_line("separated_infills", is_internal_infill_separable);
|
||||
toggle_line("separated_infills", is_separable_infill_pattern(pattern));
|
||||
|
||||
// Fill order is only meaningful for the center-based surface fill patterns; hide it otherwise.
|
||||
auto is_centered_fill = [](InfillPattern p) { return p == ipConcentric || p == ipSpiralInset || p == ipArchimedeanChords || p == ipOctagramSpiral; };
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
@@ -1790,3 +1791,200 @@ TEST_CASE("Sparse plane-path anchors match the printed infill", "[Fill][Internal
|
||||
// would hide anchors that no longer coincide with printed lines.
|
||||
CHECK(unscale<double>(max_distance) <= config.opt_float("resolution"));
|
||||
}
|
||||
|
||||
// Orca: Slices the meshes as the parts of one object, where they are.
|
||||
static Print &slice_parts(Print &print, DynamicPrintConfig config, const std::vector<TriangleMesh> &parts)
|
||||
{
|
||||
config.set_deserialize_strict({{"layer_height", 0.2},
|
||||
{"initial_layer_print_height", 0.2},
|
||||
{"elefant_foot_compensation", 0},
|
||||
{"top_shell_thickness", 0},
|
||||
{"bottom_shell_thickness", 0}});
|
||||
Model model;
|
||||
Slic3r::Test::init_print({parts.front()}, print, model, config, nullptr, false);
|
||||
for (size_t i = 1; i < parts.size(); ++ i)
|
||||
model.objects.front()->add_volume(TriangleMesh(parts[i]), ModelVolumeType::MODEL_PART, false);
|
||||
print.apply(model, config);
|
||||
print.process();
|
||||
return print;
|
||||
}
|
||||
|
||||
// Orca: Two identical cubes in one mesh that never touch, so each is a body of its own.
|
||||
static Print &slice_two_bodies(Print &print, const DynamicPrintConfig &config, double height)
|
||||
{
|
||||
TriangleMesh mesh = make_cube(20, 20, height);
|
||||
TriangleMesh second = make_cube(20, 20, height);
|
||||
second.translate(33, 7, 0);
|
||||
mesh.merge(second);
|
||||
return slice_parts(print, config, {mesh});
|
||||
}
|
||||
|
||||
// Orca: Counts the points sampled along both sets that the other set does not repeat.
|
||||
static void count_unmatched(const Polylines &a, const Polylines &b, size_t &sampled, size_t &unmatched)
|
||||
{
|
||||
const std::array<const Polylines *, 2> sets{&a, &b};
|
||||
for (size_t i = 0; i < 2; ++ i) {
|
||||
const Polylines &other = *sets[1 - i];
|
||||
const AABBTreeLines::LinesDistancer<Line> distancer(to_lines(other));
|
||||
for (const Polyline &path : *sets[i])
|
||||
for (const Point &point : path.equally_spaced_points(scale_(0.2))) {
|
||||
++ sampled;
|
||||
unmatched += other.empty() || distancer.distance_from_lines<false>(point) > scale_(0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Polylines layer_paths(const Layer &layer, ExtrusionRole role)
|
||||
{
|
||||
Polylines polylines;
|
||||
for (const LayerRegion *region : layer.regions())
|
||||
for (const ExtrusionEntity *entity : region->fills.flatten().entities)
|
||||
if (entity->role() == role)
|
||||
entity->collect_polylines(polylines);
|
||||
return polylines;
|
||||
}
|
||||
|
||||
// Orca: Share of the paths of a role that the other body does not repeat around its own center.
|
||||
static double unmatched_between_bodies(const Print &print, ExtrusionRole role)
|
||||
{
|
||||
size_t sampled = 0, unmatched = 0;
|
||||
for (const Layer *layer : print.objects().front()->layers()) {
|
||||
REQUIRE(layer->lslices.size() == 2);
|
||||
const Polylines polylines = layer_paths(*layer, role);
|
||||
std::array<Polylines, 2> paths;
|
||||
for (size_t body = 0; body < 2; ++ body) {
|
||||
// Orca: Exclude the links along the walls, which each body may chain differently.
|
||||
paths[body] = intersection_pl(polylines, shrink(to_polygons(layer->lslices[body]), scale_(3.)));
|
||||
for (Polyline &path : paths[body])
|
||||
path.translate(-layer->lslices_bboxes[body].center());
|
||||
}
|
||||
count_unmatched(paths[0], paths[1], sampled, unmatched);
|
||||
}
|
||||
REQUIRE(sampled > 0);
|
||||
return double(unmatched) / double(sampled);
|
||||
}
|
||||
|
||||
// Orca: Share of the paths of a role inside a bed region that two slices of the same body do not share.
|
||||
static double unmatched_between_prints(const Print &a, const Print &b, ExtrusionRole role, const Polygons ®ion)
|
||||
{
|
||||
const PrintObject &object_a = *a.objects().front(), &object_b = *b.objects().front();
|
||||
REQUIRE(object_a.layer_count() == object_b.layer_count());
|
||||
size_t sampled = 0, unmatched = 0;
|
||||
for (size_t i = 0; i < object_a.layer_count(); ++ i) {
|
||||
std::array<Polylines, 2> paths;
|
||||
for (const PrintObject *object : {&object_a, &object_b}) {
|
||||
Polylines &out = paths[object == &object_b];
|
||||
out = layer_paths(*object->get_layer(int(i)), role);
|
||||
for (Polyline &path : out)
|
||||
path.translate(object->instances().front().shift);
|
||||
out = intersection_pl(out, region);
|
||||
}
|
||||
count_unmatched(paths[0], paths[1], sampled, unmatched);
|
||||
}
|
||||
REQUIRE(sampled > 0);
|
||||
return double(unmatched) / double(sampled);
|
||||
}
|
||||
|
||||
TEST_CASE("Separated infill centers the sparse infill of each body on itself", "[Fill][Regression]")
|
||||
{
|
||||
const std::string pattern = GENERATE("line", "zigzag", "crosszag", "honeycomb", "3dhoneycomb", "crosshatch", "tpmsd", "tpmsfk", "gyroid");
|
||||
const bool separated = GENERATE(false, true);
|
||||
CAPTURE(pattern, separated);
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
// Orca: The Zig Zag patterns mirror each body about its own center.
|
||||
config.set_deserialize_strict({{"sparse_infill_pattern", pattern},
|
||||
{"sparse_infill_density", "20%"},
|
||||
{"symmetric_infill_y_axis", true},
|
||||
{"top_shell_layers", 0},
|
||||
{"bottom_shell_layers", 0},
|
||||
{"separated_infills", separated}});
|
||||
Print print;
|
||||
const double unmatched = unmatched_between_bodies(slice_two_bodies(print, config, 2.), erInternalInfill);
|
||||
// Orca: Without separation both bodies cut one object-wide pattern at different places.
|
||||
if (separated)
|
||||
CHECK(unmatched < 0.02);
|
||||
else
|
||||
CHECK(unmatched > 0.5);
|
||||
}
|
||||
|
||||
TEST_CASE("Separated infill centers monotonic and rectilinear bridges on each body", "[Fill][InternalBridge][Regression]")
|
||||
{
|
||||
// Orca: Bridges use the Monotonic pattern below monotonic top surfaces and Rectilinear otherwise.
|
||||
const std::string top_pattern = GENERATE("monotonicline", "rectilinear");
|
||||
const bool separated = GENERATE(false, true);
|
||||
CAPTURE(top_pattern, separated);
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({{"sparse_infill_pattern", "rectilinear"},
|
||||
{"sparse_infill_density", "15%"},
|
||||
{"top_surface_pattern", top_pattern},
|
||||
{"top_shell_layers", 4},
|
||||
{"bottom_shell_layers", 0},
|
||||
{"separated_infills", separated}});
|
||||
Print print;
|
||||
const double unmatched = unmatched_between_bodies(slice_two_bodies(print, config, 4.), erInternalBridgeInfill);
|
||||
if (separated)
|
||||
CHECK(unmatched < 0.02);
|
||||
else
|
||||
CHECK(unmatched > 0.5);
|
||||
}
|
||||
|
||||
// Orca: Share of the infill of an off center pillar, and of the frame of four overlapping bars around it,
|
||||
// that each body sliced alone does not repeat. The frame is one body of several parts that holds the pillar.
|
||||
static std::pair<double, double> frame_and_pillar_unmatched(const DynamicPrintConfig &config)
|
||||
{
|
||||
auto box = [](double x, double y, double size_x, double size_y) {
|
||||
TriangleMesh mesh = make_cube(size_x, size_y, 6);
|
||||
mesh.translate(x, y, 0);
|
||||
return mesh;
|
||||
};
|
||||
const std::vector<TriangleMesh> frame{box(0, 0, 60, 14), box(0, 46, 60, 14), box(0, 0, 14, 60), box(46, 0, 14, 60)};
|
||||
const std::vector<TriangleMesh> pillar{box(18, 20, 16, 16)};
|
||||
std::vector<TriangleMesh> both = frame;
|
||||
both.push_back(pillar.front());
|
||||
Print print_both, print_frame, print_pillar;
|
||||
slice_parts(print_both, config, both);
|
||||
slice_parts(print_frame, config, frame);
|
||||
slice_parts(print_pillar, config, pillar);
|
||||
|
||||
// Orca: Bed regions 3 mm inside the walls, away from the links along them.
|
||||
auto rect = [](double x0, double y0, double x1, double y1) {
|
||||
return Polygon({Point::new_scale(x0, y0), Point::new_scale(x1, y0), Point::new_scale(x1, y1), Point::new_scale(x0, y1)});
|
||||
};
|
||||
return {unmatched_between_prints(print_both, print_pillar, erInternalInfill, {rect(21, 23, 31, 33)}),
|
||||
unmatched_between_prints(print_both, print_frame, erInternalInfill, diff(Polygons{rect(3, 3, 57, 57)}, Polygons{rect(11, 11, 49, 49)}))};
|
||||
}
|
||||
|
||||
TEST_CASE("Separated infill fills each body like the body sliced alone", "[Fill][Regression]")
|
||||
{
|
||||
// Orca: Hilbert Curve and the Zig Zag links follow the extent of the box, not only its center.
|
||||
const std::string pattern = GENERATE("hilbertcurve", "zigzag", "crosszag", "gyroid");
|
||||
CAPTURE(pattern);
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({{"sparse_infill_pattern", pattern},
|
||||
{"sparse_infill_density", "20%"},
|
||||
{"symmetric_infill_y_axis", true},
|
||||
{"top_shell_layers", 0},
|
||||
{"bottom_shell_layers", 0},
|
||||
{"separated_infills", true}});
|
||||
const std::pair<double, double> unmatched = frame_and_pillar_unmatched(config);
|
||||
CHECK(unmatched.first < 0.02);
|
||||
CHECK(unmatched.second < 0.02);
|
||||
}
|
||||
|
||||
TEST_CASE("Adaptive infill fills each body like the body sliced alone", "[Fill][Regression]")
|
||||
{
|
||||
const std::string pattern = GENERATE("adaptivecubic", "supportcubic");
|
||||
// Orca: Octree infill centers each body whether or not separated infills are enabled.
|
||||
const bool separated = GENERATE(false, true);
|
||||
CAPTURE(pattern, separated);
|
||||
auto config = DynamicPrintConfig::full_print_config();
|
||||
config.set_deserialize_strict({{"sparse_infill_pattern", pattern},
|
||||
{"sparse_infill_density", "40%"},
|
||||
{"top_shell_layers", 0},
|
||||
{"bottom_shell_layers", 0},
|
||||
{"separated_infills", separated}});
|
||||
// Orca: The octree of the whole object is laid out from its center, which the off center pillar does not share.
|
||||
const std::pair<double, double> unmatched = frame_and_pillar_unmatched(config);
|
||||
CHECK(unmatched.first < 0.02);
|
||||
CHECK(unmatched.second < 0.02);
|
||||
}
|
||||
|
||||
@@ -512,12 +512,13 @@ TEST_CASE("Separated infill keeps fragmented and nested bodies independent", "[P
|
||||
print.process();
|
||||
const PrintObject &object = *print.objects().front();
|
||||
REQUIRE(object.layer_count() > 1);
|
||||
CHECK(object.separated_body_bboxes().size() == grid_size * grid_size + 2);
|
||||
for (const Layer *layer : object.layers()) {
|
||||
REQUIRE(layer->lslices.size() == grid_size * grid_size + 2);
|
||||
REQUIRE(layer->lslices_separated_component_bboxes.size() == layer->lslices.size());
|
||||
REQUIRE(layer->lslices_separated_component_ids.size() == layer->lslices.size());
|
||||
size_t holes = 0;
|
||||
for (size_t i = 0; i < layer->lslices.size(); ++ i) {
|
||||
const BoundingBox &body = layer->lslices_separated_component_bboxes[i];
|
||||
const BoundingBox &body = object.separated_body_bboxes()[layer->lslices_separated_component_ids[i]];
|
||||
const BoundingBox &island = layer->lslices_bboxes[i];
|
||||
CHECK(body.min == island.min);
|
||||
CHECK(body.max == island.max);
|
||||
@@ -574,6 +575,7 @@ TEST_CASE("Body centering survives islands merging and splitting between layers"
|
||||
REQUIRE(object.get_layer(1)->lslices.size() == 3);
|
||||
REQUIRE(object.get_layer(2)->lslices.size() == 3);
|
||||
REQUIRE(object.get_layer(4)->lslices.size() == 5);
|
||||
CHECK(object.separated_body_bboxes().size() == 2);
|
||||
|
||||
BoundingBox isolated_bbox = object.get_layer(0)->lslices_bboxes.front();
|
||||
for (const BoundingBox &bbox : object.get_layer(0)->lslices_bboxes)
|
||||
@@ -585,10 +587,10 @@ TEST_CASE("Body centering survives islands merging and splitting between layers"
|
||||
if (bbox.min.x() < isolated_bbox.min.x())
|
||||
connected_bbox.merge(bbox);
|
||||
for (const Layer *layer : object.layers()) {
|
||||
REQUIRE(layer->lslices_separated_component_bboxes.size() == layer->lslices.size());
|
||||
REQUIRE(layer->lslices_separated_component_ids.size() == layer->lslices.size());
|
||||
for (size_t i = 0; i < layer->lslices.size(); ++ i) {
|
||||
const BoundingBox &expected = layer->lslices_bboxes[i].min.x() < isolated_bbox.min.x() ? connected_bbox : isolated_bbox;
|
||||
const BoundingBox &actual = layer->lslices_separated_component_bboxes[i];
|
||||
const BoundingBox &actual = object.separated_body_bboxes()[layer->lslices_separated_component_ids[i]];
|
||||
CHECK(actual.min == expected.min);
|
||||
CHECK(actual.max == expected.max);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "libslic3r/TriangleMesh.hpp"
|
||||
#include "libslic3r/Polygon.hpp"
|
||||
#include "libslic3r/BoundingBox.hpp"
|
||||
#include "libslic3r/Point.hpp"
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <catch2/matchers/catch_matchers.hpp>
|
||||
#include <catch2/matchers/catch_matchers_floating_point.hpp>
|
||||
#include "libslic3r/Model.hpp"
|
||||
#include "libslic3r/Geometry.hpp"
|
||||
|
||||
@@ -44,3 +49,25 @@ TEST_CASE("A part's 2D convex hull is its footprint projected onto the bed", "[M
|
||||
CHECK(bb.max.y() == scaled(45.));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("An object's raw mesh keeps the triangles of each part on its own vertices", "[Model]")
|
||||
{
|
||||
Model model;
|
||||
ModelObject *object = model.add_object();
|
||||
object->add_volume(make_cube(10, 10, 10), ModelVolumeType::MODEL_PART, false);
|
||||
TriangleMesh second = make_cube(10, 10, 10);
|
||||
second.translate(30, 0, 0);
|
||||
object->add_volume(std::move(second), ModelVolumeType::MODEL_PART, false);
|
||||
|
||||
// Two separate cubes stay two closed components, one around each cube.
|
||||
const std::vector<indexed_triangle_set> parts = its_split(object->raw_indexed_triangle_set());
|
||||
REQUIRE(parts.size() == 2);
|
||||
std::vector<double> min_x;
|
||||
for (const indexed_triangle_set &part : parts) {
|
||||
CHECK(part.indices.size() == 12);
|
||||
min_x.push_back(bounding_box(part).min.x());
|
||||
}
|
||||
std::sort(min_x.begin(), min_x.end());
|
||||
CHECK_THAT(min_x.front(), Catch::Matchers::WithinAbs(0., 1e-4));
|
||||
CHECK_THAT(min_x.back(), Catch::Matchers::WithinAbs(30., 1e-4));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user