Merge branch 'master' of https://github.com/prusa3d/PrusaSlicer into et_gcode_window

This commit is contained in:
enricoturri1966 2021-03-15 11:27:43 +01:00
commit db71a6308d
97 changed files with 4090 additions and 2862 deletions

View file

@ -21,22 +21,30 @@ public:
min(pmin), max(pmax), defined(pmin(0) < pmax(0) && pmin(1) < pmax(1)) {}
BoundingBoxBase(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
min(p1), max(p1), defined(false) { merge(p2); merge(p3); }
BoundingBoxBase(const std::vector<PointClass>& points) : min(PointClass::Zero()), max(PointClass::Zero())
template<class It, class = IteratorOnly<It> >
BoundingBoxBase(It from, It to) : min(PointClass::Zero()), max(PointClass::Zero())
{
if (points.empty()) {
if (from == to) {
this->defined = false;
// throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBoxBase constructor");
} else {
typename std::vector<PointClass>::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
for (++ it; it != points.end(); ++ it) {
this->min = this->min.cwiseMin(*it);
this->max = this->max.cwiseMax(*it);
auto it = from;
this->min = it->template cast<typename PointClass::Scalar>();
this->max = this->min;
for (++ it; it != to; ++ it) {
auto vec = it->template cast<typename PointClass::Scalar>();
this->min = this->min.cwiseMin(vec);
this->max = this->max.cwiseMax(vec);
}
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1));
}
}
BoundingBoxBase(const std::vector<PointClass> &points)
: BoundingBoxBase(points.begin(), points.end())
{}
void reset() { this->defined = false; this->min = PointClass::Zero(); this->max = PointClass::Zero(); }
void merge(const PointClass &point);
void merge(const std::vector<PointClass> &points);
@ -74,19 +82,27 @@ public:
{ if (pmin(2) >= pmax(2)) BoundingBoxBase<PointClass>::defined = false; }
BoundingBox3Base(const PointClass &p1, const PointClass &p2, const PointClass &p3) :
BoundingBoxBase<PointClass>(p1, p1) { merge(p2); merge(p3); }
BoundingBox3Base(const std::vector<PointClass>& points)
template<class It, class = IteratorOnly<It> > BoundingBox3Base(It from, It to)
{
if (points.empty())
if (from == to)
throw Slic3r::InvalidArgument("Empty point set supplied to BoundingBox3Base constructor");
typename std::vector<PointClass>::const_iterator it = points.begin();
this->min = *it;
this->max = *it;
for (++ it; it != points.end(); ++ it) {
this->min = this->min.cwiseMin(*it);
this->max = this->max.cwiseMax(*it);
auto it = from;
this->min = it->template cast<typename PointClass::Scalar>();
this->max = this->min;
for (++ it; it != to; ++ it) {
auto vec = it->template cast<typename PointClass::Scalar>();
this->min = this->min.cwiseMin(vec);
this->max = this->max.cwiseMax(vec);
}
this->defined = (this->min(0) < this->max(0)) && (this->min(1) < this->max(1)) && (this->min(2) < this->max(2));
}
BoundingBox3Base(const std::vector<PointClass> &points)
: BoundingBox3Base(points.begin(), points.end())
{}
void merge(const PointClass &point);
void merge(const std::vector<PointClass> &points);
void merge(const BoundingBox3Base<PointClass> &bb);
@ -188,9 +204,7 @@ public:
class BoundingBoxf3 : public BoundingBox3Base<Vec3d>
{
public:
BoundingBoxf3() : BoundingBox3Base<Vec3d>() {}
BoundingBoxf3(const Vec3d &pmin, const Vec3d &pmax) : BoundingBox3Base<Vec3d>(pmin, pmax) {}
BoundingBoxf3(const std::vector<Vec3d> &points) : BoundingBox3Base<Vec3d>(points) {}
using BoundingBox3Base::BoundingBox3Base;
BoundingBoxf3 transformed(const Transform3d& matrix) const;
};

View file

@ -320,7 +320,7 @@ static void make_inner_brim(const Print &print, const ConstPrintObjectPtrs &top_
loops = union_pt_chained_outside_in(loops, false);
std::reverse(loops.begin(), loops.end());
extrusion_entities_append_loops(brim.entities, std::move(loops), erSkirt, float(flow.mm3_per_mm()),
float(flow.width), float(print.skirt_first_layer_height()));
float(flow.width()), float(print.skirt_first_layer_height()));
}
// Produce brim lines around those objects, that have the brim enabled.
@ -495,7 +495,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
if (i + 1 == j && first_path.size() > 3 && first_path.front().X == first_path.back().X && first_path.front().Y == first_path.back().Y) {
auto *loop = new ExtrusionLoop();
brim.entities.emplace_back(loop);
loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()));
loop->paths.emplace_back(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
Points &points = loop->paths.front().polyline.points;
points.reserve(first_path.size());
for (const ClipperLib_Z::IntPoint &pt : first_path)
@ -506,7 +506,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
ExtrusionEntityCollection this_loop_trimmed;
this_loop_trimmed.entities.reserve(j - i);
for (; i < j; ++ i) {
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height())));
this_loop_trimmed.entities.emplace_back(new ExtrusionPath(erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height())));
const ClipperLib_Z::Path &path = *loops_trimmed_order[i].first;
Points &points = static_cast<ExtrusionPath*>(this_loop_trimmed.entities.back())->polyline.points;
points.reserve(path.size());
@ -522,7 +522,7 @@ ExtrusionEntityCollection make_brim(const Print &print, PrintTryCancel try_cance
}
}
} else {
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width), float(print.skirt_first_layer_height()));
extrusion_entities_append_loops_and_paths(brim.entities, std::move(all_loops), erSkirt, float(flow.mm3_per_mm()), float(flow.width()), float(print.skirt_first_layer_height()));
}
make_inner_brim(print, top_level_objects_with_brim, brim);

View file

@ -1441,6 +1441,24 @@ private:
class ConfigOptionDef
{
public:
enum class GUIType {
undefined,
// Open enums, integer value could be one of the enumerated values or something else.
i_enum_open,
// Open enums, float value could be one of the enumerated values or something else.
f_enum_open,
// Color picker, string value.
color,
// ???
select_open,
// Currently unused.
slider,
// Static text
legend,
// Vector value, but edited as a single string.
one_string,
};
// Identifier of this option. It is stored here so that it is accessible through the by_serialization_key_ordinal map.
t_config_option_key opt_key;
// What type? bool, int, string etc.
@ -1524,7 +1542,7 @@ public:
// Usually empty.
// Special values - "i_enum_open", "f_enum_open" to provide combo box for int or float selection,
// "select_open" - to open a selection dialog (currently only a serial port selection).
std::string gui_type;
GUIType gui_type { GUIType::undefined };
// Usually empty. Otherwise "serialized" or "show_value"
// The flags may be combined.
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.

View file

@ -621,7 +621,7 @@ ExPolygon elephant_foot_compensation(const ExPolygon &input_expoly, double min_c
ExPolygon elephant_foot_compensation(const ExPolygon &input, const Flow &external_perimeter_flow, const double compensation)
{
// The contour shall be wide enough to apply the external perimeter plus compensation on both sides.
double min_contour_width = double(external_perimeter_flow.width + external_perimeter_flow.spacing());
double min_contour_width = double(external_perimeter_flow.width() + external_perimeter_flow.spacing());
return elephant_foot_compensation(input, min_contour_width, compensation);
}

View file

@ -52,7 +52,9 @@ void ExtrusionPath::polygons_covered_by_spacing(Polygons &out, const float scale
{
// Instantiating the Flow class to get the line spacing.
// Don't know the nozzle diameter, setting to zero. It shall not matter it shall be optimized out by the compiler.
Flow flow(this->width, this->height, 0.f, is_bridge(this->role()));
bool bridge = is_bridge(this->role());
assert(! bridge || this->width == this->height);
auto flow = bridge ? Flow::bridging_flow(this->width, 0.f) : Flow(this->width, this->height, 0.f);
polygons_append(out, offset(this->polyline, 0.5f * float(flow.scaled_spacing()) + scaled_epsilon));
}

View file

@ -28,6 +28,8 @@ struct SurfaceFillParams
// coordf_t overlap = 0.;
// Angle as provided by the region config, in radians.
float angle = 0.f;
// Is bridging used for this fill? Bridging parameters may be used even if this->flow.bridge() is not set.
bool bridge;
// Non-negative for a bridge.
float bridge_angle = 0.f;
@ -42,7 +44,7 @@ struct SurfaceFillParams
// width, height of extrusion, nozzle diameter, is bridge
// For the output, for fill generator.
Flow flow = Flow(0.f, 0.f, 0.f, false);
Flow flow;
// For the output
ExtrusionRole extrusion_role = ExtrusionRole(0);
@ -70,21 +72,22 @@ struct SurfaceFillParams
// RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
RETURN_COMPARE_NON_EQUAL(anchor_length);
RETURN_COMPARE_NON_EQUAL(anchor_length_max);
RETURN_COMPARE_NON_EQUAL(flow.width);
RETURN_COMPARE_NON_EQUAL(flow.height);
RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, flow.bridge);
RETURN_COMPARE_NON_EQUAL(flow.width());
RETURN_COMPARE_NON_EQUAL(flow.height());
RETURN_COMPARE_NON_EQUAL(flow.nozzle_diameter());
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, bridge);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, extrusion_role);
return false;
}
bool operator==(const SurfaceFillParams &rhs) const {
return this->extruder == rhs.extruder &&
this->pattern == rhs.pattern &&
this->pattern == rhs.pattern &&
this->spacing == rhs.spacing &&
// this->overlap == rhs.overlap &&
this->angle == rhs.angle &&
this->bridge == rhs.bridge &&
// this->bridge_angle == rhs.bridge_angle &&
this->density == rhs.density &&
// this->dont_adjust == rhs.dont_adjust &&
this->anchor_length == rhs.anchor_length &&
@ -128,6 +131,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
if (surface.is_solid()) {
params.density = 100.f;
//FIXME for non-thick bridges, shall we allow a bottom surface pattern?
params.pattern = (surface.is_external() && ! is_bridge) ?
(surface.is_top() ? region_config.top_fill_pattern.value : region_config.bottom_fill_pattern.value) :
region_config.top_fill_pattern == ipMonotonic ? ipMonotonic : ipRectilinear;
@ -143,17 +147,13 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
params.bridge_angle = float(surface.bridge_angle);
params.angle = float(Geometry::deg2rad(region_config.fill_angle.value));
// calculate the actual flow we'll be using for this infill
params.flow = layerm.region()->flow(
extrusion_role,
(surface.thickness == -1) ? layer.height : surface.thickness, // extrusion height
is_bridge || Fill::use_bridge_flow(params.pattern), // bridge flow?
layer.id() == 0, // first layer?
-1, // auto width
*layer.object()
);
// Calculate flow spacing for infill pattern generation.
// Calculate the actual flow we'll be using for this infill.
params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern);
params.flow = params.bridge ?
layerm.bridging_flow(extrusion_role) :
layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness);
// Calculate flow spacing for infill pattern generation.
if (surface.is_solid() || is_bridge) {
params.spacing = params.flow.spacing();
// Don't limit anchor length for solid or bridging infill.
@ -164,14 +164,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
// for all layers, for avoiding the ugly effect of
// misaligned infill on first layer because of different extrusion width and
// layer height
params.spacing = layerm.region()->flow(
frInfill,
layer.object()->config().layer_height.value, // TODO: handle infill_every_layers?
false, // no bridge
false, // no first layer
-1, // auto width
*layer.object()
).spacing();
params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing();
// Anchor a sparse infill to inner perimeters with the following anchor length:
params.anchor_length = float(region_config.infill_anchor);
if (region_config.infill_anchor.percent)
@ -278,7 +271,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
region_id = region_some_infill;
const LayerRegion& layerm = *layer.regions()[region_id];
for (SurfaceFill &surface_fill : surface_fills)
if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height) < EPSILON) {
if (surface_fill.surface.surface_type == stInternalSolid && std::abs(layer.height - surface_fill.params.flow.height()) < EPSILON) {
internal_solid_fill = &surface_fill;
break;
}
@ -290,14 +283,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer)
params.extrusion_role = erInternalInfill;
params.angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
// calculate the actual flow we'll be using for this infill
params.flow = layerm.region()->flow(
frSolidInfill,
layer.height, // extrusion height
false, // bridge flow?
layer.id() == 0, // first layer?
-1, // auto width
*layer.object()
);
params.flow = layerm.flow(frSolidInfill);
params.spacing = params.flow.spacing();
surface_fills.emplace_back(params);
surface_fills.back().surface.surface_type = stInternalSolid;
@ -365,9 +351,9 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
f->adapt_fill_octree = (surface_fill.params.pattern == ipSupportCubic) ? support_fill_octree : adaptive_fill_octree;
// calculate flow spacing for infill pattern generation
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.bridge;
double link_max_length = 0.;
if (! surface_fill.params.flow.bridge) {
if (! surface_fill.params.bridge) {
#if 0
link_max_length = layerm.region()->config().get_abs_value(surface.is_external() ? "external_fill_link_max_length" : "fill_link_max_length", flow.spacing());
// printf("flow spacing: %f, is_external: %d, link_max_length: %lf\n", flow.spacing(), int(surface.is_external()), link_max_length);
@ -380,7 +366,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// Maximum length of the perimeter segment linking two infill lines.
f->link_max_length = (coord_t)scale_(link_max_length);
// Used by the concentric infill pattern to clip the loops to create extrusion paths.
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
f->loop_clipping = coord_t(scale_(surface_fill.params.flow.nozzle_diameter()) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
// apply half spacing using this flow's own spacing and generate infill
FillParams params;
@ -402,15 +388,15 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
double flow_mm3_per_mm = surface_fill.params.flow.mm3_per_mm();
double flow_width = surface_fill.params.flow.width;
double flow_width = surface_fill.params.flow.width();
if (using_internal_flow) {
// if we used the internal flow we're not doing a solid infill
// so we can safely ignore the slight variation that might have
// been applied to f->spacing
} else {
Flow new_flow = Flow::new_from_spacing(float(f->spacing), surface_fill.params.flow.nozzle_diameter, surface_fill.params.flow.height, surface_fill.params.flow.bridge);
Flow new_flow = surface_fill.params.flow.with_spacing(float(f->spacing));
flow_mm3_per_mm = new_flow.mm3_per_mm();
flow_width = new_flow.width;
flow_width = new_flow.width();
}
// Save into layer.
ExtrusionEntityCollection* eec = nullptr;
@ -420,7 +406,7 @@ void Layer::make_fills(FillAdaptive::Octree* adaptive_fill_octree, FillAdaptive:
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
surface_fill.params.extrusion_role,
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height);
flow_mm3_per_mm, float(flow_width), surface_fill.params.flow.height());
}
}
}
@ -618,9 +604,9 @@ void Layer::make_ironing()
fill.spacing = ironing_params.line_spacing;
fill.angle = float(ironing_params.angle + 0.25 * M_PI);
fill.link_max_length = (coord_t)scale_(3. * fill.spacing);
double height = ironing_params.height * fill.spacing / nozzle_dmr;
Flow flow = Flow::new_from_spacing(float(nozzle_dmr), 0., float(height), false);
double flow_mm3_per_mm = flow.mm3_per_mm();
double extrusion_height = ironing_params.height * fill.spacing / nozzle_dmr;
float extrusion_width = Flow::rounded_rectangle_extrusion_width_from_spacing(float(nozzle_dmr), float(extrusion_height));
double flow_mm3_per_mm = nozzle_dmr * extrusion_height;
Surface surface_fill(stTop, ExPolygon());
for (ExPolygon &expoly : ironing_areas) {
surface_fill.expolygon = std::move(expoly);
@ -638,7 +624,7 @@ void Layer::make_ironing()
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
erIroning,
flow_mm3_per_mm, float(flow.width), float(height));
flow_mm3_per_mm, extrusion_width, float(extrusion_height));
}
}
}

View file

@ -122,20 +122,13 @@ double Flow::extrusion_width(const std::string& opt_key, const ConfigOptionResol
// This constructor builds a Flow object from an extrusion width config setting
// and other context properties.
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio)
Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height)
{
// we need layer height unless it's a bridge
if (height <= 0 && bridge_flow_ratio == 0)
if (height <= 0)
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_config_width()");
float w;
if (bridge_flow_ratio > 0) {
// If bridge flow was requested, calculate the bridge width.
height = w = (bridge_flow_ratio == 1.) ?
// optimization to avoid sqrt()
nozzle_diameter :
sqrt(bridge_flow_ratio) * nozzle_diameter;
} else if (! width.percent && width.value == 0.) {
if (! width.percent && width.value == 0.) {
// If user left option to 0, calculate a sane default width.
w = auto_extrusion_width(role, nozzle_diameter);
} else {
@ -143,71 +136,89 @@ Flow Flow::new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent
w = float(width.get_abs_value(height));
}
return Flow(w, height, nozzle_diameter, bridge_flow_ratio > 0);
return Flow(w, height, rounded_rectangle_extrusion_spacing(w, height), nozzle_diameter, false);
}
// This constructor builds a Flow object from a given centerline spacing.
Flow Flow::new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge)
// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
Flow Flow::with_spacing(float new_spacing) const
{
// we need layer height unless it's a bridge
if (height <= 0 && !bridge)
throw Slic3r::InvalidArgument("Invalid flow height supplied to new_from_spacing()");
// Calculate width from spacing.
// For normal extrusons, extrusion width is wider than the spacing due to the rounding and squishing of the extrusions.
// For bridge extrusions, the extrusions are placed with a tiny BRIDGE_EXTRA_SPACING gaps between the threads.
float width = float(bridge ?
(spacing - BRIDGE_EXTRA_SPACING) :
#ifdef HAS_PERIMETER_LINE_OVERLAP
(spacing + PERIMETER_LINE_OVERLAP_FACTOR * height * (1. - 0.25 * PI));
#else
(spacing + height * (1. - 0.25 * PI)));
#endif
return Flow(width, bridge ? width : height, nozzle_diameter, bridge);
Flow out = *this;
if (m_bridge) {
// Diameter of the rounded extrusion.
assert(m_width == m_height);
float gap = m_spacing - m_width;
auto new_diameter = new_spacing - gap;
out.m_width = out.m_height = new_diameter;
} else {
assert(m_width >= m_height);
out.m_width += new_spacing - m_spacing;
if (out.m_width < out.m_height)
throw Slic3r::InvalidArgument("Invalid spacing supplied to Flow::with_spacing()");
}
out.m_spacing = new_spacing;
return out;
}
// This method returns the centerline spacing between two adjacent extrusions
// having the same extrusion width (and other properties).
float Flow::spacing() const
// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
Flow Flow::with_cross_section(float area_new) const
{
#ifdef HAS_PERIMETER_LINE_OVERLAP
if (this->bridge)
return this->width + BRIDGE_EXTRA_SPACING;
// rectangle with semicircles at the ends
float min_flow_spacing = this->width - this->height * (1. - 0.25 * PI);
float res = this->width - PERIMETER_LINE_OVERLAP_FACTOR * (this->width - min_flow_spacing);
#else
float res = float(this->bridge ? (this->width + BRIDGE_EXTRA_SPACING) : (this->width - this->height * (1. - 0.25 * PI)));
#endif
// assert(res > 0.f);
if (res <= 0.f)
throw FlowErrorNegativeSpacing();
return res;
assert(! m_bridge);
assert(m_width >= m_height);
// Adjust for bridge_flow_ratio, maintain the extrusion spacing.
float area = this->mm3_per_mm();
if (area_new > area + EPSILON) {
// Increasing the flow rate.
float new_full_spacing = area_new / m_height;
if (new_full_spacing > m_spacing) {
// Filling up the spacing without an air gap. Grow the extrusion in height.
float height = area_new / m_spacing;
return Flow(rounded_rectangle_extrusion_width_from_spacing(m_spacing, height), height, m_spacing, m_nozzle_diameter, false);
} else {
return this->with_width(rounded_rectangle_extrusion_width_from_spacing(area / m_height, m_height));
}
} else if (area_new < area - EPSILON) {
// Decreasing the flow rate.
float width_new = m_width - (area - area_new) / m_height;
assert(width_new > 0);
if (width_new > m_height) {
// Shrink the extrusion width.
return this->with_width(width_new);
} else {
// Create a rounded extrusion.
auto dmr = float(sqrt(area_new / M_PI));
return Flow(dmr, dmr, m_spacing, m_nozzle_diameter, false);
}
} else
return *this;
}
// This method returns the centerline spacing between an extrusion using this
// flow and another one using another flow.
// this->spacing(other) shall return the same value as other.spacing(*this)
float Flow::spacing(const Flow &other) const
float Flow::rounded_rectangle_extrusion_spacing(float width, float height)
{
assert(this->height == other.height);
assert(this->bridge == other.bridge);
float res = float(this->bridge ?
0.5 * this->width + 0.5 * other.width + BRIDGE_EXTRA_SPACING :
0.5 * this->spacing() + 0.5 * other.spacing());
// assert(res > 0.f);
if (res <= 0.f)
throw FlowErrorNegativeSpacing();
return res;
auto out = width - height * float(1. - 0.25 * PI);
if (out <= 0.f)
throw FlowErrorNegativeSpacing();
return out;
}
float Flow::rounded_rectangle_extrusion_width_from_spacing(float spacing, float height)
{
return float(spacing + height * (1. - 0.25 * PI));
}
float Flow::bridge_extrusion_spacing(float dmr)
{
return dmr + BRIDGE_EXTRA_SPACING;
}
// This method returns extrusion volume per head move unit.
double Flow::mm3_per_mm() const
double Flow::mm3_per_mm() const
{
float res = this->bridge ?
float res = m_bridge ?
// Area of a circle with dmr of this->width.
float((this->width * this->width) * 0.25 * PI) :
float((m_width * m_width) * 0.25 * PI) :
// Rectangle with semicircles at the ends. ~ h (w - 0.215 h)
float(this->height * (this->width - this->height * (1. - 0.25 * PI)));
float(m_height * (m_width - m_height * (1. - 0.25 * PI)));
//assert(res > 0.);
if (res <= 0.)
throw FlowErrorNegativeFlow();
@ -222,9 +233,7 @@ Flow support_material_flow(const PrintObject *object, float layer_height)
(object->config().support_material_extrusion_width.value > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
// if object->config().support_material_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
// bridge_flow_ratio
0.f);
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height)
@ -235,9 +244,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig
// The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution.
(width.value > 0) ? width : object->config().extrusion_width,
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)),
// bridge_flow_ratio
0.f);
(layer_height > 0.f) ? layer_height : float(object->config().first_layer_height.get_abs_value(object->config().layer_height.value)));
}
Flow support_material_interface_flow(const PrintObject *object, float layer_height)
@ -248,9 +255,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig
(object->config().support_material_extrusion_width > 0) ? object->config().support_material_extrusion_width : object->config().extrusion_width,
// if object->config().support_material_interface_extruder == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component.
float(object->print()->config().nozzle_diameter.get_at(object->config().support_material_interface_extruder-1)),
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value),
// bridge_flow_ratio
0.f);
(layer_height > 0.f) ? layer_height : float(object->config().layer_height.value));
}
}

