This commit is contained in:
YuSanka 2019-09-05 15:31:38 +02:00
commit c4d90ed47a
15 changed files with 510 additions and 370 deletions

View File

@ -13,103 +13,200 @@
namespace Slic3r {
struct SurfaceGroupAttrib
struct SurfaceFillParams
{
SurfaceGroupAttrib() : is_solid(false), flow_width(0.f), pattern(-1) {}
bool operator==(const SurfaceGroupAttrib &other) const
{ return is_solid == other.is_solid && flow_width == other.flow_width && pattern == other.pattern; }
bool is_solid;
float flow_width;
// pattern is of type InfillPattern, -1 for an unset pattern.
int pattern;
SurfaceFillParams() : flow(0.f, 0.f, 0.f, false) { memset(this, 0, sizeof(*this)); }
// Zero based extruder ID.
unsigned int extruder;
// Infill pattern, adjusted for the density etc.
InfillPattern pattern;
// FillBase
// in unscaled coordinates
coordf_t spacing;
// infill / perimeter overlap, in unscaled coordinates
coordf_t overlap;
// Angle as provided by the region config, in radians.
float angle;
// Non-negative for a bridge.
float bridge_angle;
// FillParams
float density;
// Don't connect the fill lines around the inner perimeter.
bool dont_connect;
// Don't adjust spacing to fill the space evenly.
bool dont_adjust;
// width, height of extrusion, nozzle diameter, is bridge
// For the output, for fill generator.
Flow flow;
// For the output
ExtrusionRole extrusion_role;
// Various print settings?
// Index of this entry in a linear vector.
size_t idx;
bool operator<(const SurfaceFillParams &rhs) const {
#define RETURN_COMPARE_NON_EQUAL(KEY) if (this->KEY < rhs.KEY) return true; if (this->KEY > rhs.KEY) return false;
#define RETURN_COMPARE_NON_EQUAL_TYPED(TYPE, KEY) if (TYPE(this->KEY) < TYPE(rhs.KEY)) return true; if (TYPE(this->KEY) > TYPE(rhs.KEY)) return false;
// Sort first by decreasing bridging angle, so that the bridges are processed with priority when trimming one layer by the other.
if (this->bridge_angle > rhs.bridge_angle) return true;
if (this->bridge_angle < rhs.bridge_angle) return false;
RETURN_COMPARE_NON_EQUAL(extruder);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, pattern);
RETURN_COMPARE_NON_EQUAL(spacing);
RETURN_COMPARE_NON_EQUAL(overlap);
RETURN_COMPARE_NON_EQUAL(angle);
RETURN_COMPARE_NON_EQUAL(density);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_connect);
RETURN_COMPARE_NON_EQUAL_TYPED(unsigned, dont_adjust);
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_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->density == rhs.density &&
this->dont_connect == rhs.dont_connect &&
this->dont_adjust == rhs.dont_adjust &&
this->flow == rhs.flow &&
this->extrusion_role == rhs.extrusion_role;
}
};
// Generate infills for Slic3r::Layer::Region.
// The Slic3r::Layer::Region at this point of time may contain
// surfaces of various types (internal/bridge/top/bottom/solid).
// The infills are generated on the groups of surfaces with a compatible type.
// Returns an array of Slic3r::ExtrusionPath::Collection objects containing the infills generaed now
// and the thin fills generated by generate_perimeters().
void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
{
// Slic3r::debugf "Filling layer %d:\n", $layerm->layer->id;
double fill_density = layerm.region()->config().fill_density;
Flow infill_flow = layerm.flow(frInfill);
Flow solid_infill_flow = layerm.flow(frSolidInfill);
Flow top_solid_infill_flow = layerm.flow(frTopSolidInfill);
struct SurfaceFill {
SurfaceFill(const SurfaceFillParams& params) : region_id(size_t(-1)), surface(stCount, ExPolygon()), params(params) {}
Surfaces surfaces;
// merge adjacent surfaces
// in case of bridge surfaces, the ones with defined angle will be attached to the ones
// without any angle (shouldn't this logic be moved to process_external_surfaces()?)
{
Polygons polygons_bridged;
polygons_bridged.reserve(layerm.fill_surfaces.surfaces.size());
for (Surfaces::iterator it = layerm.fill_surfaces.surfaces.begin(); it != layerm.fill_surfaces.surfaces.end(); ++ it)
if (it->bridge_angle >= 0)
polygons_append(polygons_bridged, *it);
// group surfaces by distinct properties (equal surface_type, thickness, thickness_layers, bridge_angle)
// group is of type Slic3r::SurfaceCollection
//FIXME: Use some smart heuristics to merge similar surfaces to eliminate tiny regions.
std::vector<SurfacesPtr> groups;
layerm.fill_surfaces.group(&groups);
// merge compatible groups (we can generate continuous infill for them)
{
// cache flow widths and patterns used for all solid groups
// (we'll use them for comparing compatible groups)
std::vector<SurfaceGroupAttrib> group_attrib(groups.size());
for (size_t i = 0; i < groups.size(); ++ i) {
// we can only merge solid non-bridge surfaces, so discard
// non-solid surfaces
const Surface &surface = *groups[i].front();
if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) {
group_attrib[i].is_solid = true;
group_attrib[i].flow_width = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width;
group_attrib[i].pattern = surface.is_external() ?
size_t region_id;
Surface surface;
ExPolygons expolygons;
SurfaceFillParams params;
};
std::vector<SurfaceFill> group_fills(const Layer &layer)
{
std::vector<SurfaceFill> surface_fills;
// Fill in a map of a region & surface to SurfaceFillParams.
std::set<SurfaceFillParams> set_surface_params;
std::vector<std::vector<const SurfaceFillParams*>> region_to_surface_params(layer.regions().size(), std::vector<const SurfaceFillParams*>());
SurfaceFillParams params;
bool has_internal_voids = false;
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion &layerm = *layer.regions()[region_id];
region_to_surface_params[region_id].assign(layerm.fill_surfaces.size(), nullptr);
for (const Surface &surface : layerm.fill_surfaces.surfaces)
if (surface.surface_type == stInternalVoid)
has_internal_voids = true;
else {
FlowRole extrusion_role = (surface.surface_type == stTop) ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
bool is_bridge = layerm.layer()->id() > 0 && surface.is_bridge();
params.extruder = layerm.region()->extruder(extrusion_role);
params.pattern = layerm.region()->config().fill_pattern.value;
params.density = float(layerm.region()->config().fill_density);
if (surface.is_solid()) {
params.density = 100.f;
params.pattern = (surface.is_external() && ! is_bridge) ?
(surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) :
ipRectilinear;
}
}
// Loop through solid groups, find compatible groups and append them to this one.
for (size_t i = 0; i < groups.size(); ++ i) {
if (! group_attrib[i].is_solid)
continue;
for (size_t j = i + 1; j < groups.size();) {
if (group_attrib[i] == group_attrib[j]) {
// groups are compatible, merge them
groups[i].insert(groups[i].end(), groups[j].begin(), groups[j].end());
groups.erase(groups.begin() + j);
group_attrib.erase(group_attrib.begin() + j);
} else
++ j;
}
}
}
// Give priority to bridges. Process the bridges in the first round, the rest of the surfaces in the 2nd round.
for (size_t round = 0; round < 2; ++ round) {
for (std::vector<SurfacesPtr>::iterator it_group = groups.begin(); it_group != groups.end(); ++ it_group) {
const SurfacesPtr &group = *it_group;
bool is_bridge = group.front()->bridge_angle >= 0;
if (is_bridge != (round == 0))
continue;
// Make a union of polygons defining the infiill regions of a group, use a safety offset.
Polygons union_p = union_(to_polygons(*it_group), true);
// Subtract surfaces having a defined bridge_angle from any other, use a safety offset.
if (! polygons_bridged.empty() && ! is_bridge)
union_p = diff(union_p, polygons_bridged, true);
// subtract any other surface already processed
//FIXME Vojtech: Because the bridge surfaces came first, they are subtracted twice!
// Using group.front() as a template.
surfaces_append(surfaces, diff_ex(union_p, to_polygons(surfaces), true), *group.front());
}
}
}
ipRectilinear;
} else if (params.density <= 0)
continue;
params.extrusion_role =
is_bridge ?
erBridgeInfill :
(surface.is_solid() ?
((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) :
erInternalInfill);
params.bridge_angle = float(surface.bridge_angle);
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(
extrusion_role,
(surface.thickness == -1) ? layerm.layer()->height : surface.thickness, // extrusion height
is_bridge || Fill::use_bridge_flow(params.pattern), // bridge flow?
layerm.layer()->id() == 0, // first layer?
-1, // auto width
*layerm.layer()->object()
);
// Calculate flow spacing for infill pattern generation.
if (! surface.is_solid() && ! is_bridge) {
// it's internal infill, so we can calculate a generic flow spacing
// 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,
layerm.layer()->object()->config().layer_height.value, // TODO: handle infill_every_layers?
false, // no bridge
false, // no first layer
-1, // auto width
*layer.object()
).spacing();
} else
params.spacing = params.flow.spacing();
auto it_params = set_surface_params.find(params);
if (it_params == set_surface_params.end())
it_params = set_surface_params.insert(it_params, params);
region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()] = &(*it_params);
}
}
surface_fills.reserve(set_surface_params.size());
for (const SurfaceFillParams &params : set_surface_params) {
const_cast<SurfaceFillParams&>(params).idx = surface_fills.size();
surface_fills.emplace_back(params);
}
for (size_t region_id = 0; region_id < layer.regions().size(); ++ region_id) {
const LayerRegion &layerm = *layer.regions()[region_id];
for (const Surface &surface : layerm.fill_surfaces.surfaces)
if (surface.surface_type != stInternalVoid) {
const SurfaceFillParams *params = region_to_surface_params[region_id][&surface - &layerm.fill_surfaces.surfaces.front()];
if (params != nullptr) {
SurfaceFill &fill = surface_fills[params->idx];
if (fill.region_id = size_t(-1)) {
fill.region_id = region_id;
fill.surface = surface;
fill.expolygons.emplace_back(std::move(fill.surface.expolygon));
} else
fill.expolygons.emplace_back(surface.expolygon);
}
}
}
{
Polygons all_polygons;
for (SurfaceFill &fill : surface_fills)
if (! fill.expolygons.empty() && (fill.expolygons.size() > 1 || ! all_polygons.empty())) {
Polygons polys = to_polygons(std::move(fill.expolygons));
// Make a union of polygons, use a safety offset, subtract the preceding polygons.
// Bridges are processed first (see SurfaceFill::operator<())
fill.expolygons = all_polygons.empty() ? union_ex(polys, true) : diff_ex(polys, all_polygons, true);
append(all_polygons, std::move(polys));
}
}
// we need to detect any narrow surfaces that might collapse
// when adding spacing below
// such narrow surfaces are often generated in sloping walls
@ -119,155 +216,170 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
// we are going to grow such regions by overlapping them with the void (if any)
// TODO: detect and investigate whether there could be narrow regions without
// any void neighbors
{
coord_t distance_between_surfaces = std::max(
std::max(infill_flow.scaled_spacing(), solid_infill_flow.scaled_spacing()),
top_solid_infill_flow.scaled_spacing());
Polygons surfaces_polygons = to_polygons(surfaces);
Polygons collapsed = diff(
surfaces_polygons,
offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2),
true);
Polygons to_subtract;
to_subtract.reserve(collapsed.size() + number_polygons(surfaces));
for (Surfaces::const_iterator it_surface = surfaces.begin(); it_surface != surfaces.end(); ++ it_surface)
if (it_surface->surface_type == stInternalVoid)
polygons_append(to_subtract, *it_surface);
polygons_append(to_subtract, collapsed);
surfaces_append(
surfaces,
intersection_ex(
offset(collapsed, (float)distance_between_surfaces),
to_subtract,
true),
stInternalSolid);
if (has_internal_voids) {
// Internal voids are generated only if "infill_only_where_needed" or "infill_every_layers" are active.
coord_t distance_between_surfaces = 0;
Polygons surfaces_polygons;
Polygons voids;
int region_internal_infill = -1;
int region_solid_infill = -1;
int region_some_infill = -1;
for (SurfaceFill &surface_fill : surface_fills)
if (! surface_fill.expolygons.empty()) {
distance_between_surfaces = std::max(distance_between_surfaces, surface_fill.params.flow.scaled_spacing());
append((surface_fill.surface.surface_type == stInternalVoid) ? voids : surfaces_polygons, to_polygons(surface_fill.expolygons));
if (surface_fill.surface.surface_type == stInternalSolid)
region_internal_infill = (int)surface_fill.region_id;
if (surface_fill.surface.is_solid())
region_solid_infill = (int)surface_fill.region_id;
if (surface_fill.surface.surface_type != stInternalVoid)
region_some_infill = (int)surface_fill.region_id;
}
if (! voids.empty() && ! surfaces_polygons.empty()) {
// First clip voids by the printing polygons, as the voids were ignored by the loop above during mutual clipping.
voids = diff(voids, surfaces_polygons);
// Corners of infill regions, which would not be filled with an extrusion path with a radius of distance_between_surfaces/2
Polygons collapsed = diff(
surfaces_polygons,
offset2(surfaces_polygons, (float)-distance_between_surfaces/2, (float)+distance_between_surfaces/2),
true);
//FIXME why the voids are added to collapsed here? First it is expensive, second the result may lead to some unwanted regions being
// added if two offsetted void regions merge.
// polygons_append(voids, collapsed);
ExPolygons extensions = intersection_ex(offset(collapsed, (float)distance_between_surfaces), voids, true);
// Now find an internal infill SurfaceFill to add these extrusions to.
SurfaceFill *internal_solid_fill = nullptr;
unsigned int region_id = 0;
if (region_internal_infill != -1)
region_id = region_internal_infill;
else if (region_solid_infill != -1)
region_id = region_solid_infill;
else if (region_some_infill != -1)
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(layerm.layer()->height - surface_fill.params.flow.height) < EPSILON) {
internal_solid_fill = &surface_fill;
break;
}
if (internal_solid_fill == nullptr) {
// Produce another solid fill.
params.extruder = layerm.region()->extruder(frSolidInfill);
params.pattern = ipRectilinear;
params.density = 100.f;
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,
layerm.layer()->height, // extrusion height
false, // bridge flow?
layerm.layer()->id() == 0, // first layer?
-1, // auto width
*layer.object()
);
params.spacing = params.flow.spacing();
surface_fills.emplace_back(params);
surface_fills.back().surface.surface_type = stInternalSolid;
surface_fills.back().surface.thickness = layer.height;
surface_fills.back().expolygons = std::move(extensions);
} else {
append(extensions, std::move(internal_solid_fill->expolygons));
internal_solid_fill->expolygons = union_ex(extensions);
}
}
}
if (0) {
// require "Slic3r/SVG.pm";
// Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg",
// expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ],
// red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ],
// );
}
return surface_fills;
}
// friend to Layer
void Layer::make_fills()
{
for (LayerRegion *layerm : m_regions)
layerm->fills.clear();
std::vector<SurfaceFill> surface_fills = group_fills(*this);
const Slic3r::BoundingBox bbox = this->object()->bounding_box();
for (SurfaceFill &surface_fill : surface_fills) {
// Create the filler object.
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(surface_fill.params.pattern));
f->set_bounding_box(bbox);
f->layer_id = this->id();
f->z = this->print_z;
f->angle = surface_fill.params.angle;
f->spacing = surface_fill.params.spacing;
for (const Surface &surface : surfaces) {
if (surface.surface_type == stInternalVoid)
continue;
InfillPattern fill_pattern = layerm.region()->config().fill_pattern.value;
double density = fill_density;
FlowRole role = (surface.surface_type == stTop) ? frTopSolidInfill :
(surface.is_solid() ? frSolidInfill : frInfill);
bool is_bridge = layerm.layer()->id() > 0 && surface.is_bridge();
if (surface.is_solid()) {
density = 100.;
fill_pattern = (surface.is_external() && ! is_bridge) ?
(surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) :
ipRectilinear;
} else if (density <= 0)
continue;
// get filler object
std::unique_ptr<Fill> f = std::unique_ptr<Fill>(Fill::new_from_type(fill_pattern));
f->set_bounding_box(layerm.layer()->object()->bounding_box());
// calculate the actual flow we'll be using for this infill
coordf_t h = (surface.thickness == -1) ? layerm.layer()->height : surface.thickness;
Flow flow = layerm.region()->flow(
role,
h,
is_bridge || f->use_bridge_flow(), // bridge flow?
layerm.layer()->id() == 0, // first layer?
-1, // auto width
*layerm.layer()->object()
);
// calculate flow spacing for infill pattern generation
bool using_internal_flow = false;
if (! surface.is_solid() && ! is_bridge) {
// it's internal infill, so we can calculate a generic flow spacing
// for all layers, for avoiding the ugly effect of
// misaligned infill on first layer because of different extrusion width and
// layer height
Flow internal_flow = layerm.region()->flow(
frInfill,
layerm.layer()->object()->config().layer_height.value, // TODO: handle infill_every_layers?
false, // no bridge
false, // no first layer
-1, // auto width
*layerm.layer()->object()
);
f->spacing = internal_flow.spacing();
using_internal_flow = true;
} else {
f->spacing = flow.spacing();
}
bool using_internal_flow = ! surface_fill.surface.is_solid() && ! surface_fill.params.flow.bridge;
double link_max_length = 0.;
if (! is_bridge) {
if (! surface_fill.params.flow.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);
#else
if (density > 80.) // 80%
if (surface_fill.params.density > 80.) // 80%
link_max_length = 3. * f->spacing;
#endif
}
f->layer_id = layerm.layer()->id();
f->z = layerm.layer()->print_z;
f->angle = float(Geometry::deg2rad(layerm.region()->config().fill_angle.value));
// 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_(flow.nozzle_diameter) * LOOP_CLIPPING_LENGTH_OVER_NOZZLE_DIAMETER);
// f->layer_height = h;
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;
params.density = float(0.01 * density);
// params.dont_adjust = true;
params.dont_adjust = false;
Polylines polylines = f->fill_surface(&surface, params);
if (polylines.empty())
continue;
params.density = float(0.01 * surface_fill.params.density);
params.dont_adjust = surface_fill.params.dont_adjust; // false
// calculate actual flow from spacing (which might have been adjusted by the infill
// pattern generator)
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->flow_spacing
} else {
flow = Flow::new_from_spacing(f->spacing, flow.nozzle_diameter, (float)h, is_bridge || f->use_bridge_flow());
}
// Save into layer.
auto *eec = new ExtrusionEntityCollection();
out.entities.push_back(eec);
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
extrusion_entities_append_paths(
eec->entities, std::move(polylines),
is_bridge ?
erBridgeInfill :
(surface.is_solid() ?
((surface.surface_type == stTop) ? erTopSolidInfill : erSolidInfill) :
erInternalInfill),
flow.mm3_per_mm(), flow.width, flow.height);
for (ExPolygon &expoly : surface_fill.expolygons) {
surface_fill.surface.expolygon = std::move(expoly);
Polylines polylines = f->fill_surface(&surface_fill.surface, params);
if (! polylines.empty()) {
// 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;
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_mm3_per_mm = new_flow.mm3_per_mm();
flow_width = new_flow.width;
}
// Save into layer.
auto *eec = new ExtrusionEntityCollection();
m_regions[surface_fill.region_id]->fills.entities.push_back(eec);
// Only concentric fills are not sorted.
eec->no_sort = f->no_sort();
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);
}
}
}
// add thin fill regions
// thin_fills are of C++ Slic3r::ExtrusionEntityCollection, perl type Slic3r::ExtrusionPath::Collection
// Unpacks the collection, creates multiple collections per path.
// The path type could be ExtrusionPath, ExtrusionLoop or ExtrusionEntityCollection.
// Why the paths are unpacked?
for (const ExtrusionEntity *thin_fill : layerm.thin_fills.entities) {
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
out.entities.push_back(&collection);
collection.entities.push_back(thin_fill->clone());
}
for (LayerRegion *layerm : m_regions)
for (const ExtrusionEntity *thin_fill : layerm->thin_fills.entities) {
ExtrusionEntityCollection &collection = *(new ExtrusionEntityCollection());
layerm->fills.entities.push_back(&collection);
collection.entities.push_back(thin_fill->clone());
}
#ifndef NDEBUG
for (LayerRegion *layerm : m_regions)
for (size_t i = 0; i < layerm->fills.entities.size(); ++ i)
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != nullptr);
#endif
}
} // namespace Slic3r

