OrcaSlicer/src/libslic3r/SLAPrintSteps.cpp
bubnikv 4e11552da9 Spiral vase improvements and bugfixes.
Fixes Connecting / expanding Bottom Layers to Vase Perimeter #253
Fixes Slicing error in vase mode #452
Fixes Slicing Issue (Vase Mode, 0.6mm dmr nozzle) #1887
Fixes Top fill pattern isn't used in spiral vase mode #2533
Fixes Cisar's vase doesn't slice correctly, creates artefacts #3595

When the model is sliced, all the contours are newly oriented
counter-clockwise (even holes), merged and then only the largest area
contour is retained. In perimeter generator, if the largest contour
splits into multiple perimeters, newly only the largest area perimeter
is retained in spiral vase mode. These two changes solve #3595 and similar.

The infill is newly calculated only for the bottom solid layers
if the spiral vase mode is active (removes various unwanted infill
along the vase walls), and the last bottom solid layer is switched
to a top solid pattern (solves #2533).

The thin walls are newly enforced to be disabled in spiral vase mode,
and the "ensure vertical shell wall" is enforced in spiral vase mode
to extend the bottom of the vase to the vase hull (fixes #253).
2020-02-08 21:36:43 +01:00

906 lines
33 KiB
C++

#include <libslic3r/SLAPrintSteps.hpp>
#include <libslic3r/MeshBoolean.hpp>
// Need the cylinder method for the the drainholes in hollowing step
#include <libslic3r/SLA/SupportTreeBuilder.hpp>
#include <libslic3r/SLA/Concurrency.hpp>
#include <libslic3r/SLA/Pad.hpp>
#include <libslic3r/SLA/SupportPointGenerator.hpp>
#include <libslic3r/ClipperUtils.hpp>
// For geometry algorithms with native Clipper types (no copies and conversions)
#include <libnest2d/backends/clipper/geometries.hpp>
#include <boost/log/trivial.hpp>
#include "I18N.hpp"
//! macro used to mark string used at localization,
//! return same string
#define L(s) Slic3r::I18N::translate(s)
namespace Slic3r {
namespace {
const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS = {
10, // slaposHollowing,
10, // slaposDrillHoles
10, // slaposObjectSlice,
20, // slaposSupportPoints,
10, // slaposSupportTree,
10, // slaposPad,
30, // slaposSliceSupports,
};
std::string OBJ_STEP_LABELS(size_t idx)
{
switch (idx) {
case slaposHollowing: return L("Hollowing model");
case slaposDrillHoles: return L("Drilling holes into model.");
case slaposObjectSlice: return L("Slicing model");
case slaposSupportPoints: return L("Generating support points");
case slaposSupportTree: return L("Generating support tree");
case slaposPad: return L("Generating pad");
case slaposSliceSupports: return L("Slicing supports");
default:;
}
assert(false);
return "Out of bounds!";
}
const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS = {
10, // slapsMergeSlicesAndEval
90, // slapsRasterize
};
std::string PRINT_STEP_LABELS(size_t idx)
{
switch (idx) {
case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics");
case slapsRasterize: return L("Rasterizing layers");
default:;
}
assert(false); return "Out of bounds!";
}
}
SLAPrint::Steps::Steps(SLAPrint *print)
: m_print{print}
, m_rng{std::random_device{}()}
, objcount{m_print->m_objects.size()}
, ilhd{m_print->m_material_config.initial_layer_height.getFloat()}
, ilh{float(ilhd)}
, ilhs{scaled(ilhd)}
, objectstep_scale{(max_objstatus - min_objstatus) / (objcount * 100.0)}
{}
void SLAPrint::Steps::hollow_model(SLAPrintObject &po)
{
po.m_hollowing_data.reset();
if (! po.m_config.hollowing_enable.getBool()) {
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())
BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
else {
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
po.m_hollowing_data->interior = *meshptr;
}
}
// 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());
if (! is_hollowed && ! needs_drilling) {
// In this case we can dump any data that might have been
// generated on previous runs.
po.m_hollowing_data.reset();
return;
}
if (! po.m_hollowing_data)
po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
// Hollowing and/or drilling is active, m_hollowing_data is valid.
// Regenerate hollowed mesh, even if it was there already. It may contain
// 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 (! needs_drilling) {
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) {
holept.normal += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
holept.normal.normalize();
holept.pos += Vec3f{dist(m_rng), dist(m_rng), dist(m_rng)};
TriangleMesh m = sla::to_triangle_mesh(holept.to_mesh());
m.require_shared_vertices();
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 std::runtime_error(L("Too much 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);
} catch (const std::runtime_error &) {
throw std::runtime_error(L(
"Drilling holes into the mesh failed. "
"This is usually caused by broken model. Try to fix it first."));
}
}
// The slicing will be performed on an imaginary 1D grid which starts from
// the bottom of the bounding box created around the supported model. So
// the first layer which is usually thicker will be part of the supports
// not the model geometry. Exception is when the model is not in the air
// (elevation is zero) and no pad creation was requested. In this case the
// model geometry starts on the ground level and the initial layer is part
// 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();
// 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);
auto && bb3d = mesh.bounding_box();
double minZ = bb3d.min(Z) - po.get_elevation();
double maxZ = bb3d.max(Z);
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 std::runtime_error(
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);
// sla::DrainHoles drainholes = po.transformed_drainhole_points();
// cut_drainholes(po.m_model_slices, slice_grid, closing_r, drainholes, thr);
auto mit = slindex_it;
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
coord_t clpr_offs = scaled(doffs);
for(size_t id = 0;
id < po.m_model_slices.size() && mit != po.m_slice_index.end();
id++)
{
// We apply the printer correction offset here.
if(clpr_offs != 0)
po.m_model_slices[id] =
offset_ex(po.m_model_slices[id], float(clpr_offs));
mit->set_model_slice_idx(po, id); ++mit;
}
if(po.m_config.supports_enable.getBool() || po.m_config.pad_enable.getBool())
{
po.m_supportdata.reset(new SLAPrintObject::SupportData(mesh));
}
}
// In this step we check the slices, identify island and cover them with
// support points. Then we sprinkle the rest of the mesh.
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();
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;
// Tell the mesh where drain holes are. Although the points are
// 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(
po.m_supportdata->emesh, po.get_model_slices(), heights, config,
[this]() { throw_if_canceled(); }, statuscb);
// Now let's extract the result.
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"),
SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
} else {
// There are either some points on the front-end, or the user
// removed them on purpose. No calculation will be done.
po.m_supportdata->pts = po.transformed_support_points();
}
// If the zero elevation mode is engaged, we have to filter out all the
// points that are on the bottom of the object
if (is_zero_elevation(po.config())) {
double tolerance = po.config().pad_enable.getBool() ?
po.m_config.pad_wall_thickness.getFloat() :
po.m_config.support_base_height.getFloat();
remove_bottom_points(po.m_supportdata->pts,
po.m_supportdata->emesh.ground_level(),
tolerance);
}
}
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);
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))
report_status(current, OBJ_STEP_LABELS(slaposSupportTree),
SlicingStatus::DEFAULT, logmsg);
};
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);
}
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
// get a sample from the bottom of the mesh and use it for pad
// creation.
sla::pad_blueprint(trmesh, bp, float(pad_h),
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 std::runtime_error(
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);
}
// Slicing the support geometries similarly to the model slicing procedure.
// If the pad had been added previously (see step "base_pool" than it will
// 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));
}
double doffs = m_print->m_printer_config.absolute_correction.getFloat();
coord_t clpr_offs = scaled(doffs);
for (size_t i = 0; i < sd->support_slices.size() && i < po.m_slice_index.size(); ++i) {
// We apply the printer correction offset here.
if (clpr_offs != 0)
sd->support_slices[i] = offset_ex(sd->support_slices[i], float(clpr_offs));
po.m_slice_index[i].set_support_slice_idx(po, i);
}
// Using RELOAD_SLA_PREVIEW to tell the Plater to pass the update
// status to the 3D preview to load the SLA slices.
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
}
using ClipperPoint = ClipperLib::IntPoint;
using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
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);
}
// get polygons for all instances in the object
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)
poly.Contour.emplace_back(it->x(), it->y());
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());
else
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 std::runtime_error(
L("There are unprintable objects. Try to "
"adjust support settings to make the "
"objects printable."));
coord_t lvlid = slicerecord.print_level() - gndlvl;
// Neat trick to round the layer levels to the grid.
lvlid = eps * (lvlid / eps);
auto it = std::lower_bound(printer_input.begin(),
printer_input.end(),
PrintLayer(lvlid));
if(it == printer_input.end() || it->level() != lvlid)
it = printer_input.insert(it, PrintLayer(lvlid));
it->add(slicerecord);
}
}
}
// 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);
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 = [
// 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](PrintLayer& layer, size_t sliced_layer_cnt)
{
// 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
if (sliced_layer_cnt < 3)
estim_time += init_exp_time;
else if (fade_layer_time > exp_time)
{
fade_layer_time -= delta_fade_time;
estim_time += fade_layer_time;
}
else
estim_time += exp_time;
estim_time += tilt_time;
}
};
// sequential version for debugging:
// for(size_t i = 0; i < m_printer_input.size(); ++i) printlayerfn(i);
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), 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)
print_statistics.estimated_print_time = std::nan("");
else
print_statistics.estimated_print_time = estim_time;
print_statistics.fast_layers_count = fast_layers;
print_statistics.slow_layers_count = slow_layers;
report_status(-2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
}
// Rasterizing the model objects, and their supports
void SLAPrint::Steps::rasterize()
{
if(canceled()) return;
auto &print_statistics = m_print->m_print_statistics;
auto &printer_input = m_print->m_printer_input;
// Set up the printer, allocate space for all the layers
sla::RasterWriter &printer = m_print->init_printer();
auto lvlcnt = unsigned(printer_input.size());
printer.layers(lvlcnt);
// 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) / 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, &printer, increment, &dstatus, &pst]
(PrintLayer& printlayer, size_t idx)
{
if(canceled()) return;
auto level_id = unsigned(idx);
// Switch to the appropriate layer in the printer
printer.begin_layer(level_id);
for(const ClipperLib::Polygon& poly : printlayer.transformed_slices())
printer.draw_polygon(poly, level_id);
// Finish the layer for later saving it.
printer.finish_layer(level_id);
// Status indication guarded with the spinlock
{
Lock lck(slck);
dstatus += increment;
double st = std::round(dstatus);
if(st > pst) {
report_status(st, PRINT_STEP_LABELS(slapsRasterize));
pst = st;
}
}
};
// last minute escape
if(canceled()) return;
// Sequential version (for testing)
// for(unsigned l = 0; l < lvlcnt; ++l) lvlfn(l);
// Print all the layers in parallel
sla::ccr::enumerate(printer_input.begin(), printer_input.end(), lvlfn);
// Set statistics values to the printer
sla::RasterWriter::PrintStatistics stats;
stats.used_material = (print_statistics.objects_used_material +
print_statistics.support_used_material) / 1000;
int num_fade = m_print->m_default_object_config.faded_layers.getInt();
stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
stats.num_fast = print_statistics.fast_layers_count;
stats.num_slow = print_statistics.slow_layers_count;
stats.estimated_print_time_s = print_statistics.estimated_print_time;
printer.set_statistics(stats);
}
std::string SLAPrint::Steps::label(SLAPrintObjectStep step)
{
return OBJ_STEP_LABELS(step);
}
std::string SLAPrint::Steps::label(SLAPrintStep step)
{
return PRINT_STEP_LABELS(step);
}
double SLAPrint::Steps::progressrange(SLAPrintObjectStep step) const
{
return OBJ_STEP_LEVELS[step] * objectstep_scale;
}
double SLAPrint::Steps::progressrange(SLAPrintStep step) const
{
return PRINT_STEP_LEVELS[step] * (100 - max_objstatus) / 100.0;
}
void SLAPrint::Steps::execute(SLAPrintObjectStep step, SLAPrintObject &obj)
{
switch(step) {
case slaposHollowing: hollow_model(obj); break;
case slaposDrillHoles: drill_holes(obj); break;
case slaposObjectSlice: slice_model(obj); break;
case slaposSupportPoints: support_points(obj); break;
case slaposSupportTree: support_tree(obj); break;
case slaposPad: generate_pad(obj); break;
case slaposSliceSupports: slice_supports(obj); break;
case slaposCount: assert(false);
}
}
void SLAPrint::Steps::execute(SLAPrintStep step)
{
switch (step) {
case slapsMergeSlicesAndEval: merge_slices_and_eval_stats(); break;
case slapsRasterize: rasterize(); break;
case slapsCount: assert(false);
}
}
}