View file

@ -13,11 +13,6 @@ class PrintObject;
// Extra spacing of bridge threads, in mm.
#define BRIDGE_EXTRA_SPACING 0.05
// Overlap factor of perimeter lines. Currently no overlap.
#ifdef HAS_PERIMETER_LINE_OVERLAP
#define PERIMETER_LINE_OVERLAP_FACTOR 1.0
#endif
enum FlowRole {
frExternalPerimeter,
frPerimeter,
@ -56,26 +51,26 @@ public:
class Flow
{
public:
Flow() = default;
Flow(float width, float height, float nozzle_diameter) :
Flow(width, height, rounded_rectangle_extrusion_spacing(width, height), nozzle_diameter, false) {}
// Non bridging flow: Maximum width of an extrusion with semicircles at the ends.
// Bridging flow: Bridge thread diameter.
float width;
float width() const { return m_width; }
coord_t scaled_width() const { return coord_t(scale_(m_width)); }
// Non bridging flow: Layer height.
// Bridging flow: Bridge thread diameter = layer height.
float height;
float height() const { return m_height; }
// Spacing between the extrusion centerlines.
float spacing() const { return m_spacing; }
coord_t scaled_spacing() const { return coord_t(scale_(m_spacing)); }
// Nozzle diameter.
float nozzle_diameter;
float nozzle_diameter() const { return m_nozzle_diameter; }
// Is it a bridge?
bool bridge;
Flow(float _w, float _h, float _nd, bool _bridge = false) :
width(_w), height(_h), nozzle_diameter(_nd), bridge(_bridge) {}
float spacing() const;
float spacing(const Flow &other) const;
double mm3_per_mm() const;
coord_t scaled_width() const { return coord_t(scale_(this->width)); }
coord_t scaled_spacing() const { return coord_t(scale_(this->spacing())); }
coord_t scaled_spacing(const Flow &other) const { return coord_t(scale_(this->spacing(other))); }
bool bridge() const { return m_bridge; }
// Cross section area of the extrusion.
double mm3_per_mm() const;
// Elephant foot compensation spacing to be used to detect narrow parts, where the elephant foot compensation cannot be applied.
// To be used on frExternalPerimeter only.
@ -83,13 +78,32 @@ public:
// Here an overlap of 0.2x external perimeter spacing is allowed for by the elephant foot compensation.
coord_t scaled_elephant_foot_spacing() const { return coord_t(0.5f * float(this->scaled_width() + 0.6f * this->scaled_spacing())); }
bool operator==(const Flow &rhs) const { return this->width == rhs.width && this->height == rhs.height && this->nozzle_diameter == rhs.nozzle_diameter && this->bridge == rhs.bridge; }
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height, float bridge_flow_ratio);
// Create a flow from the spacing of extrusion lines.
// This method is used exclusively to calculate new flow of 100% infill, where the extrusion width was allowed to scale
// to fit a region with integer number of lines.
static Flow new_from_spacing(float spacing, float nozzle_diameter, float height, bool bridge);
bool operator==(const Flow &rhs) const { return m_width == rhs.m_width && m_height == rhs.m_height && m_nozzle_diameter == rhs.m_nozzle_diameter && m_bridge == rhs.m_bridge; }
Flow with_width (float width) const {
assert(! m_bridge);
return Flow(width, m_height, rounded_rectangle_extrusion_spacing(width, m_height), m_nozzle_diameter, m_bridge);
}
Flow with_height(float height) const {
assert(! m_bridge);
return Flow(m_width, height, rounded_rectangle_extrusion_spacing(m_width, height), m_nozzle_diameter, m_bridge);
}
// Adjust extrusion flow for new extrusion line spacing, maintaining the old spacing between extrusions.
Flow with_spacing(float spacing) const;
// Adjust the width / height of a rounded extrusion model to reach the prescribed cross section area while maintaining extrusion spacing.
Flow with_cross_section(float area) const;
Flow with_flow_ratio(double ratio) const { return this->with_cross_section(this->mm3_per_mm() * ratio); }
static Flow bridging_flow(float dmr, float nozzle_diameter) { return Flow { dmr, dmr, bridge_extrusion_spacing(dmr), nozzle_diameter, true }; }
static Flow new_from_config_width(FlowRole role, const ConfigOptionFloatOrPercent &width, float nozzle_diameter, float height);
// Spacing of extrusions with rounded extrusion model.
static float rounded_rectangle_extrusion_spacing(float width, float height);
// Width of extrusions with rounded extrusion model.
static float rounded_rectangle_extrusion_width_from_spacing(float spacing, float height);
// Spacing of round thread extrusions.
static float bridge_extrusion_spacing(float dmr);
// Sane extrusion width defautl based on nozzle diameter.
// The defaults were derived from manual Prusa MK3 profiles.
@ -100,6 +114,20 @@ public:
// on active extruder etc. Therefore the value calculated by this function shall be used as a hint only.
static double extrusion_width(const std::string &opt_key, const ConfigOptionFloatOrPercent *opt, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
static double extrusion_width(const std::string &opt_key, const ConfigOptionResolver &config, const unsigned int first_printing_extruder = 0);
private:
Flow(float width, float height, float spacing, float nozzle_diameter, bool bridge) :
m_width(width), m_height(height), m_spacing(spacing), m_nozzle_diameter(nozzle_diameter), m_bridge(bridge)
{
// Gap fill violates this condition.
//assert(width >= height);
}
float m_width { 0 };
float m_height { 0 };
float m_spacing { 0 };
float m_nozzle_diameter { 0 };
bool m_bridge { false };
};
extern Flow support_material_flow(const PrintObject *object, float layer_height = 0.f);

View file

@ -2014,9 +2014,10 @@ namespace Slic3r {
typedef std::map<int, ObjectData> IdToObjectDataMap;
bool m_fullpath_sources{ true };
bool m_zip64 { true };
public:
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
bool save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64);
private:
bool _save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, const ThumbnailData* thumbnail_data);
@ -2036,10 +2037,11 @@ namespace Slic3r {
bool _add_custom_gcode_per_print_z_file_to_archive(mz_zip_archive& archive, Model& model, const DynamicPrintConfig* config);
};
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
bool _3MF_Exporter::save_model_to_file(const std::string& filename, Model& model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64)
{
clear_errors();
m_fullpath_sources = fullpath_sources;
m_zip64 = zip64;
return _save_model_to_file(filename, model, config, thumbnail_data);
}
@ -2233,9 +2235,13 @@ namespace Slic3r {
{
mz_zip_writer_staged_context context;
if (!mz_zip_writer_add_staged_open(&archive, &context, MODEL_FILE.c_str(),
// Maximum expected and allowed 3MF file size is 16GiB.
// This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
(uint64_t(1) << 30) * 16,
m_zip64 ?
// Maximum expected and allowed 3MF file size is 16GiB.
// This switches the ZIP file to a 64bit mode, which adds a tiny bit of overhead to file records.
(uint64_t(1) << 30) * 16 :
// Maximum expected 3MF file size is 4GB-1. This is a workaround for interoperability with Windows 10 3D model fixing API, see
// GH issue #6193.
(uint64_t(1) << 32) - 1,
nullptr, nullptr, 0, MZ_DEFAULT_COMPRESSION, nullptr, 0, nullptr, 0)) {
add_error("Unable to add model file to archive");
return false;
@ -2926,13 +2932,13 @@ bool load_3mf(const char* path, DynamicPrintConfig* config, Model* model, bool c
return res;
}
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data)
bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data, bool zip64)
{
if (path == nullptr || model == nullptr)
return false;
_3MF_Exporter exporter;
bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data);
bool res = exporter.save_model_to_file(path, *model, config, fullpath_sources, thumbnail_data, zip64);
if (!res)
exporter.log_errors();

View file

@ -33,7 +33,7 @@ namespace Slic3r {
// Save the given model and the config data contained in the given Print into a 3mf file.
// The model could be modified during the export process if meshes are not repaired or have no shared vertices
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr);
extern bool store_3mf(const char* path, Model* model, const DynamicPrintConfig* config, bool fullpath_sources, const ThumbnailData* thumbnail_data = nullptr, bool zip64 = true);
} // namespace Slic3r

View file

@ -15,6 +15,7 @@
#include <algorithm>
#include <cstdlib>
#include <chrono>
#include <math.h>
#include <string_view>
@ -1113,15 +1114,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
const double layer_height = first_object->config().layer_height.value;
const double first_layer_height = first_object->config().first_layer_height.get_abs_value(layer_height);
for (const PrintRegion* region : print.regions()) {
_write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(frExternalPerimeter, layer_height, false, false, -1., *first_object).width);
_write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(frPerimeter, layer_height, false, false, -1., *first_object).width);
_write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(frInfill, layer_height, false, false, -1., *first_object).width);
_write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(frSolidInfill, layer_height, false, false, -1., *first_object).width);
_write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(frTopSolidInfill, layer_height, false, false, -1., *first_object).width);
_write_format(file, "; external perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frExternalPerimeter, layer_height).width());
_write_format(file, "; perimeters extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, layer_height).width());
_write_format(file, "; infill extrusion width = %.2fmm\n", region->flow(*first_object, frInfill, layer_height).width());
_write_format(file, "; solid infill extrusion width = %.2fmm\n", region->flow(*first_object, frSolidInfill, layer_height).width());
_write_format(file, "; top infill extrusion width = %.2fmm\n", region->flow(*first_object, frTopSolidInfill, layer_height).width());
if (print.has_support_material())
_write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width);
_write_format(file, "; support material extrusion width = %.2fmm\n", support_material_flow(first_object).width());
if (print.config().first_layer_extrusion_width.value > 0)
_write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(frPerimeter, first_layer_height, false, true, -1., *first_object).width);
_write_format(file, "; first layer extrusion width = %.2fmm\n", region->flow(*first_object, frPerimeter, first_layer_height, true).width());
_write_format(file, "\n");
}
print.throw_if_canceled();
@ -1137,6 +1138,7 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
// Prepare the helper object for replacing placeholders in custom G-code and output filename.
m_placeholder_parser = print.placeholder_parser();
m_placeholder_parser.update_timestamp();
m_placeholder_parser_context.rng = std::mt19937(std::chrono::high_resolution_clock::now().time_since_epoch().count());
print.update_object_placeholders(m_placeholder_parser.config_writable(), ".gcode");
// Get optimal tool ordering to minimize tool switches of a multi-exruder print.
@ -1823,7 +1825,8 @@ namespace Skirt {
// Extrude skirt at the print_z of the raft layers and normal object layers
// not at the print_z of the interlaced support material layers.
std::map<unsigned int, std::pair<size_t, size_t>> skirt_loops_per_extruder_out;
assert(skirt_done.empty());
//For sequential print, the following test may fail when extruding the 2nd and other objects.
// assert(skirt_done.empty());
if (skirt_done.empty() && print.has_skirt() && ! print.skirt().entities.empty() && layer_tools.has_skirt) {
skirt_loops_per_extruder_all_printing(print, layer_tools, skirt_loops_per_extruder_out);
skirt_done.emplace_back(layer_tools.print_z);
@ -2178,14 +2181,13 @@ void GCode::process_layer(
const std::pair<size_t, size_t> loops = loops_it->second;
this->set_origin(0., 0.);
m_avoid_crossing_perimeters.use_external_mp();
Flow layer_skirt_flow(print.skirt_flow());
layer_skirt_flow.height = float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2]));
Flow layer_skirt_flow = print.skirt_flow().with_height(float(m_skirt_done.back() - (m_skirt_done.size() == 1 ? 0. : m_skirt_done[m_skirt_done.size() - 2])));
double mm3_per_mm = layer_skirt_flow.mm3_per_mm();
for (size_t i = loops.first; i < loops.second; ++i) {
// Adjust flow according to this layer's layer height.
ExtrusionLoop loop = *dynamic_cast<const ExtrusionLoop*>(print.skirt().entities[i]);
for (ExtrusionPath &path : loop.paths) {
path.height = layer_skirt_flow.height;
path.height = layer_skirt_flow.height();
path.mm3_per_mm = mm3_per_mm;
}
//FIXME using the support_material_speed of the 1st object printed.

View file

@ -59,7 +59,10 @@ public:
// (this collection contains only ExtrusionEntityCollection objects)
ExtrusionEntityCollection fills;
Flow flow(FlowRole role, bool bridge = false, double width = -1) const;
Flow flow(FlowRole role) const;
Flow flow(FlowRole role, double layer_height) const;
Flow bridging_flow(FlowRole role) const;
void slices_to_fill_surfaces_clipped();
void prepare_fill_surfaces();
void make_perimeters(const SurfaceCollection &slices, SurfaceCollection* fill_surfaces);

View file

@ -15,16 +15,31 @@
namespace Slic3r {
Flow LayerRegion::flow(FlowRole role, bool bridge, double width) const
Flow LayerRegion::flow(FlowRole role) const
{
return m_region->flow(
role,
m_layer->height,
bridge,
m_layer->id() == 0,
width,
*m_layer->object()
);
return this->flow(role, m_layer->height);
}
Flow LayerRegion::flow(FlowRole role, double layer_height) const
{
return m_region->flow(*m_layer->object(), role, layer_height, m_layer->id() == 0);
}
Flow LayerRegion::bridging_flow(FlowRole role) const
{
const PrintRegion &region = *this->region();
const PrintRegionConfig &region_config = region.config();
if (this->layer()->object()->config().thick_bridges) {
// The old Slic3r way (different from all other slicers): Use rounded extrusions.
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
auto nozzle_diameter = float(region.print()->config().nozzle_diameter.get_at(region.extruder(role) - 1));
// Applies default bridge spacing.
return Flow::bridging_flow(float(sqrt(region_config.bridge_flow_ratio)) * nozzle_diameter, nozzle_diameter);
} else {
// The same way as other slicers: Use normal extrusions. Apply bridge_flow_ratio while maintaining the original spacing.
return this->flow(role).with_flow_ratio(region_config.bridge_flow_ratio);
}
}
// Fill in layerm->fill_surfaces by trimming the layerm->slices by the cummulative layerm->fill_surfaces.
@ -84,7 +99,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, SurfaceCollec
g.layer_id = (int)this->layer()->id();
g.ext_perimeter_flow = this->flow(frExternalPerimeter);
g.overhang_flow = this->region()->flow(frPerimeter, -1, true, false, -1, *this->layer()->object());
g.overhang_flow = this->bridging_flow(frPerimeter);
g.solid_infill_flow = this->flow(frSolidInfill);
g.process();
@ -266,11 +281,7 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly
// would get merged into a single one while they need different directions
// also, supply the original expolygon instead of the grown one, because in case
// of very thin (but still working) anchors, the grown expolygon would go beyond them
BridgeDetector bd(
initial,
lower_layer->lslices,
this->flow(frInfill, true).scaled_width()
);
BridgeDetector bd(initial, lower_layer->lslices, this->bridging_flow(frInfill).scaled_width());
#ifdef SLIC3R_DEBUG
printf("Processing bridge at layer %zu:\n", this->layer()->id());
#endif

View file

@ -1302,52 +1302,54 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
void ModelObject::split(ModelObjectPtrs* new_objects)
{
if (this->volumes.size() > 1) {
// We can't split meshes if there's more than one volume, because
// we can't group the resulting meshes by object afterwards
new_objects->emplace_back(this);
return;
}
ModelVolume* volume = this->volumes.front();
TriangleMeshPtrs meshptrs = volume->mesh().split();
size_t counter = 1;
for (TriangleMesh *mesh : meshptrs) {
for (ModelVolume* volume : this->volumes) {
if (volume->type() != ModelVolumeType::MODEL_PART)
continue;
// FIXME: crashes if not satisfied
if (mesh->facets_count() < 3) continue;
TriangleMeshPtrs meshptrs = volume->mesh().split();
size_t counter = 1;
for (TriangleMesh* mesh : meshptrs) {
mesh->repair();
// XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
ModelObject* new_object = m_model->add_object();
new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : "");
// FIXME: crashes if not satisfied
if (mesh->facets_count() < 3) continue;
// Don't copy the config's ID.
new_object->config.assign_config(this->config);
assert(new_object->config.id().valid());
assert(new_object->config.id() != this->config.id());
new_object->instances.reserve(this->instances.size());
for (const ModelInstance *model_instance : this->instances)
new_object->add_instance(*model_instance);
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh));
mesh->repair();
for (ModelInstance* model_instance : new_object->instances)
{
Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
model_instance->set_offset(model_instance->get_offset() + shift);
// XXX: this seems to be the only real usage of m_model, maybe refactor this so that it's not needed?
ModelObject* new_object = m_model->add_object();
if (meshptrs.size() == 1) {
new_object->name = volume->name;
// Don't copy the config's ID.
new_object->config.assign_config(this->config.size() > 0 ? this->config : volume->config);
}
else {
new_object->name = this->name + (meshptrs.size() > 1 ? "_" + std::to_string(counter++) : "");
// Don't copy the config's ID.
new_object->config.assign_config(this->config);
}
assert(new_object->config.id().valid());
assert(new_object->config.id() != this->config.id());
new_object->instances.reserve(this->instances.size());
for (const ModelInstance* model_instance : this->instances)
new_object->add_instance(*model_instance);
ModelVolume* new_vol = new_object->add_volume(*volume, std::move(*mesh));
for (ModelInstance* model_instance : new_object->instances)
{
Vec3d shift = model_instance->get_transformation().get_matrix(true) * new_vol->get_offset();
model_instance->set_offset(model_instance->get_offset() + shift);
}
new_vol->set_offset(Vec3d::Zero());
// reset the source to disable reload from disk
new_vol->source = ModelVolume::Source();
new_objects->emplace_back(new_object);
delete mesh;
}
new_vol->set_offset(Vec3d::Zero());
// reset the source to disable reload from disk
new_vol->source = ModelVolume::Source();
new_objects->emplace_back(new_object);
delete mesh;
}
return;
}
void ModelObject::merge()
{
if (this->volumes.size() == 1) {
@ -1738,6 +1740,7 @@ size_t ModelVolume::split(unsigned int max_extruders)
this->object->volumes[ivolume]->translate(offset);
this->object->volumes[ivolume]->name = name + "_" + std::to_string(idx + 1);
this->object->volumes[ivolume]->config.set_deserialize("extruder", auto_extruder_id(max_extruders, extruder_counter));
this->object->volumes[ivolume]->m_is_splittable = 0;
delete mesh;
++ idx;
}

View file

@ -69,7 +69,7 @@ static bool clip_narrow_corner(
Vec2i64 p2 = it2->cast<int64_t>();
Vec2i64 p02;
Vec2i64 p22;
int64_t dist2_next;
int64_t dist2_next = 0;
// As long as there is at least a single triangle left in the polygon.
while (polygon.size() >= 3) {

View file

@ -22,74 +22,54 @@ namespace Slic3r {
class TriangleMeshDataAdapter {
public:
const TriangleMesh &mesh;
float voxel_scale;
size_t polygonCount() const { return mesh.its.indices.size(); }
size_t pointCount() const { return mesh.its.vertices.size(); }
size_t vertexCount(size_t) const { return 3; }
// Return position pos in local grid index space for polygon n and vertex v
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
// The actual mesh will appear to openvdb as scaled uniformly by voxel_size
// And the voxel count per unit volume can be affected this way.
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const
{
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>() * voxel_scale;
pos = {p.x(), p.y(), p.z()};
}
TriangleMeshDataAdapter(const TriangleMesh &m, float voxel_sc = 1.f)
: mesh{m}, voxel_scale{voxel_sc} {};
};
class Contour3DDataAdapter {
public:
const sla::Contour3D &mesh;
size_t polygonCount() const { return mesh.faces3.size() + mesh.faces4.size(); }
size_t pointCount() const { return mesh.points.size(); }
size_t vertexCount(size_t n) const { return n < mesh.faces3.size() ? 3 : 4; }
// Return position pos in local grid index space for polygon n and vertex v
void getIndexSpacePoint(size_t n, size_t v, openvdb::Vec3d& pos) const;
};
void TriangleMeshDataAdapter::getIndexSpacePoint(size_t n,
size_t v,
openvdb::Vec3d &pos) const
{
auto vidx = size_t(mesh.its.indices[n](Eigen::Index(v)));
Slic3r::Vec3d p = mesh.its.vertices[vidx].cast<double>();
pos = {p.x(), p.y(), p.z()};
}
void Contour3DDataAdapter::getIndexSpacePoint(size_t n,
size_t v,
openvdb::Vec3d &pos) const
{
size_t vidx = 0;
if (n < mesh.faces3.size()) vidx = size_t(mesh.faces3[n](Eigen::Index(v)));
else vidx = size_t(mesh.faces4[n - mesh.faces3.size()](Eigen::Index(v)));
Slic3r::Vec3d p = mesh.points[vidx];
pos = {p.x(), p.y(), p.z()};
}
// TODO: Do I need to call initialize? Seems to work without it as well but the
// docs say it should be called ones. It does a mutex lock-unlock sequence all
// even if was called previously.
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
const openvdb::math::Transform &tr,
float exteriorBandWidth,
float interiorBandWidth,
int flags)
float voxel_scale,
float exteriorBandWidth,
float interiorBandWidth,
int flags)
{
openvdb::initialize();
TriangleMeshPtrs meshparts = mesh.split();
TriangleMeshPtrs meshparts_raw = mesh.split();
auto meshparts = reserve_vector<std::unique_ptr<TriangleMesh>>(meshparts_raw.size());
for (auto *p : meshparts_raw)
meshparts.emplace_back(p);
auto it = std::remove_if(meshparts.begin(), meshparts.end(),
[](TriangleMesh *m){
m->require_shared_vertices();
return !m->is_manifold() || m->volume() < EPSILON;
});
auto it = std::remove_if(meshparts.begin(), meshparts.end(), [](auto &m) {
m->require_shared_vertices();
return m->volume() < EPSILON;
});
meshparts.erase(it, meshparts.end());
openvdb::FloatGrid::Ptr grid;
for (TriangleMesh *m : meshparts) {
for (auto &m : meshparts) {
auto subgrid = openvdb::tools::meshToVolume<openvdb::FloatGrid>(
TriangleMeshDataAdapter{*m}, tr, exteriorBandWidth,
TriangleMeshDataAdapter{*m, voxel_scale}, tr, exteriorBandWidth,
interiorBandWidth, flags);
if (grid && subgrid) openvdb::tools::csgUnion(*grid, *subgrid);
@ -106,19 +86,9 @@ openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh &mesh,
interiorBandWidth, flags);
}
return grid;
}
grid->insertMeta("voxel_scale", openvdb::FloatMetadata(voxel_scale));
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D &mesh,
const openvdb::math::Transform &tr,
float exteriorBandWidth,
float interiorBandWidth,
int flags)
{
openvdb::initialize();
return openvdb::tools::meshToVolume<openvdb::FloatGrid>(
Contour3DDataAdapter{mesh}, tr, exteriorBandWidth, interiorBandWidth,
flags);
return grid;
}
template<class Grid>
@ -128,20 +98,25 @@ sla::Contour3D _volumeToMesh(const Grid &grid,
bool relaxDisorientedTriangles)
{
openvdb::initialize();
std::vector<openvdb::Vec3s> points;
std::vector<openvdb::Vec3I> triangles;
std::vector<openvdb::Vec4I> quads;
openvdb::tools::volumeToMesh(grid, points, triangles, quads, isovalue,
adaptivity, relaxDisorientedTriangles);
float scale = 1.;
try {
scale = grid.template metaValue<float>("voxel_scale");
} catch (...) { }
sla::Contour3D ret;
ret.points.reserve(points.size());
ret.faces3.reserve(triangles.size());
ret.faces4.reserve(quads.size());
for (auto &v : points) ret.points.emplace_back(to_vec3d(v));
for (auto &v : points) ret.points.emplace_back(to_vec3d(v) / scale);
for (auto &v : triangles) ret.faces3.emplace_back(to_vec3i(v));
for (auto &v : quads) ret.faces4.emplace_back(to_vec4i(v));
@ -166,9 +141,18 @@ sla::Contour3D grid_to_contour3d(const openvdb::FloatGrid &grid,
relaxDisorientedTriangles);
}
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid, double iso, double er, double ir)
openvdb::FloatGrid::Ptr redistance_grid(const openvdb::FloatGrid &grid,
double iso,
double er,
double ir)
{
return openvdb::tools::levelSetRebuild(grid, float(iso), float(er), float(ir));
auto new_grid = openvdb::tools::levelSetRebuild(grid, float(iso),
float(er), float(ir));
// Copies voxel_scale metadata, if it exists.
new_grid->insertMeta(*grid.deepCopyMeta());
return new_grid;
}
} // namespace Slic3r