View File

@ -34,7 +34,7 @@ Fill* Fill::new_from_type(const InfillPattern type)
case ipArchimedeanChords: return new FillArchimedeanChords();
case ipHilbertCurve: return new FillHilbertCurve();
case ipOctagramSpiral: return new FillOctagramSpiral();
default: throw std::invalid_argument("unknown type");;
default: throw std::invalid_argument("unknown type");
}
}
@ -45,6 +45,24 @@ Fill* Fill::new_from_type(const std::string &type)
return (it == enum_keys_map.end()) ? nullptr : new_from_type(InfillPattern(it->second));
}
// Force initialization of the Fill::use_bridge_flow() internal static map in a thread safe fashion even on compilers
// not supporting thread safe non-static data member initializers.
static bool use_bridge_flow_initializer = Fill::use_bridge_flow(ipGrid);
bool Fill::use_bridge_flow(const InfillPattern type)
{
static std::vector<unsigned char> cached;
if (cached.empty()) {
cached.assign(size_t(ipCount), 0);
for (size_t i = 0; i < cached.size(); ++ i) {
auto *fill = Fill::new_from_type((InfillPattern)i);
cached[i] = fill->use_bridge_flow();
delete fill;
}
}
return cached[type] != 0;
}
Polylines Fill::fill_surface(const Surface *surface, const FillParams &params)
{
// Perform offset.

View File

@ -70,6 +70,7 @@ public:
static Fill* new_from_type(const InfillPattern type);
static Fill* new_from_type(const std::string &type);
static bool use_bridge_flow(const InfillPattern type);
void set_bounding_box(const Slic3r::BoundingBox &bbox) { bounding_box = bbox; }

View File

@ -56,6 +56,8 @@ public:
// Enable some perimeter squish (see INSET_OVERLAP_TOLERANCE).
// 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.

View File

@ -662,15 +662,19 @@ void GCode::do_export(Print *print, const char *path, GCodePreviewData *preview_
throw std::runtime_error(msg);
}
if (print->config().remaining_times.value) {
BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for normal mode" << log_memory_info();
m_normal_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
GCodeTimeEstimator::PostProcessData normal_data = m_normal_time_estimator.get_post_process_data();
GCodeTimeEstimator::PostProcessData silent_data = m_silent_time_estimator.get_post_process_data();
bool remaining_times_enabled = print->config().remaining_times.value;
BOOST_LOG_TRIVIAL(debug) << "Time estimator post processing" << log_memory_info();
GCodeTimeEstimator::post_process(path_tmp, 60.0f, remaining_times_enabled ? &normal_data : nullptr, (remaining_times_enabled && m_silent_time_estimator_enabled) ? &silent_data : nullptr);
if (remaining_times_enabled)
{
m_normal_time_estimator.reset();
if (m_silent_time_estimator_enabled) {
BOOST_LOG_TRIVIAL(debug) << "Processing remaining times for silent mode" << log_memory_info();
m_silent_time_estimator.post_process_remaining_times(path_tmp, 60.0f);
if (m_silent_time_estimator_enabled)
m_silent_time_estimator.reset();
}
}
// starts analyzer calculations

View File

@ -173,9 +173,7 @@ namespace Slic3r {
const std::string GCodeTimeEstimator::Normal_Last_M73_Output_Placeholder_Tag = "; _TE_NORMAL_LAST_M73_OUTPUT_PLACEHOLDER";
const std::string GCodeTimeEstimator::Silent_Last_M73_Output_Placeholder_Tag = "; _TE_SILENT_LAST_M73_OUTPUT_PLACEHOLDER";
// temporary human readable form to use until not removed from gcode by the new post-process method
const std::string GCodeTimeEstimator::Color_Change_Tag = "PRINT_COLOR_CHANGE";
// const std::string GCodeTimeEstimator::Color_Change_Tag = "_TE_COLOR_CHANGE";
GCodeTimeEstimator::GCodeTimeEstimator(EMode mode)
: m_mode(mode)
@ -273,130 +271,138 @@ namespace Slic3r {
#endif // ENABLE_MOVE_STATS
}
bool GCodeTimeEstimator::post_process_remaining_times(const std::string& filename, float interval)
bool GCodeTimeEstimator::post_process(const std::string& filename, float interval_sec, const PostProcessData* const normal_mode, const PostProcessData* const silent_mode)
{
boost::nowide::ifstream in(filename);
if (!in.good())
throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for reading.\n"));
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for reading.\n"));
std::string path_tmp = filename + ".times";
std::string path_tmp = filename + ".postprocess";
FILE* out = boost::nowide::fopen(path_tmp.c_str(), "wb");
if (out == nullptr)
throw std::runtime_error(std::string("Remaining times export failed.\nCannot open file for writing.\n"));
throw std::runtime_error(std::string("Time estimator post process export failed.\nCannot open file for writing.\n"));
std::string time_mask;
switch (m_mode)
{
default:
case Normal:
{
time_mask = "M73 P%s R%s\n";
break;
}
case Silent:
{
time_mask = "M73 Q%s S%s\n";
break;
}
}
std::string normal_time_mask = "M73 P%s R%s\n";
std::string silent_time_mask = "M73 Q%s S%s\n";
char line_M73[64];
unsigned int g1_lines_count = 0;
float last_recorded_time = 0.0f;
std::string gcode_line;
// buffer line to export only when greater than 64K to reduce writing calls
std::string export_line;
char time_line[64];
G1LineIdToBlockIdMap::const_iterator it_line_id = m_g1_line_ids.begin();
while (std::getline(in, gcode_line))
{
if (!in.good())
{
fclose(out);
throw std::runtime_error(std::string("Remaining times export failed.\nError while reading from file.\n"));
}
// replaces placeholders for initial line M73 with the real lines
if (((m_mode == Normal) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag)) ||
((m_mode == Silent) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag)))
{
sprintf(time_line, time_mask.c_str(), "0", _get_time_minutes(m_time).c_str());
gcode_line = time_line;
}
// replaces placeholders for final line M73 with the real lines
else if (((m_mode == Normal) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag)) ||
((m_mode == Silent) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag)))
{
sprintf(time_line, time_mask.c_str(), "100", "0");
gcode_line = time_line;
}
else
gcode_line += "\n";
// add remaining time lines where needed
m_parser.parse_line(gcode_line,
[this, &it_line_id, &g1_lines_count, &last_recorded_time, &time_line, &gcode_line, time_mask, interval](GCodeReader& reader, const GCodeReader::GCodeLine& line)
{
if (line.cmd_is("G1"))
{
++g1_lines_count;
assert(it_line_id == m_g1_line_ids.end() || it_line_id->first >= g1_lines_count);
const Block *block = nullptr;
if (it_line_id != m_g1_line_ids.end() && it_line_id->first == g1_lines_count) {
if (line.has_e() && it_line_id->second < (unsigned int)m_blocks.size())
block = &m_blocks[it_line_id->second];
++it_line_id;
}
if (block != nullptr && block->elapsed_time != -1.0f) {
float block_remaining_time = m_time - block->elapsed_time;
if (std::abs(last_recorded_time - block_remaining_time) > interval)
{
sprintf(time_line, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / m_time)).c_str(), _get_time_minutes(block_remaining_time).c_str());
gcode_line += time_line;
last_recorded_time = block_remaining_time;
}
}
}
});
export_line += gcode_line;
if (export_line.length() > 65535)
{
fwrite((const void*)export_line.c_str(), 1, export_line.length(), out);
if (ferror(out))
{
in.close();
fclose(out);
boost::nowide::remove(path_tmp.c_str());
throw std::runtime_error(std::string("Remaining times export failed.\nIs the disk full?\n"));
}
export_line.clear();
}
}
if (export_line.length() > 0)
{
// helper function to write to disk
auto write_string = [&](const std::string& str) {
fwrite((const void*)export_line.c_str(), 1, export_line.length(), out);
if (ferror(out))
{
in.close();
fclose(out);
boost::nowide::remove(path_tmp.c_str());
throw std::runtime_error(std::string("Remaining times export failed.\nIs the disk full?\n"));
throw std::runtime_error(std::string("Time estimator post process export failed.\nIs the disk full?\n"));
}
export_line.clear();
};
GCodeReader parser;
unsigned int g1_lines_count = 0;
int normal_g1_line_id = 0;
float normal_last_recorded_time = 0.0f;
int silent_g1_line_id = 0;
float silent_last_recorded_time = 0.0f;
// helper function to process g1 lines
auto process_g1_line = [&](const PostProcessData* const data, const GCodeReader::GCodeLine& line, int& g1_line_id, float& last_recorded_time, const std::string& time_mask) {
if (data == nullptr)
return;
assert((g1_line_id >= (int)data->g1_line_ids.size()) || (data->g1_line_ids[g1_line_id].first >= g1_lines_count));
const Block* block = nullptr;
const G1LineIdToBlockId& map_item = data->g1_line_ids[g1_line_id];
if ((g1_line_id < (int)data->g1_line_ids.size()) && (map_item.first == g1_lines_count))
{
if (line.has_e() && (map_item.second < (unsigned int)data->blocks.size()))
block = &data->blocks[map_item.second];
++g1_line_id;
}
if ((block != nullptr) && (block->elapsed_time != -1.0f))
{
float block_remaining_time = data->time - block->elapsed_time;
if (std::abs(last_recorded_time - block_remaining_time) > interval_sec)
{
sprintf(line_M73, time_mask.c_str(), std::to_string((int)(100.0f * block->elapsed_time / data->time)).c_str(), _get_time_minutes(block_remaining_time).c_str());
gcode_line += line_M73;
last_recorded_time = block_remaining_time;
}
}
};
while (std::getline(in, gcode_line))
{
if (!in.good())
{
fclose(out);
throw std::runtime_error(std::string("Time estimator post process export failed.\nError while reading from file.\n"));
}
// check tags
// remove color change tag
if (gcode_line == "; " + Color_Change_Tag)
continue;
// replaces placeholders for initial line M73 with the real lines
if ((normal_mode != nullptr) && (gcode_line == Normal_First_M73_Output_Placeholder_Tag))
{
sprintf(line_M73, normal_time_mask.c_str(), "0", _get_time_minutes(normal_mode->time).c_str());
gcode_line = line_M73;
}
else if ((silent_mode != nullptr) && (gcode_line == Silent_First_M73_Output_Placeholder_Tag))
{
sprintf(line_M73, silent_time_mask.c_str(), "0", _get_time_minutes(silent_mode->time).c_str());
gcode_line = line_M73;
}
// replaces placeholders for final line M73 with the real lines
else if ((normal_mode != nullptr) && (gcode_line == Normal_Last_M73_Output_Placeholder_Tag))
{
sprintf(line_M73, normal_time_mask.c_str(), "100", "0");
gcode_line = line_M73;
}
else if ((silent_mode != nullptr) && (gcode_line == Silent_Last_M73_Output_Placeholder_Tag))
{
sprintf(line_M73, silent_time_mask.c_str(), "100", "0");
gcode_line = line_M73;
}
else
gcode_line += "\n";
// add remaining time lines where needed
parser.parse_line(gcode_line,
[&](GCodeReader& reader, const GCodeReader::GCodeLine& line)
{
if (line.cmd_is("G1"))
{
++g1_lines_count;
process_g1_line(silent_mode, line, silent_g1_line_id, silent_last_recorded_time, silent_time_mask);
process_g1_line(normal_mode, line, normal_g1_line_id, normal_last_recorded_time, normal_time_mask);
}
});
export_line += gcode_line;
if (export_line.length() > 65535)
write_string(export_line);
}
if (!export_line.empty())
write_string(export_line);
fclose(out);
in.close();
if (rename_file(path_tmp, filename))
throw std::runtime_error(std::string("Failed to rename the output G-code file from ") + path_tmp + " to " + filename + '\n' +
"Is " + path_tmp + " locked?" + '\n');
"Is " + path_tmp + " locked?" + '\n');
return true;
}