View file

@ -21,14 +21,16 @@ inline Vec3d to_vec3d(const openvdb::Vec3s &v) { return to_vec3f(v).cast<double>
inline Vec3i to_vec3i(const openvdb::Vec3I &v) { return Vec3i{int(v[0]), int(v[1]), int(v[2])}; }
inline Vec4i to_vec4i(const openvdb::Vec4I &v) { return Vec4i{int(v[0]), int(v[1]), int(v[2]), int(v[3])}; }
// Here voxel_scale defines the scaling of voxels which affects the voxel count.
// 1.0 value means a voxel for every unit cube. 2 means the model is scaled to
// be 2x larger and the voxel count is increased by the increment in the scaled
// volume, thus 4 times. This kind a sampling accuracy selection is not
// achievable through the Transform parameter. (TODO: or is it?)
// The resulting grid will contain the voxel_scale in its metadata under the
// "voxel_scale" key to be used in grid_to_mesh function.
openvdb::FloatGrid::Ptr mesh_to_grid(const TriangleMesh & mesh,
const openvdb::math::Transform &tr = {},
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f,
int flags = 0);
openvdb::FloatGrid::Ptr mesh_to_grid(const sla::Contour3D & mesh,
const openvdb::math::Transform &tr = {},
float voxel_scale = 1.f,
float exteriorBandWidth = 3.0f,
float interiorBandWidth = 3.0f,
int flags = 0);

View file

@ -8,7 +8,7 @@
namespace Slic3r {
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, Flow &flow, const float tolerance)
static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thick_polyline, ExtrusionRole role, const Flow &flow, const float tolerance)
{
ExtrusionPaths paths;
ExtrusionPath path(role);
@ -62,15 +62,15 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
path.polyline.append(line.b);
// Convert from spacing to extrusion width based on the extrusion model
// of a square extrusion ended with semi circles.
flow.width = unscale<float>(w) + flow.height * float(1. - 0.25 * PI);
Flow new_flow = flow.with_width(unscale<float>(w) + flow.height() * float(1. - 0.25 * PI));
#ifdef SLIC3R_DEBUG
printf(" filling %f gap\n", flow.width);
#endif
path.mm3_per_mm = flow.mm3_per_mm();
path.width = flow.width;
path.height = flow.height;
path.mm3_per_mm = new_flow.mm3_per_mm();
path.width = new_flow.width();
path.height = new_flow.height();
} else {
thickness_delta = fabs(scale_(flow.width) - w);
thickness_delta = fabs(scale_(flow.width()) - w);
if (thickness_delta <= tolerance) {
// the width difference between this line and the current flow width is
// within the accepted tolerance
@ -88,7 +88,7 @@ static ExtrusionPaths thick_polyline_to_extrusion_paths(const ThickPolyline &thi
return paths;
}
static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, Flow flow, std::vector<ExtrusionEntity*> &out)
static void variable_width(const ThickPolylines& polylines, ExtrusionRole role, const Flow &flow, std::vector<ExtrusionEntity*> &out)
{
// This value determines granularity of adaptive width, as G-code does not allow
// variable extrusion within a single move; this value shall only affect the amount
@ -205,8 +205,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
paths,
intersection_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
role,
is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(),
is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width,
is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm(),
is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width(),
(float)perimeter_generator.layer_height);
// get overhang paths by checking what parts of this loop fall
@ -217,8 +217,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
diff_pl({ polygon }, perimeter_generator.lower_slices_polygons()),
erOverhangPerimeter,
perimeter_generator.mm3_per_mm_overhang(),
perimeter_generator.overhang_flow.width,
perimeter_generator.overhang_flow.height);
perimeter_generator.overhang_flow.width(),
perimeter_generator.overhang_flow.height());
// Reapply the nearest point search for starting point.
// We allow polyline reversal because Clipper may have randomly reversed polylines during clipping.
@ -226,8 +226,8 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime
} else {
ExtrusionPath path(role);
path.polyline = polygon.split_at_first_point();
path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm();
path.width = is_external ? perimeter_generator.ext_perimeter_flow.width : perimeter_generator.perimeter_flow.width;
path.mm3_per_mm = is_external ? perimeter_generator.ext_mm3_per_mm() : perimeter_generator.mm3_per_mm();
path.width = is_external ? perimeter_generator.ext_perimeter_flow.width() : perimeter_generator.perimeter_flow.width();
path.height = (float)perimeter_generator.layer_height;
paths.push_back(path);
}
@ -286,7 +286,7 @@ void PerimeterGenerator::process()
m_ext_mm3_per_mm = this->ext_perimeter_flow.mm3_per_mm();
coord_t ext_perimeter_width = this->ext_perimeter_flow.scaled_width();
coord_t ext_perimeter_spacing = this->ext_perimeter_flow.scaled_spacing();
coord_t ext_perimeter_spacing2 = this->ext_perimeter_flow.scaled_spacing(this->perimeter_flow);
coord_t ext_perimeter_spacing2 = scaled<coord_t>(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing()));
// overhang perimeters
m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm();
@ -346,7 +346,7 @@ void PerimeterGenerator::process()
if (this->config->thin_walls) {
// the following offset2 ensures almost nothing in @thin_walls is narrower than $min_width
// (actually, something larger than that still may exist due to mitering or other causes)
coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter / 3));
coord_t min_width = coord_t(scale_(this->ext_perimeter_flow.nozzle_diameter() / 3));
ExPolygons expp = offset2_ex(
// medial axis requires non-overlapping geometry
diff_ex(to_polygons(last),

View file

@ -428,9 +428,10 @@ const std::vector<std::string>& Preset::print_options()
"min_skirt_length", "brim_width", "brim_offset", "brim_type", "support_material", "support_material_auto", "support_material_threshold", "support_material_enforce_layers",
"raft_layers", "raft_first_layer_density", "raft_first_layer_expansion", "raft_contact_distance", "raft_expansion",
"support_material_pattern", "support_material_with_sheath", "support_material_spacing",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers",
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops", "support_material_contact_distance",
"support_material_buildplate_only", "dont_support_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"support_material_synchronize_layers", "support_material_angle", "support_material_interface_layers", "support_material_bottom_interface_layers",
"support_material_interface_pattern", "support_material_interface_spacing", "support_material_interface_contact_loops",
"support_material_contact_distance", "support_material_bottom_contact_distance",
"support_material_buildplate_only", "dont_support_bridges", "thick_bridges", "notes", "complete_objects", "extruder_clearance_radius",
"extruder_clearance_height", "gcode_comments", "gcode_label_objects", "output_filename_format", "post_process", "perimeter_extruder",
"infill_extruder", "solid_infill_extruder", "support_material_extruder", "support_material_interface_extruder",
"ooze_prevention", "standby_temperature_delta", "interface_shells", "extrusion_width", "first_layer_extrusion_width",

View file

@ -1326,7 +1326,8 @@ std::string Print::validate(std::string* warning) const
return L("The Wipe Tower is only supported for multiple objects if they have equal layer heights");
if (slicing_params.raft_layers() != slicing_params0.raft_layers())
return L("The Wipe Tower is only supported for multiple objects if they are printed over an equal number of raft layers");
if (object->config().support_material_contact_distance != m_objects.front()->config().support_material_contact_distance)
if (slicing_params0.gap_object_support != slicing_params.gap_object_support ||
slicing_params0.gap_support_object != slicing_params.gap_support_object)
return L("The Wipe Tower is only supported for multiple objects if they are printed with the same support_material_contact_distance");
if (! equal_layering(slicing_params, slicing_params0))
return L("The Wipe Tower is only supported for multiple objects if they are sliced equally.");
@ -1577,9 +1578,7 @@ Flow Print::brim_flow() const
frPerimeter,
width,
(float)m_config.nozzle_diameter.get_at(m_regions.front()->config().perimeter_extruder-1),
(float)this->skirt_first_layer_height(),
0
);
(float)this->skirt_first_layer_height());
}
Flow Print::skirt_flow() const
@ -1599,9 +1598,7 @@ Flow Print::skirt_flow() const
frPerimeter,
width,
(float)m_config.nozzle_diameter.get_at(m_objects.front()->config().support_material_extruder-1),
(float)this->skirt_first_layer_height(),
0
);
(float)this->skirt_first_layer_height());
}
bool Print::has_support_material() const
@ -1818,7 +1815,7 @@ void Print::_make_skirt()
ExtrusionPath(
erSkirt,
(float)mm3_per_mm, // this will be overridden at G-code export time
flow.width,
flow.width(),
(float)first_layer_height // this will be overridden at G-code export time
)));
eloop.paths.back().polyline = loop.split_at_first_point();

View file

@ -65,7 +65,7 @@ public:
const PrintRegionConfig& config() const { return m_config; }
// 1-based extruder identifier for this region and role.
unsigned int extruder(FlowRole role) const;
Flow flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const;
Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const;
// Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;
// Average diameter of nozzles participating on extruding this region.

View file

@ -1,4 +1,5 @@
#include "PrintConfig.hpp"
#include "Config.hpp"
#include "I18N.hpp"
#include <set>
@ -66,7 +67,7 @@ void PrintConfigDef::init_common_params()
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
def->mode = comExpert;
def->gui_type = "one_string";
def->gui_type = ConfigOptionDef::GUIType::one_string;
def->set_default_value(new ConfigOptionPoints());
def = this->add("layer_height", coFloat);
@ -116,7 +117,7 @@ void PrintConfigDef::init_common_params()
def = this->add("printhost_port", coString);
def->label = L("Printer");
def->tooltip = L("Name of the printer");
def->gui_type = "select_open";
def->gui_type = ConfigOptionDef::GUIType::select_open;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionString(""));
@ -568,7 +569,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionBool(true));
def = this->add("extruder", coInt);
def->gui_type = "i_enum_open";
def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
def->label = L("Extruder");
def->category = L("Extruders");
def->tooltip = L("The extruder to use (unless more specific extruder settings are specified). "
@ -606,7 +607,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("extruder_colour", coStrings);
def->label = L("Extruder Color");
def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
def->gui_type = "color";
def->gui_type = ConfigOptionDef::GUIType::color;
// Empty string means no color assigned yet.
def->set_default_value(new ConfigOptionStrings { "" });
@ -668,7 +669,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("filament_colour", coStrings);
def->label = L("Color");
def->tooltip = L("This is only used in the Slic3r interface as a visual help.");
def->gui_type = "color";
def->gui_type = ConfigOptionDef::GUIType::color;
def->set_default_value(new ConfigOptionStrings { "#29B2B2" });
def = this->add("filament_notes", coStrings);
@ -812,7 +813,7 @@ void PrintConfigDef::init_fff_params()
def = this->add("filament_type", coStrings);
def->label = L("Filament type");
def->tooltip = L("The filament material type for use in custom G-codes.");
def->gui_type = "f_enum_open";
def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->gui_flags = "show_value";
def->enum_values.push_back("PLA");
def->enum_values.push_back("PET");
@ -881,7 +882,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(45));
def = this->add("fill_density", coPercent);
def->gui_type = "f_enum_open";
def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->gui_flags = "show_value";
def->label = L("Fill density");
def->category = L("Infill");
@ -1168,7 +1169,7 @@ void PrintConfigDef::init_fff_params()
"Set this parameter to zero to disable anchoring perimeters connected to a single infill line.");
def->sidetext = L("mm or %");
def->ratio_over = "infill_extrusion_width";
def->gui_type = "f_enum_open";
def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->enum_values.push_back("0");
def->enum_values.push_back("1");
def->enum_values.push_back("2");
@ -1950,7 +1951,7 @@ void PrintConfigDef::init_fff_params()
#if 0
def = this->add("seam_preferred_direction", coFloat);
// def->gui_type = "slider";
// def->gui_type = ConfigOptionDef::GUIType::slider;
def->label = L("Direction");
def->sidetext = L("°");
def->full_label = L("Preferred direction of the seam");
@ -1960,7 +1961,7 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("seam_preferred_direction_jitter", coFloat);
// def->gui_type = "slider";
// def->gui_type = ConfigOptionDef::GUIType::slider;
def->label = L("Jitter");
def->sidetext = L("°");
def->full_label = L("Seam preferred direction jitter");
@ -2234,8 +2235,8 @@ void PrintConfigDef::init_fff_params()
def->set_default_value(new ConfigOptionBool(false));
def = this->add("support_material_contact_distance", coFloat);
def->gui_type = "f_enum_open";
def->label = L("Contact Z distance");
def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->label = L("Top contact Z distance");
def->category = L("Support material");
def->tooltip = L("The vertical distance between object and support material interface. "
"Setting this to 0 will also prevent Slic3r from using bridge flow and speed "
@ -2243,12 +2244,31 @@ void PrintConfigDef::init_fff_params()
def->sidetext = L("mm");
// def->min = 0;
def->enum_values.push_back("0");
def->enum_values.push_back("0.1");
def->enum_values.push_back("0.2");
def->enum_labels.push_back(L("0 (soluble)"));
def->enum_labels.push_back(L("0.1 (detachable)"));
def->enum_labels.push_back(L("0.2 (detachable)"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0.2));
def = this->add("support_material_bottom_contact_distance", coFloat);
def->gui_type = ConfigOptionDef::GUIType::f_enum_open;
def->label = L("Bottom contact Z distance");
def->category = L("Support material");
def->tooltip = L("The vertical distance between the object top surface and the support material interface. "
"If set to zero, support_material_contact_distance will be used for both top and bottom contact Z distances.");
def->sidetext = L("mm");
// def->min = 0;
def->enum_values.push_back("0");
def->enum_values.push_back("0.1");
def->enum_values.push_back("0.2");
def->enum_labels.push_back(L("same as top"));
def->enum_labels.push_back(L("0.1"));
def->enum_labels.push_back(L("0.2"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("support_material_enforce_layers", coInt);
def->label = L("Enforce support for the first");
def->category = L("Support material");
@ -2298,15 +2318,39 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(1));
def = this->add("support_material_interface_layers", coInt);
def->label = L("Interface layers");
auto support_material_interface_layers = def = this->add("support_material_interface_layers", coInt);
def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
def->label = L("Top interface layers");
def->category = L("Support material");
def->tooltip = L("Number of interface layers to insert between the object(s) and support material.");
def->sidetext = L("layers");
def->min = 0;
def->enum_values.push_back("0");
def->enum_values.push_back("1");
def->enum_values.push_back("2");
def->enum_values.push_back("3");
def->enum_labels.push_back(L("0 (off)"));
def->enum_labels.push_back(L("1 (light)"));
def->enum_labels.push_back(L("2 (default)"));
def->enum_labels.push_back(L("3 (heavy)"));
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(3));
def = this->add("support_material_bottom_interface_layers", coInt);
def->gui_type = ConfigOptionDef::GUIType::i_enum_open;
def->label = L("Bottom interface layers");
def->category = L("Support material");
def->tooltip = L("Number of interface layers to insert between the object(s) and support material. "
"Set to -1 to use support_material_interface_layers");
def->sidetext = L("layers");
def->min = -1;
def->enum_values.push_back("-1");
append(def->enum_values, support_material_interface_layers->enum_values);
def->enum_labels.push_back(L("same as top"));
append(def->enum_labels, support_material_interface_layers->enum_labels);
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(-1));
def = this->add("support_material_interface_spacing", coFloat);
def->label = L("Interface pattern spacing");
def->category = L("Support material");
@ -2415,6 +2459,13 @@ void PrintConfigDef::init_fff_params()
def->max = max_temp;
def->set_default_value(new ConfigOptionInts { 200 });
def = this->add("thick_bridges", coBool);
def->label = L("Thick bridges");
def->category = L("Layers and Perimeters");
def->tooltip = L("Print bridges with round extrusions.");
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionBool(true));
def = this->add("thin_walls", coBool);
def->label = L("Detect thin walls");
def->category = L("Layers and Perimeters");
@ -2823,7 +2874,7 @@ void PrintConfigDef::init_sla_params()
def = this->add("material_type", coString);
def->label = L("SLA material type");
def->tooltip = L("SLA material type");
def->gui_type = "f_enum_open"; // TODO: ???
def->gui_type = ConfigOptionDef::GUIType::f_enum_open; // TODO: ???
def->gui_flags = "show_value";
def->enum_values.push_back("Tough");
def->enum_values.push_back("Flexible");

View file

@ -503,12 +503,14 @@ public:
ConfigOptionFloat support_material_angle;
ConfigOptionBool support_material_buildplate_only;
ConfigOptionFloat support_material_contact_distance;
ConfigOptionFloat support_material_bottom_contact_distance;
ConfigOptionInt support_material_enforce_layers;
ConfigOptionInt support_material_extruder;
ConfigOptionFloatOrPercent support_material_extrusion_width;
ConfigOptionBool support_material_interface_contact_loops;
ConfigOptionInt support_material_interface_extruder;
ConfigOptionInt support_material_interface_layers;
ConfigOptionInt support_material_bottom_interface_layers;
// Spacing between interface lines (the hatching distance). Set zero to get a solid interface.
ConfigOptionFloat support_material_interface_spacing;
ConfigOptionFloatOrPercent support_material_interface_speed;
@ -522,6 +524,7 @@ public:
ConfigOptionInt support_material_threshold;
ConfigOptionBool support_material_with_sheath;
ConfigOptionFloatOrPercent support_material_xy_spacing;
ConfigOptionBool thick_bridges;
ConfigOptionFloat xy_size_compensation;
ConfigOptionBool wipe_into_objects;
@ -553,12 +556,14 @@ protected:
OPT_PTR(support_material_angle);
OPT_PTR(support_material_buildplate_only);
OPT_PTR(support_material_contact_distance);
OPT_PTR(support_material_bottom_contact_distance);
OPT_PTR(support_material_enforce_layers);
OPT_PTR(support_material_interface_contact_loops);
OPT_PTR(support_material_extruder);
OPT_PTR(support_material_extrusion_width);
OPT_PTR(support_material_interface_extruder);
OPT_PTR(support_material_interface_layers);
OPT_PTR(support_material_bottom_interface_layers);
OPT_PTR(support_material_interface_spacing);
OPT_PTR(support_material_interface_speed);
OPT_PTR(support_material_pattern);
@ -569,6 +574,7 @@ protected:
OPT_PTR(support_material_xy_spacing);
OPT_PTR(support_material_threshold);
OPT_PTR(support_material_with_sheath);
OPT_PTR(thick_bridges);
OPT_PTR(xy_size_compensation);
OPT_PTR(wipe_into_objects);
}

View file

@ -546,14 +546,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "extra_perimeters"
|| opt_key == "gap_fill_enabled"
|| opt_key == "gap_fill_speed"
|| opt_key == "overhangs"
|| opt_key == "first_layer_extrusion_width"
|| opt_key == "fuzzy_skin"
|| opt_key == "fuzzy_skin_thickness"
|| opt_key == "fuzzy_skin_point_dist"
|| opt_key == "perimeter_extrusion_width"
|| opt_key == "infill_overlap"
|| opt_key == "thin_walls"
|| opt_key == "external_perimeters_first") {
steps.emplace_back(posPerimeters);
} else if (
@ -585,7 +580,9 @@ bool PrintObject::invalidate_state_by_config_options(
|| opt_key == "support_material_enforce_layers"
|| opt_key == "support_material_extruder"
|| opt_key == "support_material_extrusion_width"
|| opt_key == "support_material_bottom_contact_distance"
|| opt_key == "support_material_interface_layers"
|| opt_key == "support_material_bottom_interface_layers"
|| opt_key == "support_material_interface_pattern"
|| opt_key == "support_material_interface_contact_loops"
|| opt_key == "support_material_interface_extruder"
@ -652,7 +649,13 @@ bool PrintObject::invalidate_state_by_config_options(
steps.emplace_back(posPrepareInfill);
} else if (
opt_key == "external_perimeter_extrusion_width"
|| opt_key == "perimeter_extruder") {
|| opt_key == "perimeter_extruder"
|| opt_key == "fuzzy_skin"
|| opt_key == "fuzzy_skin_thickness"
|| opt_key == "fuzzy_skin_point_dist"
|| opt_key == "overhangs"
|| opt_key == "thin_walls"
|| opt_key == "thick_bridges") {
steps.emplace_back(posPerimeters);
steps.emplace_back(posSupportMaterial);
} else if (opt_key == "bridge_flow_ratio") {
@ -1456,26 +1459,18 @@ void PrintObject::bridge_over_infill()
const PrintRegion &region = *m_print->regions()[region_id];
// skip bridging in case there are no voids
if (region.config().fill_density.value == 100) continue;
// get bridge flow
Flow bridge_flow = region.flow(
frSolidInfill,
-1, // layer height, not relevant for bridge flow
true, // bridge
false, // first layer
-1, // custom width, not relevant for bridge flow
*this
);
if (region.config().fill_density.value == 100)
continue;
for (LayerPtrs::iterator layer_it = m_layers.begin(); layer_it != m_layers.end(); ++ layer_it) {
// skip first layer
if (layer_it == m_layers.begin())
continue;
Layer* layer = *layer_it;
LayerRegion* layerm = layer->m_regions[region_id];
Layer *layer = *layer_it;
LayerRegion *layerm = layer->m_regions[region_id];
Flow bridge_flow = layerm->bridging_flow(frSolidInfill);
// extract the stInternalSolid surfaces that might be transformed into bridges
Polygons internal_solid;
layerm->fill_surfaces.filter_by_type(stInternalSolid, &internal_solid);
@ -1488,7 +1483,7 @@ void PrintObject::bridge_over_infill()
Polygons to_bridge_pp = internal_solid;
// iterate through lower layers spanned by bridge_flow
double bottom_z = layer->print_z - bridge_flow.height;
double bottom_z = layer->print_z - bridge_flow.height();
for (int i = int(layer_it - m_layers.begin()) - 1; i >= 0; --i) {
const Layer* lower_layer = m_layers[i];

View file

@ -18,31 +18,25 @@ unsigned int PrintRegion::extruder(FlowRole role) const
return extruder;
}
Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const
{
ConfigOptionFloatOrPercent config_width;
if (width != -1) {
// use the supplied custom width, if any
config_width.value = width;
config_width.percent = false;
// Get extrusion width from configuration.
// (might be an absolute value, or a percent value, or zero for auto)
if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
config_width = m_print->config().first_layer_extrusion_width;
} else if (role == frExternalPerimeter) {
config_width = m_config.external_perimeter_extrusion_width;
} else if (role == frPerimeter) {
config_width = m_config.perimeter_extrusion_width;
} else if (role == frInfill) {
config_width = m_config.infill_extrusion_width;
} else if (role == frSolidInfill) {
config_width = m_config.solid_infill_extrusion_width;
} else if (role == frTopSolidInfill) {
config_width = m_config.top_infill_extrusion_width;
} else {
// otherwise, get extrusion width from configuration
// (might be an absolute value, or a percent value, or zero for auto)
if (first_layer && m_print->config().first_layer_extrusion_width.value > 0) {
config_width = m_print->config().first_layer_extrusion_width;
} else if (role == frExternalPerimeter) {
config_width = m_config.external_perimeter_extrusion_width;
} else if (role == frPerimeter) {
config_width = m_config.perimeter_extrusion_width;
} else if (role == frInfill) {
config_width = m_config.infill_extrusion_width;
} else if (role == frSolidInfill) {
config_width = m_config.solid_infill_extrusion_width;
} else if (role == frTopSolidInfill) {
config_width = m_config.top_infill_extrusion_width;
} else {
throw Slic3r::InvalidArgument("Unknown role");
}
throw Slic3r::InvalidArgument("Unknown role");
}
if (config_width.value == 0)
@ -50,8 +44,8 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
// Get the configured nozzle_diameter for the extruder associated to the flow role requested.
// Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right.
double nozzle_diameter = m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1);
return Flow::new_from_config_width(role, config_width, (float)nozzle_diameter, (float)layer_height, bridge ? (float)m_config.bridge_flow_ratio : 0.0f);
auto nozzle_diameter = float(m_print->config().nozzle_diameter.get_at(this->extruder(role) - 1));
return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height));
}
coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const

View file

@ -5,6 +5,7 @@
#include <tbb/mutex.h>
#include <tbb/parallel_for.h>
#include <tbb/parallel_reduce.h>
#include <tbb/task_arena.h>
#include <algorithm>
#include <numeric>
@ -76,13 +77,18 @@ template<> struct _ccr<true>
from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; }, granularity);
}
static size_t max_concurreny()
{
return tbb::this_task_arena::max_concurrency();
}
};
template<> struct _ccr<false>
{
private:
struct _Mtx { inline void lock() {} inline void unlock() {} };
public:
using SpinningMutex = _Mtx;
using BlockingMutex = _Mtx;
@ -133,6 +139,8 @@ public:
return reduce(from, to, init, std::forward<MergeFn>(mergefn),
[](typename I::value_type &i) { return i; });
}
static size_t max_concurreny() { return 1; }
};
using ccr = _ccr<USE_FULL_CONCURRENCY>;

View file

@ -26,64 +26,99 @@ inline void _scale(S s, TriangleMesh &m) { m.scale(float(s)); }
template<class S, class = FloatingOnly<S>>
inline void _scale(S s, Contour3D &m) { for (auto &p : m.points) p *= s; }
static TriangleMesh _generate_interior(const TriangleMesh &mesh,
const JobController &ctl,
double min_thickness,
double voxel_scale,
double closing_dist)
struct Interior {
TriangleMesh mesh;
openvdb::FloatGrid::Ptr gridptr;
mutable std::optional<openvdb::FloatGrid::ConstAccessor> accessor;
double closing_distance = 0.;
double thickness = 0.;
double voxel_scale = 1.;
double nb_in = 3.; // narrow band width inwards
double nb_out = 3.; // narrow band width outwards
// Full narrow band is the sum of the two above values.
void reset_accessor() const // This resets the accessor and its cache
// Not a thread safe call!
{
if (gridptr)
accessor = gridptr->getConstAccessor();
}
};
void InteriorDeleter::operator()(Interior *p)
{
delete p;
}
TriangleMesh &get_mesh(Interior &interior)
{
return interior.mesh;
}
const TriangleMesh &get_mesh(const Interior &interior)
{
return interior.mesh;
}
static InteriorPtr generate_interior_verbose(const TriangleMesh & mesh,
const JobController &ctl,
double min_thickness,
double voxel_scale,
double closing_dist)
{
TriangleMesh imesh{mesh};
_scale(voxel_scale, imesh);
double offset = voxel_scale * min_thickness;
double D = voxel_scale * closing_dist;
float out_range = 0.1f * float(offset);
float in_range = 1.1f * float(offset + D);
if (ctl.stopcondition()) return {};
else ctl.statuscb(0, L("Hollowing"));
auto gridptr = mesh_to_grid(imesh, {}, out_range, in_range);
auto gridptr = mesh_to_grid(mesh, {}, voxel_scale, out_range, in_range);
assert(gridptr);
if (!gridptr) {
BOOST_LOG_TRIVIAL(error) << "Returned OpenVDB grid is NULL";
return {};
}
if (ctl.stopcondition()) return {};
else ctl.statuscb(30, L("Hollowing"));
if (closing_dist > .0) {
gridptr = redistance_grid(*gridptr, -(offset + D), double(in_range));
} else {
D = -offset;
}
double iso_surface = D;
auto narrowb = double(in_range);
gridptr = redistance_grid(*gridptr, -(offset + D), narrowb, narrowb);
if (ctl.stopcondition()) return {};
else ctl.statuscb(70, L("Hollowing"));
double iso_surface = D;
double adaptivity = 0.;
auto omesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
_scale(1. / voxel_scale, omesh);
InteriorPtr interior = InteriorPtr{new Interior{}};
interior->mesh = grid_to_mesh(*gridptr, iso_surface, adaptivity);
interior->gridptr = gridptr;
if (ctl.stopcondition()) return {};
else ctl.statuscb(100, L("Hollowing"));
return omesh;
interior->closing_distance = D;
interior->thickness = offset;
interior->voxel_scale = voxel_scale;
interior->nb_in = narrowb;
interior->nb_out = narrowb;
return interior;
}
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
const HollowingConfig &hc,
const JobController & ctl)
InteriorPtr generate_interior(const TriangleMesh & mesh,
const HollowingConfig &hc,
const JobController & ctl)
{
static const double MIN_OVERSAMPL = 3.;
static const double MAX_OVERSAMPL = 8.;
// I can't figure out how to increase the grid resolution through openvdb
// API so the model will be scaled up before conversion and the result
// scaled down. Voxels have a unit size. If I set voxelSize smaller, it
@ -92,26 +127,29 @@ std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
//
// max 8x upscale, min is native voxel size
auto voxel_scale = MIN_OVERSAMPL + (MAX_OVERSAMPL - MIN_OVERSAMPL) * hc.quality;
auto meshptr = std::make_unique<TriangleMesh>(
_generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
hc.closing_distance));
if (meshptr && !meshptr->empty()) {
InteriorPtr interior =
generate_interior_verbose(mesh, ctl, hc.min_thickness, voxel_scale,
hc.closing_distance);
if (interior && !interior->mesh.empty()) {
// This flips the normals to be outward facing...
meshptr->require_shared_vertices();
indexed_triangle_set its = std::move(meshptr->its);
interior->mesh.require_shared_vertices();
indexed_triangle_set its = std::move(interior->mesh.its);
Slic3r::simplify_mesh(its);
// flip normals back...
for (stl_triangle_vertex_indices &ind : its.indices)
std::swap(ind(0), ind(2));
*meshptr = Slic3r::TriangleMesh{its};
interior->mesh = Slic3r::TriangleMesh{its};
interior->mesh.repaired = true;
interior->mesh.require_shared_vertices();
}
return meshptr;
return interior;
}
Contour3D DrainHole::to_mesh() const
@ -273,12 +311,264 @@ void cut_drainholes(std::vector<ExPolygons> & obj_slices,
obj_slices[i] = diff_ex(obj_slices[i], hole_slices[i]);
}
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg)
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags)
{
std::unique_ptr<Slic3r::TriangleMesh> inter_ptr =
Slic3r::sla::generate_interior(mesh);
InteriorPtr interior = generate_interior(mesh, cfg, JobController{});
if (!interior) return;
if (inter_ptr) mesh.merge(*inter_ptr);
hollow_mesh(mesh, *interior, flags);
}
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags)
{
if (mesh.empty() || interior.mesh.empty()) return;
if (flags & hfRemoveInsideTriangles && interior.gridptr)
remove_inside_triangles(mesh, interior);
mesh.merge(interior.mesh);
mesh.require_shared_vertices();
}
// Get the distance of p to the interior's zero iso_surface. Interior should
// have its zero isosurface positioned at offset + closing_distance inwards form
// the model surface.
static double get_distance_raw(const Vec3f &p, const Interior &interior)
{
assert(interior.gridptr);
if (!interior.accessor) interior.reset_accessor();
auto v = (p * interior.voxel_scale).cast<double>();
auto grididx = interior.gridptr->transform().worldToIndexCellCentered(
{v.x(), v.y(), v.z()});
return interior.accessor->getValue(grididx) ;
}
struct TriangleBubble { Vec3f center; double R; };
// Return the distance of bubble center to the interior boundary or NaN if the
// triangle is too big to be measured.
static double get_distance(const TriangleBubble &b, const Interior &interior)
{
double R = b.R * interior.voxel_scale;
double D = get_distance_raw(b.center, interior);
return (D > 0. && R >= interior.nb_out) ||
(D < 0. && R >= interior.nb_in) ||
((D - R) < 0. && 2 * R > interior.thickness) ?
std::nan("") :
// FIXME: Adding interior.voxel_scale is a compromise supposed
// to prevent the deletion of the triangles forming the interior
// itself. This has a side effect that a small portion of the
// bad triangles will still be visible.
D - interior.closing_distance /*+ 2 * interior.voxel_scale*/;
}
double get_distance(const Vec3f &p, const Interior &interior)
{
double d = get_distance_raw(p, interior) - interior.closing_distance;
return d / interior.voxel_scale;
}
// A face that can be divided. Stores the indices into the original mesh if its
// part of that mesh and the vertices it consists of.
enum { NEW_FACE = -1};
struct DivFace {
Vec3i indx;
std::array<Vec3f, 3> verts;
long faceid = NEW_FACE;
long parent = NEW_FACE;
};
// Divide a face recursively and call visitor on all the sub-faces.
template<class Fn>
void divide_triangle(const DivFace &face, Fn &&visitor)
{
std::array<Vec3f, 3> edges = {(face.verts[0] - face.verts[1]),
(face.verts[1] - face.verts[2]),
(face.verts[2] - face.verts[0])};
std::array<size_t, 3> edgeidx = {0, 1, 2};
std::sort(edgeidx.begin(), edgeidx.end(), [&edges](size_t e1, size_t e2) {
return edges[e1].squaredNorm() > edges[e2].squaredNorm();
});
DivFace child1, child2;
child1.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
child1.indx(0) = -1;
child1.indx(1) = face.indx(edgeidx[1]);
child1.indx(2) = face.indx((edgeidx[1] + 1) % 3);
child1.verts[0] = (face.verts[edgeidx[0]] + face.verts[(edgeidx[0] + 1) % 3]) / 2.;
child1.verts[1] = face.verts[edgeidx[1]];
child1.verts[2] = face.verts[(edgeidx[1] + 1) % 3];
if (visitor(child1))
divide_triangle(child1, std::forward<Fn>(visitor));
child2.parent = face.faceid == NEW_FACE ? face.parent : face.faceid;
child2.indx(0) = -1;
child2.indx(1) = face.indx(edgeidx[2]);
child2.indx(2) = face.indx((edgeidx[2] + 1) % 3);
child2.verts[0] = child1.verts[0];
child2.verts[1] = face.verts[edgeidx[2]];
child2.verts[2] = face.verts[(edgeidx[2] + 1) % 3];
if (visitor(child2))
divide_triangle(child2, std::forward<Fn>(visitor));
}
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask)
{
enum TrPos { posInside, posTouch, posOutside };
auto &faces = mesh.its.indices;
auto &vertices = mesh.its.vertices;
auto bb = mesh.bounding_box();
bool use_exclude_mask = faces.size() == exclude_mask.size();
auto is_excluded = [&exclude_mask, use_exclude_mask](size_t face_id) {
return use_exclude_mask && exclude_mask[face_id];
};
// TODO: Parallel mode not working yet
using exec_policy = ccr_seq;
// Info about the needed modifications on the input mesh.
struct MeshMods {
// Just a thread safe wrapper for a vector of triangles.
struct {
std::vector<std::array<Vec3f, 3>> data;
exec_policy::SpinningMutex mutex;
void emplace_back(const std::array<Vec3f, 3> &pts)
{
std::lock_guard lk{mutex};
data.emplace_back(pts);
}
size_t size() const { return data.size(); }
const std::array<Vec3f, 3>& operator[](size_t idx) const
{
return data[idx];
}
} new_triangles;
// A vector of bool for all faces signaling if it needs to be removed
// or not.
std::vector<bool> to_remove;
MeshMods(const TriangleMesh &mesh):
to_remove(mesh.its.indices.size(), false) {}
// Number of triangles that need to be removed.
size_t to_remove_cnt() const
{
return std::accumulate(to_remove.begin(), to_remove.end(), size_t(0));
}
} mesh_mods{mesh};
// Must return true if further division of the face is needed.
auto divfn = [&interior, bb, &mesh_mods](const DivFace &f) {
BoundingBoxf3 facebb { f.verts.begin(), f.verts.end() };
// Face is certainly outside the cavity
if (! facebb.intersects(bb) && f.faceid != NEW_FACE) {
return false;
}
TriangleBubble bubble{facebb.center().cast<float>(), facebb.radius()};
double D = get_distance(bubble, interior);
double R = bubble.R * interior.voxel_scale;
if (std::isnan(D)) // The distance cannot be measured, triangle too big
return true;
// Distance of the bubble wall to the interior wall. Negative if the
// bubble is overlapping with the interior
double bubble_distance = D - R;
// The face is crossing the interior or inside, it must be removed and
// parts of it re-added, that are outside the interior
if (bubble_distance < 0.) {
if (f.faceid != NEW_FACE)
mesh_mods.to_remove[f.faceid] = true;
if (f.parent != NEW_FACE) // Top parent needs to be removed as well
mesh_mods.to_remove[f.parent] = true;
// If the outside part is between the interior end the exterior
// (inside the wall being invisible), no further division is needed.
if ((R + D) < interior.thickness)
return false;
return true;
} else if (f.faceid == NEW_FACE) {
// New face completely outside needs to be re-added.
mesh_mods.new_triangles.emplace_back(f.verts);
}
return false;
};
interior.reset_accessor();
exec_policy::for_each(size_t(0), faces.size(), [&] (size_t face_idx) {
const Vec3i &face = faces[face_idx];
// If the triangle is excluded, we need to keep it.
if (is_excluded(face_idx))
return;
std::array<Vec3f, 3> pts =
{ vertices[face(0)], vertices[face(1)], vertices[face(2)] };
BoundingBoxf3 facebb { pts.begin(), pts.end() };
// Face is certainly outside the cavity
if (! facebb.intersects(bb)) return;
DivFace df{face, pts, long(face_idx)};
if (divfn(df))
divide_triangle(df, divfn);
}, exec_policy::max_concurreny());
auto new_faces = reserve_vector<Vec3i>(faces.size() +
mesh_mods.new_triangles.size());
for (size_t face_idx = 0; face_idx < faces.size(); ++face_idx) {
if (!mesh_mods.to_remove[face_idx])
new_faces.emplace_back(faces[face_idx]);
}
for(size_t i = 0; i < mesh_mods.new_triangles.size(); ++i) {
size_t o = vertices.size();
vertices.emplace_back(mesh_mods.new_triangles[i][0]);
vertices.emplace_back(mesh_mods.new_triangles[i][1]);
vertices.emplace_back(mesh_mods.new_triangles[i][2]);
new_faces.emplace_back(int(o), int(o + 1), int(o + 2));
}
BOOST_LOG_TRIVIAL(info)
<< "Trimming: " << mesh_mods.to_remove_cnt() << " triangles removed";
BOOST_LOG_TRIVIAL(info)
<< "Trimming: " << mesh_mods.new_triangles.size() << " triangles added";
faces.swap(new_faces);
new_faces = {};
mesh = TriangleMesh{mesh.its};
mesh.repaired = true;
mesh.require_shared_vertices();
}