View File

@ -213,9 +213,19 @@ namespace Slic3r {
typedef std::map<Block::EMoveType, MoveStats> MovesStatsMap;
#endif // ENABLE_MOVE_STATS
public:
typedef std::pair<unsigned int, unsigned int> G1LineIdToBlockId;
typedef std::vector<G1LineIdToBlockId> G1LineIdToBlockIdMap;
struct PostProcessData
{
const G1LineIdToBlockIdMap& g1_line_ids;
const BlocksList& blocks;
float time;
PostProcessData(const G1LineIdToBlockIdMap& g1_line_ids, const BlocksList& blocks, float time) : g1_line_ids(g1_line_ids), blocks(blocks), time(time) {}
};
private:
EMode m_mode;
GCodeReader m_parser;
@ -263,11 +273,12 @@ namespace Slic3r {
void calculate_time_from_lines(const std::vector<std::string>& gcode_lines);
// Process the gcode contained in the file with the given filename,
// placing in it new lines (M73) containing the remaining time, at the given interval in seconds
// and saving the result back in the same file
// This time estimator should have been already used to calculate the time estimate for the gcode
// contained in the given file before to call this method
bool post_process_remaining_times(const std::string& filename, float interval_sec);
// replacing placeholders with correspondent new lines M73
// placing new lines M73 (containing the remaining time) where needed (in dependence of the given interval in seconds)
// and removing working tags (as those used for color changes)
// if normal_mode == nullptr no M73 line will be added for normal mode
// if silent_mode == nullptr no M73 line will be added for silent mode
static bool post_process(const std::string& filename, float interval_sec, const PostProcessData* const normal_mode, const PostProcessData* const silent_mode);
// Set current position on the given axis with the given value
void set_axis_position(EAxis axis, float position);
@ -362,6 +373,8 @@ namespace Slic3r {
// Return an estimate of the memory consumed by the time estimator.
size_t memory_used() const;
PostProcessData get_post_process_data() const { return PostProcessData(m_g1_line_ids, m_blocks, m_time); }
private:
void _reset();
void _reset_time();

View File

@ -171,21 +171,6 @@ void Layer::make_perimeters()
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id() << " - Done";
}
void Layer::make_fills()
{
#ifdef SLIC3R_DEBUG
printf("Making fills for layer " PRINTF_ZU "\n", this->id());
#endif
for (LayerRegion *layerm : m_regions) {
layerm->fills.clear();
make_fill(*layerm, layerm->fills);
#ifndef NDEBUG
for (size_t i = 0; i < layerm->fills.entities.size(); ++ i)
assert(dynamic_cast<ExtrusionEntityCollection*>(layerm->fills.entities[i]) != NULL);
#endif
}
}
void Layer::export_region_slices_to_svg(const char *path) const
{
BoundingBox bbox;

View File

@ -74,7 +74,7 @@ public:
config(config), object_config(object_config), print_config(print_config),
loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces),
_ext_mm3_per_mm(-1), _mm3_per_mm(-1), _mm3_per_mm_overhang(-1)
{};
{}
void process();
private:

View File

@ -39,6 +39,8 @@ class PrintRegion
public:
const Print* print() const { return m_print; }
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;
// Average diameter of nozzles participating on extruding this region.
coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const;

View File

@ -35,7 +35,7 @@ enum PrintHostType {
enum InfillPattern {
ipRectilinear, ipGrid, ipTriangles, ipStars, ipCubic, ipLine, ipConcentric, ipHoneycomb, ip3DHoneycomb,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral,
ipGyroid, ipHilbertCurve, ipArchimedeanChords, ipOctagramSpiral, ipCount,
};
enum SupportMaterialPattern {

View File

@ -2,6 +2,21 @@
namespace Slic3r {
// 1-based extruder identifier for this region and role.
unsigned int PrintRegion::extruder(FlowRole role) const
{
size_t extruder = 0;
if (role == frPerimeter || role == frExternalPerimeter)
extruder = m_config.perimeter_extruder;
else if (role == frInfill)
extruder = m_config.infill_extruder;
else if (role == frSolidInfill || role == frTopSolidInfill)
extruder = m_config.solid_infill_extruder;
else
throw std::invalid_argument("Unknown role");
return extruder;
}
Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool first_layer, double width, const PrintObject &object) const
{
ConfigOptionFloatOrPercent config_width;
@ -28,24 +43,13 @@ Flow PrintRegion::flow(FlowRole role, double layer_height, bool bridge, bool fir
throw std::invalid_argument("Unknown role");
}
}
if (config_width.value == 0) {
if (config_width.value == 0)
config_width = object.config().extrusion_width;
}
// get the configured nozzle_diameter for the extruder associated
// to the flow role requested
size_t extruder = 0; // 1-based
if (role == frPerimeter || role == frExternalPerimeter) {
extruder = m_config.perimeter_extruder;
} else if (role == frInfill) {
extruder = m_config.infill_extruder;
} else if (role == frSolidInfill || role == frTopSolidInfill) {
extruder = m_config.solid_infill_extruder;
} else {
throw std::invalid_argument("Unknown role");
}
double nozzle_diameter = m_print->config().nozzle_diameter.get_at(extruder-1);
// 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);
}

View File

@ -24,9 +24,8 @@ enum SurfaceType {
stInternalVoid,
// Inner/outer perimeters.
stPerimeter,
// Last surface type, if the SurfaceType is used as an index into a vector.
stLast,
stCount = stLast + 1
// Number of SurfaceType enums.
stCount,
};
class Surface

View File

@ -37,6 +37,7 @@ public:
void clear() { surfaces.clear(); }
bool empty() const { return surfaces.empty(); }
size_t size() const { return surfaces.size(); }
bool has(SurfaceType type) const {
for (const Surface &surface : this->surfaces)
if (surface.surface_type == type) return true;

View File

@ -65,13 +65,6 @@ new_from_type(CLASS, type)
OUTPUT:
RETVAL
void
make_fill(CLASS, layer_region, out_append)
char* CLASS;
LayerRegion* layer_region;
ExtrusionEntityCollection* out_append;
CODE:
make_fill(*layer_region, *out_append);
%}
};