View file

@ -19,6 +19,17 @@ struct HollowingConfig
bool enabled = true;
};
enum HollowingFlags { hfRemoveInsideTriangles = 0x1 };
// All data related to a generated mesh interior. Includes the 3D grid and mesh
// and various metadata. No need to manipulate from outside.
struct Interior;
struct InteriorDeleter { void operator()(Interior *p); };
using InteriorPtr = std::unique_ptr<Interior, InteriorDeleter>;
TriangleMesh & get_mesh(Interior &interior);
const TriangleMesh &get_mesh(const Interior &interior);
struct DrainHole
{
Vec3f pos;
@ -60,11 +71,26 @@ using DrainHoles = std::vector<DrainHole>;
constexpr float HoleStickOutLength = 1.f;
std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
const HollowingConfig & = {},
const JobController &ctl = {});
InteriorPtr generate_interior(const TriangleMesh &mesh,
const HollowingConfig & = {},
const JobController &ctl = {});
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg);
// Will do the hollowing
void hollow_mesh(TriangleMesh &mesh, const HollowingConfig &cfg, int flags = 0);
// Hollowing prepared in "interior", merge with original mesh
void hollow_mesh(TriangleMesh &mesh, const Interior &interior, int flags = 0);
void remove_inside_triangles(TriangleMesh &mesh, const Interior &interior,
const std::vector<bool> &exclude_mask = {});
double get_distance(const Vec3f &p, const Interior &interior);
template<class T>
FloatingOnly<T> get_distance(const Vec<3, T> &p, const Interior &interior)
{
return get_distance(Vec3f(p.template cast<float>()), interior);
}
void cut_drainholes(std::vector<ExPolygons> & obj_slices,
const std::vector<float> &slicegrid,

View file

@ -1120,7 +1120,7 @@ TriangleMesh SLAPrintObject::get_mesh(SLAPrintObjectStep step) const
return this->pad_mesh();
case slaposDrillHoles:
if (m_hollowing_data)
return m_hollowing_data->hollow_mesh_with_holes;
return get_mesh_to_print();
[[fallthrough]];
default:
return TriangleMesh();
@ -1149,8 +1149,9 @@ const TriangleMesh& SLAPrintObject::pad_mesh() const
const TriangleMesh &SLAPrintObject::hollowed_interior_mesh() const
{
if (m_hollowing_data && m_config.hollowing_enable.getBool())
return m_hollowing_data->interior;
if (m_hollowing_data && m_hollowing_data->interior &&
m_config.hollowing_enable.getBool())
return sla::get_mesh(*m_hollowing_data->interior);
return EMPTY_MESH;
}

View file

@ -85,6 +85,10 @@ public:
// Get the mesh that is going to be printed with all the modifications
// like hollowing and drilled holes.
const TriangleMesh & get_mesh_to_print() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes_trimmed : transformed_mesh();
}
const TriangleMesh & get_mesh_to_slice() const {
return (m_hollowing_data && is_step_done(slaposDrillHoles)) ? m_hollowing_data->hollow_mesh_with_holes : transformed_mesh();
}
@ -327,9 +331,10 @@ private:
class HollowingData
{
public:
TriangleMesh interior;
sla::InteriorPtr interior;
mutable TriangleMesh hollow_mesh_with_holes; // caching the complete hollowed mesh
mutable TriangleMesh hollow_mesh_with_holes_trimmed;
};
std::unique_ptr<HollowingData> m_hollowing_data;

View file

@ -1,3 +1,5 @@
#include <unordered_set>
#include <libslic3r/Exception.hpp>
#include <libslic3r/SLAPrintSteps.hpp>
#include <libslic3r/MeshBoolean.hpp>
@ -84,17 +86,17 @@ SLAPrint::Steps::Steps(SLAPrint *print)
void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin o)
{
if (o == soSupport && !po.m_supportdata) return;
auto faded_lyrs = size_t(po.m_config.faded_layers.getInt());
double min_w = m_print->m_printer_config.elefant_foot_min_width.getFloat() / 2.;
double start_efc = m_print->m_printer_config.elefant_foot_compensation.getFloat();
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
coord_t clpr_offs = scaled(doffs);
faded_lyrs = std::min(po.m_slice_index.size(), faded_lyrs);
size_t faded_lyrs_efc = std::max(size_t(1), faded_lyrs - 1);
auto efc = [start_efc, faded_lyrs_efc](size_t pos) {
return (faded_lyrs_efc - pos) * start_efc / faded_lyrs_efc;
};
@ -102,13 +104,13 @@ void SLAPrint::Steps::apply_printer_corrections(SLAPrintObject &po, SliceOrigin
std::vector<ExPolygons> &slices = o == soModel ?
po.m_model_slices :
po.m_supportdata->support_slices;
if (clpr_offs != 0) for (size_t i = 0; i < po.m_slice_index.size(); ++i) {
size_t idx = po.m_slice_index[i].get_slice_idx(o);
if (idx < slices.size())
slices[idx] = offset_ex(slices[idx], float(clpr_offs));
}
if (start_efc > 0.) for (size_t i = 0; i < faded_lyrs; ++i) {
size_t idx = po.m_slice_index[i].get_slice_idx(o);
if (idx < slices.size())
@ -124,28 +126,157 @@ void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
return;
}
BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
double thickness = po.m_config.hollowing_min_thickness.getFloat();
double quality = po.m_config.hollowing_quality.getFloat();
double closing_d = po.m_config.hollowing_closing_distance.getFloat();
sla::HollowingConfig hlwcfg{thickness, quality, closing_d};
auto meshptr = generate_interior(po.transformed_mesh(), hlwcfg);
if (meshptr->empty())
sla::InteriorPtr interior = generate_interior(po.transformed_mesh(), hlwcfg);
if (!interior || sla::get_mesh(*interior).empty())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->interior = *meshptr;
po.m_hollowing_data->interior = std::move(interior);
}
}
struct FaceHash {
// A hash is created for each triangle to be identifiable. The hash uses
// only the triangle's geometric traits, not the index in a particular mesh.
std::unordered_set<std::string> facehash;
static std::string facekey(const Vec3i &face,
const std::vector<Vec3f> &vertices)
{
// Scale to integer to avoid floating points
std::array<Vec<3, int64_t>, 3> pts = {
scaled<int64_t>(vertices[face(0)]),
scaled<int64_t>(vertices[face(1)]),
scaled<int64_t>(vertices[face(2)])
};
// Get the first two sides of the triangle, do a cross product and move
// that vector to the center of the triangle. This encodes all
// information to identify an identical triangle at the same position.
Vec<3, int64_t> a = pts[0] - pts[2], b = pts[1] - pts[2];
Vec<3, int64_t> c = a.cross(b) + (pts[0] + pts[1] + pts[2]) / 3;
// Return a concatenated string representation of the coordinates
return std::to_string(c(0)) + std::to_string(c(1)) + std::to_string(c(2));
};
FaceHash(const indexed_triangle_set &its)
{
for (const Vec3i &face : its.indices) {
std::string keystr = facekey(face, its.vertices);
facehash.insert(keystr);
}
}
bool find(const std::string &key)
{
auto it = facehash.find(key);
return it != facehash.end();
}
};
// Create exclude mask for triangle removal inside hollowed interiors.
// This is necessary when the interior is already part of the mesh which was
// drilled using CGAL mesh boolean operation. Excluded will be the triangles
// originally part of the interior mesh and triangles that make up the drilled
// hole walls.
static std::vector<bool> create_exclude_mask(
const indexed_triangle_set &its,
const sla::Interior &interior,
const std::vector<sla::DrainHole> &holes)
{
FaceHash interior_hash{sla::get_mesh(interior).its};
std::vector<bool> exclude_mask(its.indices.size(), false);
std::vector< std::vector<size_t> > neighbor_index =
create_neighbor_index(its);
auto exclude_neighbors = [&neighbor_index, &exclude_mask](const Vec3i &face)
{
for (int i = 0; i < 3; ++i) {
const std::vector<size_t> &neighbors = neighbor_index[face(i)];
for (size_t fi_n : neighbors) exclude_mask[fi_n] = true;
}
};
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
auto &face = its.indices[fi];
if (interior_hash.find(FaceHash::facekey(face, its.vertices))) {
exclude_mask[fi] = true;
continue;
}
if (exclude_mask[fi]) {
exclude_neighbors(face);
continue;
}
// Lets deal with the holes. All the triangles of a hole and all the
// neighbors of these triangles need to be kept. The neigbors were
// created by CGAL mesh boolean operation that modified the original
// interior inside the input mesh to contain the holes.
Vec3d tr_center = (
its.vertices[face(0)] +
its.vertices[face(1)] +
its.vertices[face(2)]
).cast<double>() / 3.;
// If the center is more than half a mm inside the interior,
// it cannot possibly be part of a hole wall.
if (sla::get_distance(tr_center, interior) < -0.5)
continue;
Vec3f U = its.vertices[face(1)] - its.vertices[face(0)];
Vec3f V = its.vertices[face(2)] - its.vertices[face(0)];
Vec3f C = U.cross(V);
Vec3f face_normal = C.normalized();
for (const sla::DrainHole &dh : holes) {
Vec3d dhpos = dh.pos.cast<double>();
Vec3d dhend = dhpos + dh.normal.cast<double>() * dh.height;
Linef3 holeaxis{dhpos, dhend};
double D_hole_center = line_alg::distance_to(holeaxis, tr_center);
double D_hole = std::abs(D_hole_center - dh.radius);
float dot = dh.normal.dot(face_normal);
// Empiric tolerances for center distance and normals angle.
// For triangles that are part of a hole wall the angle of
// triangle normal and the hole axis is around 90 degrees,
// so the dot product is around zero.
double D_tol = dh.radius / sla::DrainHole::steps;
float normal_angle_tol = 1.f / sla::DrainHole::steps;
if (D_hole < D_tol && std::abs(dot) < normal_angle_tol) {
exclude_mask[fi] = true;
exclude_neighbors(face);
}
}
}
return exclude_mask;
}
// Drill holes into the hollowed/original mesh.
void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
{
bool needs_drilling = ! po.m_model_object->sla_drain_holes.empty();
bool is_hollowed = (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty());
bool is_hollowed =
(po.m_hollowing_data && po.m_hollowing_data->interior &&
!sla::get_mesh(*po.m_hollowing_data->interior).empty());
if (! is_hollowed && ! needs_drilling) {
// In this case we can dump any data that might have been
@ -163,19 +294,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// holes that are no longer on the frontend.
TriangleMesh &hollowed_mesh = po.m_hollowing_data->hollow_mesh_with_holes;
hollowed_mesh = po.transformed_mesh();
if (! po.m_hollowing_data->interior.empty()) {
hollowed_mesh.merge(po.m_hollowing_data->interior);
hollowed_mesh.require_shared_vertices();
}
if (is_hollowed)
sla::hollow_mesh(hollowed_mesh, *po.m_hollowing_data->interior);
TriangleMesh &mesh_view = po.m_hollowing_data->hollow_mesh_with_holes_trimmed;
if (! needs_drilling) {
mesh_view = po.transformed_mesh();
if (is_hollowed)
sla::hollow_mesh(mesh_view, *po.m_hollowing_data->interior,
sla::hfRemoveInsideTriangles);
BOOST_LOG_TRIVIAL(info) << "Drilling skipped (no holes).";
return;
}
BOOST_LOG_TRIVIAL(info) << "Drilling drainage holes.";
sla::DrainHoles drainholes = po.transformed_drainhole_points();
std::uniform_real_distribution<float> dist(0., float(EPSILON));
auto holes_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal({});
for (sla::DrainHole holept : drainholes) {
@ -187,15 +324,25 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
auto cgal_m = MeshBoolean::cgal::triangle_mesh_to_cgal(m);
MeshBoolean::cgal::plus(*holes_mesh_cgal, *cgal_m);
}
if (MeshBoolean::cgal::does_self_intersect(*holes_mesh_cgal))
throw Slic3r::SlicingError(L("Too many overlapping holes."));
auto hollowed_mesh_cgal = MeshBoolean::cgal::triangle_mesh_to_cgal(hollowed_mesh);
try {
MeshBoolean::cgal::minus(*hollowed_mesh_cgal, *holes_mesh_cgal);
hollowed_mesh = MeshBoolean::cgal::cgal_to_triangle_mesh(*hollowed_mesh_cgal);
mesh_view = hollowed_mesh;
if (is_hollowed) {
auto &interior = *po.m_hollowing_data->interior;
std::vector<bool> exclude_mask =
create_exclude_mask(mesh_view.its, interior, drainholes);
sla::remove_inside_triangles(mesh_view, interior, exclude_mask);
}
} catch (const std::runtime_error &) {
throw Slic3r::SlicingError(L(
"Drilling holes into the mesh failed. "
@ -212,11 +359,11 @@ void SLAPrint::Steps::drill_holes(SLAPrintObject &po)
// of it. In any case, the model and the supports have to be sliced in the
// same imaginary grid (the height vector argument to TriangleMeshSlicer).
void SLAPrint::Steps::slice_model(SLAPrintObject &po)
{
const TriangleMesh &mesh = po.get_mesh_to_print();
{
const TriangleMesh &mesh = po.get_mesh_to_slice();
// We need to prepare the slice index...
double lhd = m_print->m_objects.front()->m_config.layer_height.getFloat();
float lh = float(lhd);
coord_t lhs = scaled(lhd);
@ -226,43 +373,49 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
auto minZf = float(minZ);
coord_t minZs = scaled(minZ);
coord_t maxZs = scaled(maxZ);
po.m_slice_index.clear();
size_t cap = size_t(1 + (maxZs - minZs - ilhs) / lhs);
po.m_slice_index.reserve(cap);
po.m_slice_index.emplace_back(minZs + ilhs, minZf + ilh / 2.f, ilh);
for(coord_t h = minZs + ilhs + lhs; h <= maxZs; h += lhs)
po.m_slice_index.emplace_back(h, unscaled<float>(h) - lh / 2.f, lh);
// Just get the first record that is from the model:
auto slindex_it =
po.closest_slice_record(po.m_slice_index, float(bb3d.min(Z)));
if(slindex_it == po.m_slice_index.end())
//TRN To be shown at the status bar on SLA slicing error.
throw Slic3r::RuntimeError(
L("Slicing had to be stopped due to an internal error: "
"Inconsistent slice index."));
po.m_model_height_levels.clear();
po.m_model_height_levels.reserve(po.m_slice_index.size());
for(auto it = slindex_it; it != po.m_slice_index.end(); ++it)
po.m_model_height_levels.emplace_back(it->slice_level());
TriangleMeshSlicer slicer(&mesh);
po.m_model_slices.clear();
float closing_r = float(po.config().slice_closing_radius.value);
auto thr = [this]() { m_print->throw_if_canceled(); };
auto &slice_grid = po.m_model_height_levels;
slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &po.m_model_slices, thr);
if (po.m_hollowing_data && ! po.m_hollowing_data->interior.empty()) {
po.m_hollowing_data->interior.repair(true);
TriangleMeshSlicer interior_slicer(&po.m_hollowing_data->interior);
sla::Interior *interior = po.m_hollowing_data ?
po.m_hollowing_data->interior.get() :
nullptr;
if (interior && ! sla::get_mesh(*interior).empty()) {
TriangleMesh interiormesh = sla::get_mesh(*interior);
interiormesh.repaired = false;
interiormesh.repair(true);
TriangleMeshSlicer interior_slicer(&interiormesh);
std::vector<ExPolygons> interior_slices;
interior_slicer.slice(slice_grid, SlicingMode::Regular, closing_r, &interior_slices, thr);
@ -273,17 +426,17 @@ void SLAPrint::Steps::slice_model(SLAPrintObject &po)
diff_ex(po.m_model_slices[i], slice);
});
}
auto mit = slindex_it;
for (size_t id = 0;
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
id++) {
mit->set_model_slice_idx(po, id); ++mit;
}
// We apply the printer correction offset here.
apply_printer_corrections(po, soModel);
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
{
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
@ -296,22 +449,22 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
{
// If supports are disabled, we can skip the model scan.
if(!po.m_config.supports_enable.getBool()) return;
const TriangleMesh &mesh = po.get_mesh_to_print();
const TriangleMesh &mesh = po.get_mesh_to_slice();
if (!po.m_supportdata)
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
const ModelObject& mo = *po.m_model_object;
BOOST_LOG_TRIVIAL(debug) << "Support point count "
<< mo.sla_support_points.size();
// Unless the user modified the points or we already did the calculation,
// we will do the autoplacement. Otherwise we will just blindly copy the
// frontend data into the backend cache.
if (mo.sla_points_status != sla::PointsStatus::UserModified) {
// calculate heights of slices (slices are calculated already)
const std::vector<float>& heights = po.m_model_height_levels;
@ -319,27 +472,27 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
// calculated on slices, the algorithm then raycasts the points
// so they actually lie on the mesh.
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
throw_if_canceled();
sla::SupportPointGenerator::Config config;
const SLAPrintObjectConfig& cfg = po.config();
// the density config value is in percents:
config.density_relative = float(cfg.support_points_density_relative / 100.f);
config.minimal_distance = float(cfg.support_points_minimal_distance);
config.head_diameter = float(cfg.support_head_front_diameter);
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
double init = current_status();
auto statuscb = [this, d, init](unsigned st)
{
double current = init + st * d;
if(std::round(current_status()) < std::round(current))
report_status(current, OBJ_STEP_LABELS(slaposSupportPoints));
};
// Construction of this object does the calculation.
throw_if_canceled();
sla::SupportPointGenerator auto_supports(
@ -350,10 +503,10 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
const std::vector<sla::SupportPoint>& points = auto_supports.output();
throw_if_canceled();
po.m_supportdata->pts = points;
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
<< po.m_supportdata->pts.size();
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass
// the update status to GLGizmoSlaSupports
report_status(-1, L("Generating support points"),
@ -368,9 +521,9 @@ void SLAPrint::Steps::support_points(SLAPrintObject &po)
void SLAPrint::Steps::support_tree(SLAPrintObject &po)
{
if(!po.m_supportdata) return;
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
if (pcfg.embed_object)
po.m_supportdata->emesh.ground_level_offset(pcfg.wall_thickness_mm);
@ -380,15 +533,15 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
remove_bottom_points(po.m_supportdata->pts,
float(po.m_supportdata->emesh.ground_level() + EPSILON));
}
po.m_supportdata->cfg = make_support_cfg(po.m_config);
// po.m_supportdata->emesh.load_holes(po.transformed_drainhole_points());
// scaling for the sub operations
double d = objectstep_scale * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
double init = current_status();
sla::JobController ctl;
ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
double current = init + st * d;
if (std::round(current_status()) < std::round(current))
@ -397,26 +550,26 @@ void SLAPrint::Steps::support_tree(SLAPrintObject &po)
};
ctl.stopcondition = [this]() { return canceled(); };
ctl.cancelfn = [this]() { throw_if_canceled(); };
po.m_supportdata->create_support_tree(ctl);
if (!po.m_config.supports_enable.getBool()) return;
throw_if_canceled();
// Create the unified mesh
auto rc = SlicingStatus::RELOAD_SCENE;
// This is to prevent "Done." being displayed during merged_mesh()
report_status(-1, L("Visualizing supports"));
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
<< po.m_supportdata->pts.size();
// Check the mesh for later troubleshooting.
if(po.support_mesh().empty())
BOOST_LOG_TRIVIAL(warning) << "Support mesh is empty";
report_status(-1, L("Visualizing supports"), rc);
}
@ -424,15 +577,15 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
// this step can only go after the support tree has been created
// and before the supports had been sliced. (or the slicing has to be
// repeated)
if(po.m_config.pad_enable.getBool()) {
// Get the distilled pad configuration from the config
sla::PadConfig pcfg = make_pad_cfg(po.m_config);
ExPolygons bp; // This will store the base plate of the pad.
double pad_h = pcfg.full_height();
const TriangleMesh &trmesh = po.transformed_mesh();
if (!po.m_config.supports_enable.getBool() || pcfg.embed_object) {
// No support (thus no elevation) or zero elevation mode
// we sometimes call it "builtin pad" is enabled so we will
@ -442,19 +595,19 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
float(po.m_config.layer_height.getFloat()),
[this](){ throw_if_canceled(); });
}
po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(sla::MeshType::Pad);
if (!validate_pad(pad_mesh, pcfg))
throw Slic3r::SlicingError(
L("No pad can be generated for this model with the "
"current configuration"));
} else if(po.m_supportdata && po.m_supportdata->support_tree_ptr) {
po.m_supportdata->support_tree_ptr->remove_pad();
}
throw_if_canceled();
report_status(-1, L("Visualizing supports"), SlicingStatus::RELOAD_SCENE);
}
@ -464,25 +617,25 @@ void SLAPrint::Steps::generate_pad(SLAPrintObject &po) {
// be part of the slices)
void SLAPrint::Steps::slice_supports(SLAPrintObject &po) {
auto& sd = po.m_supportdata;
if(sd) sd->support_slices.clear();
// Don't bother if no supports and no pad is present.
if (!po.m_config.supports_enable.getBool() && !po.m_config.pad_enable.getBool())
return;
if(sd && sd->support_tree_ptr) {
auto heights = reserve_vector<float>(po.m_slice_index.size());
for(auto& rec : po.m_slice_index) heights.emplace_back(rec.slice_level());
sd->support_slices = sd->support_tree_ptr->slice(
heights, float(po.config().slice_closing_radius.value));
}
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i)
po.m_slice_index[i].set_support_slice_idx(po, i);
apply_printer_corrections(po, soSupport);
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
@ -497,37 +650,37 @@ using ClipperPolygons = std::vector<ClipperPolygon>;
static ClipperPolygons polyunion(const ClipperPolygons &subjects)
{
ClipperLib::Clipper clipper;
bool closed = true;
for(auto& path : subjects) {
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
}
auto mode = ClipperLib::pftPositive;
return libnest2d::clipper_execute(clipper, ClipperLib::ctUnion, mode, mode);
}
static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPolygons& clips)
{
ClipperLib::Clipper clipper;
bool closed = true;
for(auto& path : subjects) {
clipper.AddPath(path.Contour, ClipperLib::ptSubject, closed);
clipper.AddPaths(path.Holes, ClipperLib::ptSubject, closed);
}
for(auto& path : clips) {
clipper.AddPath(path.Contour, ClipperLib::ptClip, closed);
clipper.AddPaths(path.Holes, ClipperLib::ptClip, closed);
}
auto mode = ClipperLib::pftPositive;
return libnest2d::clipper_execute(clipper, ClipperLib::ctDifference, mode, mode);
}
@ -535,28 +688,28 @@ static ClipperPolygons polydiff(const ClipperPolygons &subjects, const ClipperPo
static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o)
{
namespace sl = libnest2d::sl;
if (!record.print_obj()) return {};
ClipperPolygons polygons;
auto &input_polygons = record.get_slice(o);
auto &instances = record.print_obj()->instances();
bool is_lefthanded = record.print_obj()->is_left_handed();
polygons.reserve(input_polygons.size() * instances.size());
for (const ExPolygon& polygon : input_polygons) {
if(polygon.contour.empty()) continue;
for (size_t i = 0; i < instances.size(); ++i)
{
ClipperPolygon poly;
// We need to reverse if is_lefthanded is true but
bool needreverse = is_lefthanded;
// should be a move
poly.Contour.reserve(polygon.contour.size() + 1);
auto& cntr = polygon.contour.points;
if(needreverse)
for(auto it = cntr.rbegin(); it != cntr.rend(); ++it)
@ -564,12 +717,12 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o
else
for(auto& p : cntr)
poly.Contour.emplace_back(p.x(), p.y());
for(auto& h : polygon.holes) {
poly.Holes.emplace_back();
auto& hole = poly.Holes.back();
hole.reserve(h.points.size() + 1);
if(needreverse)
for(auto it = h.points.rbegin(); it != h.points.rend(); ++it)
hole.emplace_back(it->x(), it->y());
@ -577,42 +730,42 @@ static ClipperPolygons get_all_polygons(const SliceRecord& record, SliceOrigin o
for(auto& p : h.points)
hole.emplace_back(p.x(), p.y());
}
if(is_lefthanded) {
for(auto& p : poly.Contour) p.X = -p.X;
for(auto& h : poly.Holes) for(auto& p : h) p.X = -p.X;
}
sl::rotate(poly, double(instances[i].rotation));
sl::translate(poly, ClipperPoint{instances[i].shift.x(),
instances[i].shift.y()});
polygons.emplace_back(std::move(poly));
}
}
return polygons;
}
void SLAPrint::Steps::initialize_printer_input()
{
auto &printer_input = m_print->m_printer_input;
// clear the rasterizer input
printer_input.clear();
size_t mx = 0;
for(SLAPrintObject * o : m_print->m_objects) {
if(auto m = o->get_slice_index().size() > mx) mx = m;
}
printer_input.reserve(mx);
auto eps = coord_t(SCALED_EPSILON);
for(SLAPrintObject * o : m_print->m_objects) {
coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
for(const SliceRecord& slicerecord : o->get_slice_index()) {
if (!slicerecord.is_valid())
throw Slic3r::SlicingError(
@ -621,7 +774,7 @@ void SLAPrint::Steps::initialize_printer_input()
"objects printable."));
coord_t lvlid = slicerecord.print_level() - gndlvl;
// Neat trick to round the layer levels to the grid.
lvlid = eps * (lvlid / eps);
@ -631,8 +784,8 @@ void SLAPrint::Steps::initialize_printer_input()
if(it == printer_input.end() || it->level() != lvlid)
it = printer_input.insert(it, PrintLayer(lvlid));
it->add(slicerecord);
}
}
@ -641,53 +794,53 @@ void SLAPrint::Steps::initialize_printer_input()
// Merging the slices from all the print objects into one slice grid and
// calculating print statistics from the merge result.
void SLAPrint::Steps::merge_slices_and_eval_stats() {
initialize_printer_input();
auto &print_statistics = m_print->m_print_statistics;
auto &printer_config = m_print->m_printer_config;
auto &material_config = m_print->m_material_config;
auto &printer_input = m_print->m_printer_input;
print_statistics.clear();
// libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
auto areafn = [](const ClipperPolygon& poly) { return - libnest2d::sl::area(poly); };
const double area_fill = printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
const double fast_tilt = printer_config.fast_tilt_time.getFloat();// 5.0;
const double slow_tilt = printer_config.slow_tilt_time.getFloat();// 8.0;
const double init_exp_time = material_config.initial_exposure_time.getFloat();
const double exp_time = material_config.exposure_time.getFloat();
const int fade_layers_cnt = m_print->m_default_object_config.faded_layers.getInt();// 10 // [3;20]
const auto width = scaled<double>(printer_config.display_width.getFloat());
const auto height = scaled<double>(printer_config.display_height.getFloat());
const double display_area = width*height;
double supports_volume(0.0);
double models_volume(0.0);
double estim_time(0.0);
std::vector<double> layers_times;
layers_times.reserve(printer_input.size());
size_t slow_layers = 0;
size_t fast_layers = 0;
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
double fade_layer_time = init_exp_time;
sla::ccr::SpinningMutex mutex;
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
// Going to parallel:
auto printlayerfn = [this,
// functions and read only vars
areafn, area_fill, display_area, exp_time, init_exp_time, fast_tilt, slow_tilt, delta_fade_time,
// write vars
&mutex, &models_volume, &supports_volume, &estim_time, &slow_layers,
&fast_layers, &fade_layer_time, &layers_times](size_t sliced_layer_cnt)
@ -696,87 +849,87 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
// vector of slice record references
auto& slicerecord_references = layer.slices();
if(slicerecord_references.empty()) return;
// Layer height should match for all object slices for a given level.
const auto l_height = double(slicerecord_references.front().get().layer_height());
// Calculation of the consumed material
ClipperPolygons model_polygons;
ClipperPolygons supports_polygons;
size_t c = std::accumulate(layer.slices().begin(),
layer.slices().end(),
size_t(0),
[](size_t a, const SliceRecord &sr) {
return a + sr.get_slice(soModel).size();
});
model_polygons.reserve(c);
c = std::accumulate(layer.slices().begin(),
layer.slices().end(),
size_t(0),
[](size_t a, const SliceRecord &sr) {
return a + sr.get_slice(soModel).size();
});
supports_polygons.reserve(c);
for(const SliceRecord& record : layer.slices()) {
ClipperPolygons modelslices = get_all_polygons(record, soModel);
for(ClipperPolygon& p_tmp : modelslices) model_polygons.emplace_back(std::move(p_tmp));
ClipperPolygons supportslices = get_all_polygons(record, soSupport);
for(ClipperPolygon& p_tmp : supportslices) supports_polygons.emplace_back(std::move(p_tmp));
}
model_polygons = polyunion(model_polygons);
double layer_model_area = 0;
for (const ClipperPolygon& polygon : model_polygons)
layer_model_area += areafn(polygon);
if (layer_model_area < 0 || layer_model_area > 0) {
Lock lck(mutex); models_volume += layer_model_area * l_height;
}
if(!supports_polygons.empty()) {
if(model_polygons.empty()) supports_polygons = polyunion(supports_polygons);
else supports_polygons = polydiff(supports_polygons, model_polygons);
// allegedly, union of subject is done withing the diff according to the pftPositive polyFillType
}
double layer_support_area = 0;
for (const ClipperPolygon& polygon : supports_polygons)
layer_support_area += areafn(polygon);
if (layer_support_area < 0 || layer_support_area > 0) {
Lock lck(mutex); supports_volume += layer_support_area * l_height;
}
// Here we can save the expensively calculated polygons for printing
ClipperPolygons trslices;
trslices.reserve(model_polygons.size() + supports_polygons.size());
for(ClipperPolygon& poly : model_polygons) trslices.emplace_back(std::move(poly));
for(ClipperPolygon& poly : supports_polygons) trslices.emplace_back(std::move(poly));
layer.transformed_slices(polyunion(trslices));
// Calculation of the slow and fast layers to the future controlling those values on FW
const bool is_fast_layer = (layer_model_area + layer_support_area) <= display_area*area_fill;
const double tilt_time = is_fast_layer ? fast_tilt : slow_tilt;
{ Lock lck(mutex);
if (is_fast_layer)
fast_layers++;
else
slow_layers++;
// Calculation of the printing time
double layer_times = 0.0;
@ -794,15 +947,15 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
estim_time += layer_times;
}
};
// sequential version for debugging:
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
sla::ccr::for_each(size_t(0), printer_input.size(), printlayerfn);
auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
print_statistics.support_used_material = supports_volume * SCALING2;
print_statistics.objects_used_material = models_volume * SCALING2;
// Estimated printing time
// A layers count o the highest object
if (printer_input.size() == 0)
@ -811,10 +964,10 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
print_statistics.estimated_print_time = estim_time;
print_statistics.layers_times = layers_times;
}
print_statistics.fast_layers_count = fast_layers;
print_statistics.slow_layers_count = slow_layers;
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
}
@ -822,23 +975,23 @@ void SLAPrint::Steps::merge_slices_and_eval_stats() {
void SLAPrint::Steps::rasterize()
{
if(canceled() || !m_print->m_printer) return;
// coefficient to map the rasterization state (0-99) to the allocated
// portion (slot) of the process state
double sd = (100 - max_objstatus) / 100.0;
// slot is the portion of 100% that is realted to rasterization
unsigned slot = PRINT_STEP_LEVELS[slapsRasterize];
// pst: previous state
double pst = current_status();
double increment = (slot * sd) / m_print->m_printer_input.size();
double dstatus = current_status();
sla::ccr::SpinningMutex slck;
using Lock = std::lock_guard<sla::ccr::SpinningMutex>;
// procedure to process one height level. This will run in parallel
auto lvlfn =
[this, &slck, increment, &dstatus, &pst]
@ -846,10 +999,10 @@ void SLAPrint::Steps::rasterize()
{
PrintLayer& printlayer = m_print->m_printer_input[idx];
if(canceled()) return;
for (const ClipperLib::Polygon& poly : printlayer.transformed_slices())
raster.draw(poly);
// Status indication guarded with the spinlock
{
Lock lck(slck);
@ -861,10 +1014,10 @@ void SLAPrint::Steps::rasterize()
}
}
};
// last minute escape
if(canceled()) return;
// Print all the layers in parallel
m_print->m_printer->draw_layers(m_print->m_printer_input.size(), lvlfn);
}

View file

@ -112,8 +112,10 @@ SlicingParameters SlicingParameters::create_from_config(
if (! soluble_interface) {
params.gap_raft_object = object_config.raft_contact_distance.value;
params.gap_object_support = object_config.support_material_contact_distance.value;
params.gap_object_support = object_config.support_material_bottom_contact_distance.value;
params.gap_support_object = object_config.support_material_contact_distance.value;
if (params.gap_object_support <= 0)
params.gap_object_support = params.gap_support_object;
}
if (params.base_raft_layers > 0) {

View file

@ -26,7 +26,7 @@ class DynamicPrintConfig;
// (using a normal flow over a soluble support, using a bridging flow over a non-soluble support).
struct SlicingParameters
{
SlicingParameters() { memset(this, 0, sizeof(SlicingParameters)); }
SlicingParameters() = default;
static SlicingParameters create_from_config(
const PrintConfig &print_config,
@ -44,58 +44,58 @@ struct SlicingParameters
// Height of the object to be printed. This value does not contain the raft height.
coordf_t object_print_z_height() const { return object_print_z_max - object_print_z_min; }
bool valid;
bool valid { false };
// Number of raft layers.
size_t base_raft_layers;
size_t base_raft_layers { 0 };
// Number of interface layers including the contact layer.
size_t interface_raft_layers;
size_t interface_raft_layers { 0 };
// Layer heights of the raft (base, interface and a contact layer).
coordf_t base_raft_layer_height;
coordf_t interface_raft_layer_height;
coordf_t contact_raft_layer_height;
coordf_t base_raft_layer_height { 0 };
coordf_t interface_raft_layer_height { 0 };
coordf_t contact_raft_layer_height { 0 };
// The regular layer height, applied for all but the first layer, if not overridden by layer ranges
// or by the variable layer thickness table.
coordf_t layer_height;
coordf_t layer_height { 0 };
// Minimum / maximum layer height, to be used for the automatic adaptive layer height algorithm,
// or by an interactive layer height editor.
coordf_t min_layer_height;
coordf_t max_layer_height;
coordf_t max_suport_layer_height;
coordf_t min_layer_height { 0 };
coordf_t max_layer_height { 0 };
coordf_t max_suport_layer_height { 0 };
// First layer height of the print, this may be used for the first layer of the raft
// or for the first layer of the print.
coordf_t first_print_layer_height;
coordf_t first_print_layer_height { 0 };
// Thickness of the first layer. This is either the first print layer thickness if printed without a raft,
// or a bridging flow thickness if printed over a non-soluble raft,
// or a normal layer height if printed over a soluble raft.
coordf_t first_object_layer_height;
coordf_t first_object_layer_height { 0 };
// If the object is printed over a non-soluble raft, the first layer may be printed with a briding flow.
bool first_object_layer_bridging;
bool first_object_layer_bridging { false };
// Soluble interface? (PLA soluble in water, HIPS soluble in lemonen)
// otherwise the interface must be broken off.
bool soluble_interface;
bool soluble_interface { false };
// Gap when placing object over raft.
coordf_t gap_raft_object;
coordf_t gap_raft_object { 0 };
// Gap when placing support over object.
coordf_t gap_object_support;
coordf_t gap_object_support { 0 };
// Gap when placing object over support.
coordf_t gap_support_object;
coordf_t gap_support_object { 0 };
// Bottom and top of the printed object.
// If printed without a raft, object_print_z_min = 0 and object_print_z_max = object height.
// Otherwise object_print_z_min is equal to the raft height.
coordf_t raft_base_top_z;
coordf_t raft_interface_top_z;
coordf_t raft_contact_top_z;
coordf_t raft_base_top_z { 0 };
coordf_t raft_interface_top_z { 0 };
coordf_t raft_contact_top_z { 0 };
// In case of a soluble interface, object_print_z_min == raft_contact_top_z, otherwise there is a gap between the raft and the 1st object layer.
coordf_t object_print_z_min;
coordf_t object_print_z_max;
coordf_t object_print_z_min { 0 };
coordf_t object_print_z_max { 0 };
};
static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor).");

View file

@ -10,6 +10,7 @@
#include <cmath>
#include <memory>
#include <boost/log/trivial.hpp>
#include <boost/container/static_vector.hpp>
#include <tbb/parallel_for.h>
#include <tbb/atomic.h>
@ -334,7 +335,7 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
for (auto lh : m_print_config->min_layer_height.values)
m_support_layer_height_min = std::min(m_support_layer_height_min, std::max(0.01, lh));
if (m_object_config->support_material_interface_layers.value == 0) {
if (m_slicing_params.soluble_interface) {
// No interface layers allowed, print everything with the base support pattern.
m_support_material_interface_flow = m_support_material_flow;
}
@ -342,11 +343,21 @@ PrintObjectSupportMaterial::PrintObjectSupportMaterial(const PrintObject *object
// Evaluate the XY gap between the object outer perimeters and the support structures.
// Evaluate the XY gap between the object outer perimeters and the support structures.
coordf_t external_perimeter_width = 0.;
size_t num_nonempty_regions = 0;
coordf_t bridge_flow_ratio = 0;
for (size_t region_id = 0; region_id < object->region_volumes.size(); ++ region_id)
if (! object->region_volumes[region_id].empty())
external_perimeter_width = std::max(external_perimeter_width,
(coordf_t)object->print()->get_region(region_id)->flow(frExternalPerimeter, slicing_params.layer_height, false, false, -1, *object).width);
if (! object->region_volumes[region_id].empty()) {
++ num_nonempty_regions;
const PrintRegion &region = *object->print()->get_region(region_id);
external_perimeter_width = std::max(external_perimeter_width, coordf_t(region.flow(*object, frExternalPerimeter, slicing_params.layer_height).width()));
bridge_flow_ratio += region.config().bridge_flow_ratio;
}
m_gap_xy = m_object_config->support_material_xy_spacing.get_abs_value(external_perimeter_width);
bridge_flow_ratio /= num_nonempty_regions;
m_support_material_bottom_interface_flow = m_slicing_params.soluble_interface || ! m_object_config->thick_bridges ?
m_support_material_interface_flow.with_flow_ratio(bridge_flow_ratio) :
Flow::bridging_flow(bridge_flow_ratio * m_support_material_interface_flow.nozzle_diameter(), m_support_material_interface_flow.nozzle_diameter());
m_can_merge_support_regions = m_object_config->support_material_extruder.value == m_object_config->support_material_interface_extruder.value;
if (! m_can_merge_support_regions && (m_object_config->support_material_extruder.value == 0 || m_object_config->support_material_interface_extruder.value == 0)) {
@ -387,14 +398,6 @@ inline void layers_append(PrintObjectSupportMaterial::MyLayersPtr &dst, const Pr
dst.insert(dst.end(), src.begin(), src.end());
}
// Compare layers lexicographically.
struct MyLayersPtrCompare
{
bool operator()(const PrintObjectSupportMaterial::MyLayer* layer1, const PrintObjectSupportMaterial::MyLayer* layer2) const {
return *layer1 < *layer2;
}
};
void PrintObjectSupportMaterial::generate(PrintObject &object)
{
BOOST_LOG_TRIVIAL(info) << "Support generator - Start";
@ -457,10 +460,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
MyLayersPtr intermediate_layers = this->raft_and_intermediate_support_layers(
object, bottom_contacts, top_contacts, layer_storage);
// this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.soluble_interface ? 0. : m_support_layer_height_min, 0., m_gap_xy);
this->trim_support_layers_by_object(object, top_contacts,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
this->trim_support_layers_by_object(object, top_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy);
#ifdef SLIC3R_DEBUG
for (const MyLayer *layer : top_contacts)
@ -542,7 +542,7 @@ void PrintObjectSupportMaterial::generate(PrintObject &object)
layers_append(layers_sorted, interface_layers);
layers_append(layers_sorted, base_interface_layers);
// Sort the layers lexicographically by a raising print_z and a decreasing height.
std::sort(layers_sorted.begin(), layers_sorted.end(), MyLayersPtrCompare());
std::sort(layers_sorted.begin(), layers_sorted.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
int layer_id = 0;
assert(object.support_layers().empty());
for (size_t i = 0; i < layers_sorted.size();) {
@ -1231,8 +1231,8 @@ namespace SupportMaterialInternal {
// since we're dealing with bridges, we can't assume width is larger than spacing,
// so we take the largest value and also apply safety offset to be ensure no gaps
// are left in between
Flow bridge_flow = layerm->flow(frPerimeter, true);
float w = float(std::max(bridge_flow.scaled_width(), bridge_flow.scaled_spacing()));
Flow perimeter_bridge_flow = layerm->bridging_flow(frPerimeter);
float w = float(std::max(perimeter_bridge_flow.scaled_width(), perimeter_bridge_flow.scaled_spacing()));
for (Polyline &polyline : overhang_perimeters)
if (polyline.is_straight()) {
// This is a bridge
@ -1542,16 +1542,20 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
}
}
// Offset the contact polygons outside.
#if 0
for (size_t i = 0; i < NUM_MARGIN_STEPS; ++ i) {
diff_polygons = diff(
offset(
diff_polygons,
SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS,
scaled<float>(SUPPORT_MATERIAL_MARGIN / NUM_MARGIN_STEPS),
ClipperLib::jtRound,
// round mitter limit
scale_(0.05)),
slices_margin_cached);
}
#else
diff_polygons = diff(diff_polygons, slices_margin_cached);
#endif
}
polygons_append(contact_polygons, diff_polygons);
} // for each layer.region
@ -1571,11 +1575,11 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
} else if (m_slicing_params.soluble_interface) {
// Align the contact surface height with a layer immediately below the supported layer.
// Interface layer will be synchronized with the object.
new_layer.print_z = layer.print_z - layer.height;
new_layer.print_z = layer.bottom_z();
new_layer.height = object.layers()[layer_id - 1]->height;
new_layer.bottom_z = (layer_id == 1) ? m_slicing_params.object_print_z_min : object.layers()[layer_id - 2]->print_z;
} else {
new_layer.print_z = layer.print_z - layer.height - m_object_config->support_material_contact_distance;
new_layer.print_z = layer.bottom_z() - m_slicing_params.gap_object_support;
new_layer.bottom_z = new_layer.print_z;
new_layer.height = 0.;
// Ignore this contact area if it's too low.
@ -1597,12 +1601,12 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::top_contact_
// Contact layer will be printed with a normal flow, but
// it will support layers printed with a bridging flow.
if (SupportMaterialInternal::has_bridging_extrusions(layer)) {
if (m_object_config->thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer)) {
coordf_t bridging_height = 0.;
for (const LayerRegion *region : layer.regions())
bridging_height += region->region()->bridging_height_avg(*m_print_config);
bridging_height /= coordf_t(layer.regions().size());
coordf_t bridging_print_z = layer.print_z - bridging_height - m_object_config->support_material_contact_distance;
coordf_t bridging_print_z = layer.print_z - bridging_height - m_slicing_params.gap_support_object;
if (bridging_print_z >= m_slicing_params.first_print_layer_height - EPSILON) {
// Not below the first layer height means this layer is printable.
if (new_layer.print_z < m_slicing_params.first_print_layer_height + EPSILON) {
@ -1875,16 +1879,13 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
layer_new.height = m_slicing_params.soluble_interface ?
// Align the interface layer with the object's layer height.
object.layers()[layer_id + 1]->height :
// Place a bridge flow interface layer over the top surface.
//FIXME Check whether the bottom bridging surfaces are extruded correctly (no bridging flow correction applied?)
// According to Jindrich the bottom surfaces work well.
//FIXME test the bridging flow instead?
m_support_material_interface_flow.nozzle_diameter;
// Place a bridge flow interface layer or the normal flow interface layer over the top surface.
m_support_material_bottom_interface_flow.height();
layer_new.print_z = m_slicing_params.soluble_interface ? object.layers()[layer_id + 1]->print_z :
layer.print_z + layer_new.height + m_object_config->support_material_contact_distance.value;
layer.print_z + layer_new.height + m_slicing_params.gap_object_support;
layer_new.bottom_z = layer.print_z;
layer_new.idx_object_layer_below = layer_id;
layer_new.bridging = ! m_slicing_params.soluble_interface;
layer_new.bridging = ! m_slicing_params.soluble_interface && m_object_config->thick_bridges;
//FIXME how much to inflate the bottom surface, as it is being extruded with a bridging flow? The following line uses a normal flow.
//FIXME why is the offset positive? It will be trimmed by the object later on anyway, but then it just wastes CPU clocks.
layer_new.polygons = offset(touching, float(m_support_material_flow.scaled_width()), SUPPORT_SURFACES_OFFSET_PARAMETERS);
@ -2028,11 +2029,7 @@ PrintObjectSupportMaterial::MyLayersPtr PrintObjectSupportMaterial::bottom_conta
task_group.wait();
}
std::reverse(bottom_contacts.begin(), bottom_contacts.end());
// trim_support_layers_by_object(object, bottom_contacts, 0., 0., m_gap_xy);
trim_support_layers_by_object(object, bottom_contacts,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
trim_support_layers_by_object(object, bottom_contacts, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy);
} // ! top_contacts.empty()
return bottom_contacts;
@ -2462,10 +2459,7 @@ void PrintObjectSupportMaterial::generate_base_layers(
++ iRun;
#endif /* SLIC3R_DEBUG */
// trim_support_layers_by_object(object, intermediate_layers, 0., 0., m_gap_xy);
this->trim_support_layers_by_object(object, intermediate_layers,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value,
m_slicing_params.soluble_interface ? 0. : m_object_config->support_material_contact_distance.value, m_gap_xy);
this->trim_support_layers_by_object(object, intermediate_layers, m_slicing_params.gap_support_object, m_slicing_params.gap_object_support, m_gap_xy);
}
void PrintObjectSupportMaterial::trim_support_layers_by_object(
@ -2499,7 +2493,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
// BOOST_LOG_TRIVIAL(trace) << "Support generator - trim_support_layers_by_object - trimmming non-empty layer " << idx_layer << " of " << nonempty_layers.size();
assert(! support_layer.polygons.empty() && support_layer.print_z >= m_slicing_params.raft_contact_top_z + EPSILON);
// Find the overlapping object layers including the extra above / below gap.
coordf_t z_threshold = support_layer.print_z - support_layer.height - gap_extra_below + EPSILON;
coordf_t z_threshold = support_layer.bottom_print_z() - gap_extra_below + EPSILON;
idx_object_layer_overlapping = idx_higher_or_equal(
object.layers().begin(), object.layers().end(), idx_object_layer_overlapping,
[z_threshold](const Layer *layer){ return layer->print_z >= z_threshold; });
@ -2508,11 +2502,11 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
size_t i = idx_object_layer_overlapping;
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];
if (object_layer.print_z - object_layer.height > support_layer.print_z + gap_extra_above - EPSILON)
if (object_layer.bottom_z() > support_layer.print_z + gap_extra_above - EPSILON)
break;
polygons_append(polygons_trimming, offset(object_layer.lslices, gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
}
if (! m_slicing_params.soluble_interface) {
if (! m_slicing_params.soluble_interface && m_object_config->thick_bridges) {
// Collect all bottom surfaces, which will be extruded with a bridging flow.
for (; i < object.layers().size(); ++ i) {
const Layer &object_layer = *object.layers()[i];
@ -2526,6 +2520,7 @@ void PrintObjectSupportMaterial::trim_support_layers_by_object(
offset(to_expolygons(region->fill_surfaces.filter_by_type(stBottomBridge)),
gap_xy_scaled, SUPPORT_SURFACES_OFFSET_PARAMETERS));
if (region->region()->config().overhangs.value)
// Add bridging perimeters.
SupportMaterialInternal::collect_bridging_perimeter_areas(region->perimeters, gap_xy_scaled, polygons_trimming);
}
if (! some_region_overlaps)
@ -2709,17 +2704,23 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
m_object_config->support_material_interface_extruder.value > 0 && m_print_config->filament_soluble.get_at(m_object_config->support_material_interface_extruder.value - 1) &&
// Base extruder: Either "print with active extruder" not soluble.
(m_object_config->support_material_extruder.value == 0 || ! m_print_config->filament_soluble.get_at(m_object_config->support_material_extruder.value - 1));
int num_interface_layers = m_object_config->support_material_interface_layers.value;
int num_base_interface_layers = soluble_interface_non_soluble_base ? std::min(num_interface_layers / 2, 2) : 0;
int num_interface_layers_top = m_object_config->support_material_interface_layers;
int num_interface_layers_bottom = m_object_config->support_material_bottom_interface_layers;
if (num_interface_layers_bottom < 0)
num_interface_layers_bottom = num_interface_layers_top;
int num_base_interface_layers_top = soluble_interface_non_soluble_base ? std::min(num_interface_layers_top / 2, 2) : 0;
int num_base_interface_layers_bottom = soluble_interface_non_soluble_base ? std::min(num_interface_layers_bottom / 2, 2) : 0;
if (! intermediate_layers.empty() && num_interface_layers > 1) {
if (! intermediate_layers.empty() && (num_interface_layers_top > 1 || num_interface_layers_bottom > 1)) {
// For all intermediate layers, collect top contact surfaces, which are not further than support_material_interface_layers.
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start";
// Since the intermediate layer index starts at zero the number of interface layer needs to be reduced by 1.
-- num_interface_layers;
int num_interface_layers_only = num_interface_layers - num_base_interface_layers;
-- num_interface_layers_top;
-- num_interface_layers_bottom;
int num_interface_layers_only_top = num_interface_layers_top - num_base_interface_layers_top;
int num_interface_layers_only_bottom = num_interface_layers_bottom - num_base_interface_layers_bottom;
interface_layers.assign(intermediate_layers.size(), nullptr);
if (num_base_interface_layers)
if (num_base_interface_layers_top || num_base_interface_layers_bottom)
base_interface_layers.assign(intermediate_layers.size(), nullptr);
tbb::spin_mutex layer_storage_mutex;
// Insert a new layer into base_interface_layers, if intersection with base exists.
@ -2743,7 +2744,8 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
return &layer_new;
};
tbb::parallel_for(tbb::blocked_range<int>(0, int(intermediate_layers.size())),
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer, num_interface_layers, num_base_interface_layers, num_interface_layers_only,
[&bottom_contacts, &top_contacts, &intermediate_layers, &insert_layer,
num_interface_layers_top, num_interface_layers_bottom, num_base_interface_layers_top, num_base_interface_layers_bottom, num_interface_layers_only_top, num_interface_layers_only_bottom,
&interface_layers, &base_interface_layers](const tbb::blocked_range<int>& range) {
// Gather the top / bottom contact layers intersecting with num_interface_layers resp. num_interface_layers_only intermediate layers above / below
// this intermediate layer.
@ -2754,45 +2756,51 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
auto num_intermediate = int(intermediate_layers.size());
for (int idx_intermediate_layer = range.begin(); idx_intermediate_layer < range.end(); ++ idx_intermediate_layer) {
MyLayer &intermediate_layer = *intermediate_layers[idx_intermediate_layer];
// Top / bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers - 1)]->print_z;
coordf_t top_inteface_z = std::numeric_limits<coordf_t>::max();
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers + 1)]->bottom_z;
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
if (num_base_interface_layers > 0) {
// Some base interface layers will be generated.
if (num_interface_layers_only == 0)
// Only base interface layers to generate.
std::swap(top_inteface_z, bottom_interface_z);
else {
top_inteface_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only - 1)]->print_z;
bottom_interface_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only)]->bottom_z;
}
}
// Move idx_top_contact_first up until above the current print_z.
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
// Collect the top contact areas above this intermediate layer, below top_z.
Polygons polygons_top_contact_projected_interface;
Polygons polygons_top_contact_projected_base;
for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) {
const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
//FIXME maybe this adds one interface layer in excess?
if (top_contact_layer.bottom_z - EPSILON > top_z)
break;
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons);
}
// Move idx_bottom_contact_first up until touching bottom_z.
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
// Collect the top contact areas above this intermediate layer, below top_z.
Polygons polygons_bottom_contact_projected_interface;
Polygons polygons_bottom_contact_projected_base;
for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) {
const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact];
if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z)
break;
polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons);
if (num_interface_layers_top > 0) {
// Top Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
coordf_t top_z = intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_top - 1)]->print_z;
coordf_t top_inteface_z = std::numeric_limits<coordf_t>::max();
if (num_base_interface_layers_top > 0)
// Some top base interface layers will be generated.
top_inteface_z = num_interface_layers_only_top == 0 ?
// Only base interface layers to generate.
- std::numeric_limits<coordf_t>::max() :
intermediate_layers[std::min(num_intermediate - 1, idx_intermediate_layer + num_interface_layers_only_top - 1)]->print_z;
// Move idx_top_contact_first up until above the current print_z.
idx_top_contact_first = idx_higher_or_equal(top_contacts, idx_top_contact_first, [&intermediate_layer](const MyLayer *layer){ return layer->print_z >= intermediate_layer.print_z; }); // - EPSILON
// Collect the top contact areas above this intermediate layer, below top_z.
for (int idx_top_contact = idx_top_contact_first; idx_top_contact < int(top_contacts.size()); ++ idx_top_contact) {
const MyLayer &top_contact_layer = *top_contacts[idx_top_contact];
//FIXME maybe this adds one interface layer in excess?
if (top_contact_layer.bottom_z - EPSILON > top_z)
break;
polygons_append(top_contact_layer.bottom_z - EPSILON > top_inteface_z ? polygons_top_contact_projected_base : polygons_top_contact_projected_interface, top_contact_layer.polygons);
}
}
if (num_interface_layers_bottom > 0) {
// Bottom Z coordinate of a slab, over which we are collecting the top / bottom contact surfaces
coordf_t bottom_z = intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_bottom + 1)]->bottom_z;
coordf_t bottom_interface_z = - std::numeric_limits<coordf_t>::max();
if (num_base_interface_layers_bottom > 0)
// Some bottom base interface layers will be generated.
bottom_interface_z = num_interface_layers_only_bottom == 0 ?
// Only base interface layers to generate.
std::numeric_limits<coordf_t>::max() :
intermediate_layers[std::max(0, idx_intermediate_layer - num_interface_layers_only_bottom)]->bottom_z;
// Move idx_bottom_contact_first up until touching bottom_z.
idx_bottom_contact_first = idx_higher_or_equal(bottom_contacts, idx_bottom_contact_first, [bottom_z](const MyLayer *layer){ return layer->print_z >= bottom_z - EPSILON; });
// Collect the top contact areas above this intermediate layer, below top_z.
for (int idx_bottom_contact = idx_bottom_contact_first; idx_bottom_contact < int(bottom_contacts.size()); ++ idx_bottom_contact) {
const MyLayer &bottom_contact_layer = *bottom_contacts[idx_bottom_contact];
if (bottom_contact_layer.print_z - EPSILON > intermediate_layer.bottom_z)
break;
polygons_append(bottom_contact_layer.print_z - EPSILON > bottom_interface_z ? polygons_bottom_contact_projected_interface : polygons_bottom_contact_projected_base, bottom_contact_layer.polygons);
}
}
MyLayer *interface_layer = nullptr;
if (! polygons_bottom_contact_projected_interface.empty() || ! polygons_top_contact_projected_interface.empty()) {
interface_layer = insert_layer(
@ -2810,7 +2818,7 @@ std::pair<PrintObjectSupportMaterial::MyLayersPtr, PrintObjectSupportMaterial::M
// Compress contact_out, remove the nullptr items.
remove_nulls(interface_layers);
remove_nulls(base_interface_layers);
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - start";
BOOST_LOG_TRIVIAL(debug) << "PrintObjectSupportMaterial::generate_interface_layers() in parallel - end";
}
return base_and_interface_layers;
@ -2835,7 +2843,7 @@ static inline void fill_expolygon_generate_paths(
dst,
std::move(polylines),
role,
flow.mm3_per_mm(), flow.width, flow.height);
flow.mm3_per_mm(), flow.width(), flow.height());
}
static inline void fill_expolygons_generate_paths(
@ -2872,7 +2880,8 @@ static inline void fill_expolygons_with_sheath_generate_paths(
float density,
ExtrusionRole role,
const Flow &flow,
bool with_sheath)
bool with_sheath,
bool no_sort)
{
if (polygons.empty())
return;
@ -2892,8 +2901,12 @@ static inline void fill_expolygons_with_sheath_generate_paths(
for (ExPolygon &expoly : offset2_ex(polygons, float(SCALED_EPSILON), float(- SCALED_EPSILON - 0.5*flow.scaled_width()))) {
// Don't reorder the skirt and its infills.
auto eec = std::make_unique<ExtrusionEntityCollection>();
eec->no_sort = true;
std::unique_ptr<ExtrusionEntityCollection> eec;
if (no_sort) {
eec = std::make_unique<ExtrusionEntityCollection>();
eec->no_sort = true;
}
ExtrusionEntitiesPtr &out = no_sort ? eec->entities : dst;
// Draw the perimeters.
Polylines polylines;
polylines.reserve(expoly.holes.size() + 1);
@ -2903,10 +2916,11 @@ static inline void fill_expolygons_with_sheath_generate_paths(
pl.clip_end(clip_length);
polylines.emplace_back(std::move(pl));
}
extrusion_entities_append_paths(eec->entities, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width, flow.height);
extrusion_entities_append_paths(out, polylines, erSupportMaterial, flow.mm3_per_mm(), flow.width(), flow.height());
// Fill in the rest.
fill_expolygons_generate_paths(eec->entities, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
dst.emplace_back(eec.release());
fill_expolygons_generate_paths(out, offset_ex(expoly, float(-0.4 * spacing)), filler, fill_params, density, role, flow);
if (no_sort)
dst.emplace_back(eec.release());
}
}
@ -3011,8 +3025,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
if (n_contact_loops == 0 || top_contact_layer.empty())
return;
Flow flow = interface_flow_src;
flow.height = float(top_contact_layer.layer->height);
Flow flow = interface_flow_src.with_height(top_contact_layer.layer->height);
Polygons overhang_polygons;
if (top_contact_layer.layer->overhang_polygons != nullptr)
@ -3209,7 +3222,7 @@ void LoopInterfaceProcessor::generate(MyLayerExtruded &top_contact_layer, const
extrusion_entities_append_paths(
top_contact_layer.extrusions,
std::move(loop_lines),
erSupportMaterialInterface, flow.mm3_per_mm(), flow.width, flow.height);
erSupportMaterialInterface, flow.mm3_per_mm(), flow.width(), flow.height());
}
#ifdef SLIC3R_DEBUG
@ -3232,6 +3245,9 @@ static std::string dbg_index_to_color(int idx)
// Therefore the bottom interface spots are expanded a bit. The expanded regions may overlap with another bottom interface layers,
// leading to over extrusion, where they overlap. The over extrusion is better avoided as it often makes the interface layers
// to stick too firmly to the object.
//
// Modulate thickness (increase bottom_z) of extrusions_in_out generated for this_layer
// if they overlap with overlapping_layers, whose print_z is above this_layer.bottom_z() and below this_layer.print_z.
void modulate_extrusion_by_overlapping_layers(
// Extrusions generated for this_layer.
ExtrusionEntitiesPtr &extrusions_in_out,
@ -3320,8 +3336,8 @@ void modulate_extrusion_by_overlapping_layers(
// Collect the paths of this_layer.
{
Polylines &polylines = path_fragments.back().polylines;
for (ExtrusionEntitiesPtr::const_iterator it = extrusions_in_out.begin(); it != extrusions_in_out.end(); ++ it) {
ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(*it);
for (ExtrusionEntity *ee : extrusions_in_out) {
ExtrusionPath *path = dynamic_cast<ExtrusionPath*>(ee);
assert(path != nullptr);
polylines.emplace_back(Polyline(std::move(path->polyline)));
path_ends.emplace_back(std::pair<Point, Point>(polylines.back().points.front(), polylines.back().points.back()));
@ -3342,7 +3358,7 @@ void modulate_extrusion_by_overlapping_layers(
// Adjust the extrusion parameters for a reduced layer height and a non-bridging flow (nozzle_dmr = -1, does not matter).
assert(this_layer.print_z > overlapping_layer.print_z);
frag.height = float(this_layer.print_z - overlapping_layer.print_z);
frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f, false).mm3_per_mm();
frag.mm3_per_mm = Flow(frag.width, frag.height, -1.f).mm3_per_mm();
#ifdef SLIC3R_DEBUG
svg.draw(frag.polylines, dbg_index_to_color(i_overlapping_layer), scale_(0.1));
#endif /* SLIC3R_DEBUG */
@ -3481,7 +3497,6 @@ void PrintObjectSupportMaterial::generate_toolpaths(
const MyLayersPtr &interface_layers,
const MyLayersPtr &base_interface_layers) const
{
// Slic3r::debugf "Generating patterns\n";
// loop_interface_processor with a given circle radius.
LoopInterfaceProcessor loop_interface_processor(1.5 * m_support_material_interface_flow.scaled_width());
loop_interface_processor.n_contact_loops = this->has_contact_loops() ? 1 : 0;
@ -3492,7 +3507,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
coordf_t interface_density = std::min(1., m_support_material_interface_flow.spacing() / interface_spacing);
coordf_t support_spacing = m_object_config->support_material_spacing.value + m_support_material_flow.spacing();
coordf_t support_density = std::min(1., m_support_material_flow.spacing() / support_spacing);
if (m_object_config->support_material_interface_layers.value == 0) {
if (m_slicing_params.soluble_interface) {
// No interface layers allowed, print everything with the base support pattern.
interface_spacing = support_spacing;
interface_density = support_density;
@ -3565,7 +3580,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
//FIXME misusing contact_polygons for support columns.
((raft_layer.contact_polygons == nullptr) ? Polygons() : *raft_layer.contact_polygons);
if (! to_infill_polygons.empty()) {
Flow flow(float(m_support_material_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
assert(! raft_layer.bridging);
Flow flow(float(m_support_material_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter());
Fill * filler = filler_support.get();
filler->angle = raft_angle_base;
filler->spacing = m_support_material_flow.spacing();
@ -3579,7 +3595,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler, float(support_density),
// Extrusion parameters
erSupportMaterial, flow,
with_sheath);
with_sheath, false);
}
}
@ -3596,7 +3612,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// We don't use $base_flow->spacing because we need a constant spacing
// value that guarantees that all layers are correctly aligned.
filler->spacing = m_support_material_flow.spacing();
flow = Flow(float(m_support_material_interface_flow.width), float(raft_layer.height), m_support_material_flow.nozzle_diameter, raft_layer.bridging);
assert(! raft_layer.bridging);
flow = Flow(float(m_support_material_interface_flow.width()), float(raft_layer.height), m_support_material_flow.nozzle_diameter());
density = float(interface_density);
} else
continue;
@ -3611,7 +3628,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Extrusion parameters
(support_layer_id < m_slicing_params.base_raft_layers) ? erSupportMaterial : erSupportMaterialInterface, flow,
// sheath at first layer
support_layer_id == 0);
support_layer_id == 0, support_layer_id == 0);
}
});
@ -3621,12 +3638,20 @@ void PrintObjectSupportMaterial::generate_toolpaths(
std::vector<MyLayer*> overlapping;
};
struct LayerCache {
MyLayerExtruded bottom_contact_layer;
MyLayerExtruded top_contact_layer;
MyLayerExtruded base_layer;
MyLayerExtruded interface_layer;
MyLayerExtruded base_interface_layer;
std::vector<LayerCacheItem> overlaps;
MyLayerExtruded bottom_contact_layer;
MyLayerExtruded top_contact_layer;
MyLayerExtruded base_layer;
MyLayerExtruded interface_layer;
MyLayerExtruded base_interface_layer;
boost::container::static_vector<LayerCacheItem, 5> nonempty;
void add_nonempty_and_sort() {
for (MyLayerExtruded *item : { &bottom_contact_layer, &top_contact_layer, &interface_layer, &base_interface_layer, &base_layer })
if (! item->empty())
this->nonempty.emplace_back(item);
// Sort the layers with the same print_z coordinate by their heights, thickest first.
std::stable_sort(this->nonempty.begin(), this->nonempty.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; });
}
};
std::vector<LayerCache> layer_caches(support_layers.size(), LayerCache());
@ -3700,10 +3725,6 @@ void PrintObjectSupportMaterial::generate_toolpaths(
base_layer.merge(std::move(top_contact_layer));
else if (base_layer.empty() && !top_contact_layer.empty() && !top_contact_layer.layer->bridging)
std::swap(base_layer, top_contact_layer);
if (base_layer.could_merge(bottom_contact_layer))
base_layer.merge(std::move(bottom_contact_layer));
else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging)
std::swap(base_layer, bottom_contact_layer);
}
} else {
loop_interface_processor.generate(top_contact_layer, m_support_material_interface_flow);
@ -3713,6 +3734,12 @@ void PrintObjectSupportMaterial::generate_toolpaths(
if (top_contact_layer.could_merge(interface_layer))
top_contact_layer.merge(std::move(interface_layer));
}
if ((m_object_config->support_material_interface_layers == 0 || m_object_config->support_material_bottom_interface_layers == 0) && m_can_merge_support_regions) {
if (base_layer.could_merge(bottom_contact_layer))
base_layer.merge(std::move(bottom_contact_layer));
else if (base_layer.empty() && !bottom_contact_layer.empty() && !bottom_contact_layer.layer->bridging)
std::swap(base_layer, bottom_contact_layer);
}
#if 0
if ( ! interface_layer.empty() && ! base_layer.empty()) {
@ -3731,14 +3758,12 @@ void PrintObjectSupportMaterial::generate_toolpaths(
MyLayerExtruded &layer_ex = (i == 0) ? top_contact_layer : (i == 1 ? bottom_contact_layer : interface_layer);
if (layer_ex.empty() || layer_ex.polygons_to_extrude().empty())
continue;
bool interface_as_base = (&layer_ex == &interface_layer) && m_object_config->support_material_interface_layers.value == 0;
bool interface_as_base = (&layer_ex == &interface_layer) && m_slicing_params.soluble_interface;
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
Flow interface_flow(
float(layer_ex.layer->bridging ? layer_ex.layer->height : (interface_as_base ? m_support_material_flow.width : m_support_material_interface_flow.width)),
float(layer_ex.layer->height),
m_support_material_interface_flow.nozzle_diameter,
layer_ex.layer->bridging);
auto interface_flow = layer_ex.layer->bridging ?
Flow::bridging_flow(layer_ex.layer->height, m_support_material_bottom_interface_flow.nozzle_diameter()) :
(interface_as_base ? &m_support_material_flow : &m_support_material_interface_flow)->with_height(float(layer_ex.layer->height));
filler_interface->angle = interface_as_base ?
// If zero interface layers are configured, use the same angle as for the base layers.
angles[support_layer_id % angles.size()] :
@ -3762,11 +3787,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
Fill *filler = filler_base_interface.get();
//FIXME Bottom interfaces are extruded with the briding flow. Some bridging layers have its height slightly reduced, therefore
// the bridging flow does not quite apply. Reduce the flow to area of an ellipse? (A = pi * a * b)
Flow interface_flow(
float(base_interface_layer.layer->bridging ? base_interface_layer.layer->height : m_support_material_flow.width), // m_support_material_interface_flow.width)),
float(base_interface_layer.layer->height),
m_support_material_flow.nozzle_diameter,
base_interface_layer.layer->bridging);
assert(! base_interface_layer.layer->bridging);
Flow interface_flow = m_support_material_flow.with_height(float(base_interface_layer.layer->height));
filler->angle = interface_angle;
filler->spacing = m_support_material_interface_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / interface_density));
@ -3788,15 +3810,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler->angle = angles[support_layer_id % angles.size()];
// We don't use $base_flow->spacing because we need a constant spacing
// value that guarantees that all layers are correctly aligned.
Flow flow(
float(base_layer.layer->bridging ? base_layer.layer->height : m_support_material_flow.width),
float(base_layer.layer->height),
m_support_material_flow.nozzle_diameter,
base_layer.layer->bridging);
assert(! base_layer.layer->bridging);
auto flow = m_support_material_flow.with_height(float(base_layer.layer->height));
filler->spacing = m_support_material_flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / support_density));
float density = float(support_density);
bool sheath = with_sheath;
bool no_sort = false;
if (base_layer.layer->bottom_z < EPSILON) {
// Base flange (the 1st layer).
filler = filler_first_layer;
@ -3808,7 +3828,8 @@ void PrintObjectSupportMaterial::generate_toolpaths(
//FIXME When paralellizing, each thread shall have its own copy of the fillers.
filler->spacing = flow.spacing();
filler->link_max_length = coord_t(scale_(filler->spacing * link_max_length_factor / density));
sheath = true;
sheath = true;
no_sort = true;
}
fill_expolygons_with_sheath_generate_paths(
// Destination
@ -3819,7 +3840,7 @@ void PrintObjectSupportMaterial::generate_toolpaths(
filler, density,
// Extrusion parameters
erSupportMaterial, flow,
sheath);
sheath, no_sort);
}
@ -3828,24 +3849,13 @@ void PrintObjectSupportMaterial::generate_toolpaths(
base_layer.could_merge(base_interface_layer))
base_layer.merge(std::move(base_interface_layer));
layer_cache.overlaps.reserve(5);
if (! bottom_contact_layer.empty())
layer_cache.overlaps.push_back(&bottom_contact_layer);
if (! top_contact_layer.empty())
layer_cache.overlaps.push_back(&top_contact_layer);
if (! interface_layer.empty())
layer_cache.overlaps.push_back(&interface_layer);
if (! base_interface_layer.empty())
layer_cache.overlaps.push_back(&base_interface_layer);
if (! base_layer.empty())
layer_cache.overlaps.push_back(&base_layer);
// Sort the layers with the same print_z coordinate by their heights, thickest first.
std::sort(layer_cache.overlaps.begin(), layer_cache.overlaps.end(), [](const LayerCacheItem &lc1, const LayerCacheItem &lc2) { return lc1.layer_extruded->layer->height > lc2.layer_extruded->layer->height; });
layer_cache.add_nonempty_and_sort();
// Collect the support areas with this print_z into islands, as there is no need
// for retraction over these islands.
Polygons polys;
// Collect the extrusions, sorted by the bottom extrusion height.
for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) {
// Collect islands to polys.
layer_cache_item.layer_extruded->polygons_append(polys);
// The print_z of the top contact surfaces and bottom_z of the bottom contact surfaces are "free"
@ -3859,32 +3869,22 @@ void PrintObjectSupportMaterial::generate_toolpaths(
// Collect overlapping top/bottom surfaces.
layer_cache_item.overlapping.reserve(20);
coordf_t bottom_z = layer_cache_item.layer_extruded->layer->bottom_print_z() + EPSILON;
for (int i = int(idx_layer_bottom_contact) - 1; i >= 0 && bottom_contacts[i]->print_z > bottom_z; -- i)
layer_cache_item.overlapping.push_back(bottom_contacts[i]);
for (int i = int(idx_layer_top_contact) - 1; i >= 0 && top_contacts[i]->print_z > bottom_z; -- i)
layer_cache_item.overlapping.push_back(top_contacts[i]);
auto add_overlapping = [&layer_cache_item, bottom_z](const MyLayersPtr &layers, size_t idx_top) {
for (int i = int(idx_top) - 1; i >= 0 && layers[i]->print_z > bottom_z; -- i)
layer_cache_item.overlapping.push_back(layers[i]);
};
add_overlapping(top_contacts, idx_layer_top_contact);
if (layer_cache_item.layer_extruded->layer->layer_type == sltBottomContact) {
// Bottom contact layer may overlap with a base layer, which may be changed to interface layer.
for (int i = int(idx_layer_intermediate) - 1; i >= 0 && intermediate_layers[i]->print_z > bottom_z; -- i)
layer_cache_item.overlapping.push_back(intermediate_layers[i]);
for (int i = int(idx_layer_interface) - 1; i >= 0 && interface_layers[i]->print_z > bottom_z; -- i)
layer_cache_item.overlapping.push_back(interface_layers[i]);
for (int i = int(idx_layer_base_interface) - 1; i >= 0 && base_interface_layers[i]->print_z > bottom_z; -- i)
layer_cache_item.overlapping.push_back(base_interface_layers[i]);
add_overlapping(intermediate_layers, idx_layer_intermediate);
add_overlapping(interface_layers, idx_layer_interface);
add_overlapping(base_interface_layers, idx_layer_base_interface);
}
std::sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), MyLayersPtrCompare());
// Order the layers by lexicographically by an increasing print_z and a decreasing layer height.
std::stable_sort(layer_cache_item.overlapping.begin(), layer_cache_item.overlapping.end(), [](auto *l1, auto *l2) { return *l1 < *l2; });
}
if (! polys.empty())
expolygons_append(support_layer.support_islands.expolygons, union_ex(polys));
/* {
require "Slic3r/SVG.pm";
Slic3r::SVG::output("islands_" . $z . ".svg",
red_expolygons => union_ex($contact),
green_expolygons => union_ex($interface),
green_polylines => [ map $_->unpack->polyline, @{$layer->support_contact_fills} ],
polylines => [ map $_->unpack->polyline, @{$layer->support_fills} ],
);
} */
} // for each support_layer_id
});
@ -3895,7 +3895,9 @@ void PrintObjectSupportMaterial::generate_toolpaths(
for (size_t support_layer_id = range.begin(); support_layer_id < range.end(); ++ support_layer_id) {
SupportLayer &support_layer = *support_layers[support_layer_id];
LayerCache &layer_cache = layer_caches[support_layer_id];
for (LayerCacheItem &layer_cache_item : layer_cache.overlaps) {
// For all extrusion types at this print_z, ordered by decreasing layer height:
for (LayerCacheItem &layer_cache_item : layer_cache.nonempty) {
// Trim the extrusion height from the bottom by the overlapping layers.
modulate_extrusion_by_overlapping_layers(layer_cache_item.layer_extruded->extrusions, *layer_cache_item.layer_extruded->layer, layer_cache_item.overlapping);
support_layer.support_fills.append(std::move(layer_cache_item.layer_extruded->extrusions));
}

View file

@ -244,6 +244,7 @@ private:
Flow m_first_layer_flow;
Flow m_support_material_flow;
Flow m_support_material_interface_flow;
Flow m_support_material_bottom_interface_flow;
// Is merging of regions allowed? Could the interface & base support regions be printed with the same extruder?
bool m_can_merge_support_regions;

View file

@ -2063,4 +2063,22 @@ TriangleMesh make_sphere(double radius, double fa)
return mesh;
}
std::vector<std::vector<size_t> > create_neighbor_index(const indexed_triangle_set &its)
{
if (its.vertices.empty()) return {};
size_t res = its.indices.size() / its.vertices.size();
std::vector< std::vector<size_t> > index(its.vertices.size(),
reserve_vector<size_t>(res));
for (size_t fi = 0; fi < its.indices.size(); ++fi) {
auto &face = its.indices[fi];
index[face(0)].emplace_back(fi);
index[face(1)].emplace_back(fi);
index[face(2)].emplace_back(fi);
}
return index;
}
}

View file

@ -89,6 +89,12 @@ private:
std::deque<uint32_t> find_unvisited_neighbors(std::vector<unsigned char> &facet_visited) const;
};
// Create an index of faces belonging to each vertex. The returned vector can
// be indexed with vertex indices and contains a list of face indices for each
// vertex.
std::vector< std::vector<size_t> >
create_neighbor_index(const indexed_triangle_set &its);
enum FacetEdgeType {
// A general case, the cutting plane intersect a face at two different edges.
feGeneral,