Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/supermerill/SuperSlicer.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt2
-rw-r--r--src/libslic3r/CMakeLists.txt2
-rw-r--r--src/libslic3r/SLA/Common.cpp235
-rw-r--r--src/libslic3r/SLA/Hollowing.cpp26
-rw-r--r--src/libslic3r/SLA/Hollowing.hpp21
-rw-r--r--src/libslic3r/SLA/RasterWriter.cpp2
-rw-r--r--src/libslic3r/SLA/RasterWriter.hpp8
-rw-r--r--src/libslic3r/SLAPrint.cpp1017
-rw-r--r--src/libslic3r/SLAPrint.hpp50
-rw-r--r--src/libslic3r/SLAPrintSteps.cpp848
-rw-r--r--src/libslic3r/SLAPrintSteps.hpp68
-rw-r--r--src/slic3r/GUI/Plater.cpp7
-rw-r--r--tests/libslic3r/test_hollowing.cpp6
13 files changed, 1215 insertions, 1077 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f7829fc9a..4d23556a7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -102,7 +102,7 @@ list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake/modules/)
enable_testing ()
# Enable C++11 language standard.
-set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
if(NOT WIN32)
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index f6934ac8e..0d483d997 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -150,6 +150,8 @@ add_library(libslic3r STATIC
ShortestPath.cpp
ShortestPath.hpp
SLAPrint.cpp
+ SLAPrintSteps.cpp
+ SLAPrintSteps.hpp
SLAPrint.hpp
Slicing.cpp
Slicing.hpp
diff --git a/src/libslic3r/SLA/Common.cpp b/src/libslic3r/SLA/Common.cpp
index f85731603..caabdd755 100644
--- a/src/libslic3r/SLA/Common.cpp
+++ b/src/libslic3r/SLA/Common.cpp
@@ -1,11 +1,13 @@
#include <cmath>
#include <libslic3r/SLA/Common.hpp>
+#include <libslic3r/SLA/Concurrency.hpp>
#include <libslic3r/SLA/SupportTree.hpp>
#include <libslic3r/SLA/SpatIndex.hpp>
#include <libslic3r/SLA/EigenMesh3D.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
#include <libslic3r/SLA/Clustering.hpp>
+
// Workaround: IGL signed_distance.h will define PI in the igl namespace.
#undef PI
@@ -351,128 +353,131 @@ PointSet normals(const PointSet& points,
std::function<void()> thr, // throw on cancel
const std::vector<unsigned>& pt_indices)
{
- if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
+ if (points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
return {};
-
+
std::vector<unsigned> range = pt_indices;
- if(range.empty()) {
+ if (range.empty()) {
range.resize(size_t(points.rows()), 0);
std::iota(range.begin(), range.end(), 0);
}
-
- PointSet ret(range.size(), 3);
-
+
+ PointSet ret(range.size(), 3);
+
// for (size_t ridx = 0; ridx < range.size(); ++ridx)
- tbb::parallel_for(size_t(0), range.size(),
- [&ret, &range, &mesh, &points, thr, eps](size_t ridx)
- {
- thr();
- auto eidx = Eigen::Index(range[ridx]);
- int faceid = 0;
- Vec3d p;
-
- mesh.squared_distance(points.row(eidx), faceid, p);
-
- auto trindex = mesh.F().row(faceid);
-
- const Vec3d& p1 = mesh.V().row(trindex(0));
- const Vec3d& p2 = mesh.V().row(trindex(1));
- const Vec3d& p3 = mesh.V().row(trindex(2));
-
- // We should check if the point lies on an edge of the hosting triangle.
- // If it does then all the other triangles using the same two points
- // have to be searched and the final normal should be some kind of
- // aggregation of the participating triangle normals. We should also
- // consider the cases where the support point lies right on a vertex
- // of its triangle. The procedure is the same, get the neighbor
- // triangles and calculate an average normal.
-
- // mark the vertex indices of the edge. ia and ib marks and edge ic
- // will mark a single vertex.
- int ia = -1, ib = -1, ic = -1;
-
- if(std::abs(distance(p, p1)) < eps) {
- ic = trindex(0);
- }
- else if(std::abs(distance(p, p2)) < eps) {
- ic = trindex(1);
- }
- else if(std::abs(distance(p, p3)) < eps) {
- ic = trindex(2);
- }
- else if(point_on_edge(p, p1, p2, eps)) {
- ia = trindex(0); ib = trindex(1);
- }
- else if(point_on_edge(p, p2, p3, eps)) {
- ia = trindex(1); ib = trindex(2);
- }
- else if(point_on_edge(p, p1, p3, eps)) {
- ia = trindex(0); ib = trindex(2);
- }
-
- // vector for the neigboring triangles including the detected one.
- std::vector<Vec3i> neigh;
- if(ic >= 0) { // The point is right on a vertex of the triangle
- for(int n = 0; n < mesh.F().rows(); ++n) {
- thr();
- Vec3i ni = mesh.F().row(n);
- if((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
- neigh.emplace_back(ni);
- }
- }
- else if(ia >= 0 && ib >= 0) { // the point is on and edge
- // now get all the neigboring triangles
- for(int n = 0; n < mesh.F().rows(); ++n) {
- thr();
- Vec3i ni = mesh.F().row(n);
- if((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
- (ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
- neigh.emplace_back(ni);
- }
- }
-
- // Calculate the normals for the neighboring triangles
- std::vector<Vec3d> neighnorms; neighnorms.reserve(neigh.size());
- for(const Vec3i& tri : neigh) {
- const Vec3d& pt1 = mesh.V().row(tri(0));
- const Vec3d& pt2 = mesh.V().row(tri(1));
- const Vec3d& pt3 = mesh.V().row(tri(2));
- Eigen::Vector3d U = pt2 - pt1;
- Eigen::Vector3d V = pt3 - pt1;
- neighnorms.emplace_back(U.cross(V).normalized());
- }
-
- // Throw out duplicates. They would cause trouble with summing. We will
- // use std::unique which works on sorted ranges. We will sort by the
- // coefficient-wise sum of the normals. It should force the same
- // elements to be consecutive.
- std::sort(neighnorms.begin(), neighnorms.end(),
- [](const Vec3d& v1, const Vec3d& v2){
- return v1.sum() < v2.sum();
- });
-
- auto lend = std::unique(neighnorms.begin(), neighnorms.end(),
- [](const Vec3d& n1, const Vec3d& n2) {
- // Compare normals for equivalence. This is controvers stuff.
- auto deq = [](double a, double b) { return std::abs(a-b) < 1e-3; };
- return deq(n1(X), n2(X)) && deq(n1(Y), n2(Y)) && deq(n1(Z), n2(Z));
- });
-
- if(!neighnorms.empty()) { // there were neighbors to count with
- // sum up the normals and then normalize the result again.
- // This unification seems to be enough.
- Vec3d sumnorm(0, 0, 0);
- sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm);
- sumnorm.normalize();
- ret.row(long(ridx)) = sumnorm;
- }
- else { // point lies safely within its triangle
- Eigen::Vector3d U = p2 - p1;
- Eigen::Vector3d V = p3 - p1;
- ret.row(long(ridx)) = U.cross(V).normalized();
- }
+ ccr::enumerate(
+ range.begin(), range.end(),
+ [&ret, &mesh, &points, thr, eps](unsigned el, size_t ridx) {
+ thr();
+ auto eidx = Eigen::Index(el);
+ int faceid = 0;
+ Vec3d p;
+
+ mesh.squared_distance(points.row(eidx), faceid, p);
+
+ auto trindex = mesh.F().row(faceid);
+
+ const Vec3d &p1 = mesh.V().row(trindex(0));
+ const Vec3d &p2 = mesh.V().row(trindex(1));
+ const Vec3d &p3 = mesh.V().row(trindex(2));
+
+ // We should check if the point lies on an edge of the hosting
+ // triangle. If it does then all the other triangles using the
+ // same two points have to be searched and the final normal should
+ // be some kind of aggregation of the participating triangle
+ // normals. We should also consider the cases where the support
+ // point lies right on a vertex of its triangle. The procedure is
+ // the same, get the neighbor triangles and calculate an average
+ // normal.
+
+ // mark the vertex indices of the edge. ia and ib marks and edge
+ // ic will mark a single vertex.
+ int ia = -1, ib = -1, ic = -1;
+
+ if (std::abs(distance(p, p1)) < eps) {
+ ic = trindex(0);
+ } else if (std::abs(distance(p, p2)) < eps) {
+ ic = trindex(1);
+ } else if (std::abs(distance(p, p3)) < eps) {
+ ic = trindex(2);
+ } else if (point_on_edge(p, p1, p2, eps)) {
+ ia = trindex(0);
+ ib = trindex(1);
+ } else if (point_on_edge(p, p2, p3, eps)) {
+ ia = trindex(1);
+ ib = trindex(2);
+ } else if (point_on_edge(p, p1, p3, eps)) {
+ ia = trindex(0);
+ ib = trindex(2);
+ }
+
+ // vector for the neigboring triangles including the detected one.
+ std::vector<Vec3i> neigh;
+ if (ic >= 0) { // The point is right on a vertex of the triangle
+ for (int n = 0; n < mesh.F().rows(); ++n) {
+ thr();
+ Vec3i ni = mesh.F().row(n);
+ if ((ni(X) == ic || ni(Y) == ic || ni(Z) == ic))
+ neigh.emplace_back(ni);
+ }
+ } else if (ia >= 0 && ib >= 0) { // the point is on and edge
+ // now get all the neigboring triangles
+ for (int n = 0; n < mesh.F().rows(); ++n) {
+ thr();
+ Vec3i ni = mesh.F().row(n);
+ if ((ni(X) == ia || ni(Y) == ia || ni(Z) == ia) &&
+ (ni(X) == ib || ni(Y) == ib || ni(Z) == ib))
+ neigh.emplace_back(ni);
+ }
+ }
+
+ // Calculate the normals for the neighboring triangles
+ std::vector<Vec3d> neighnorms;
+ neighnorms.reserve(neigh.size());
+ for (const Vec3i &tri : neigh) {
+ const Vec3d & pt1 = mesh.V().row(tri(0));
+ const Vec3d & pt2 = mesh.V().row(tri(1));
+ const Vec3d & pt3 = mesh.V().row(tri(2));
+ Eigen::Vector3d U = pt2 - pt1;
+ Eigen::Vector3d V = pt3 - pt1;
+ neighnorms.emplace_back(U.cross(V).normalized());
+ }
+
+ // Throw out duplicates. They would cause trouble with summing. We
+ // will use std::unique which works on sorted ranges. We will sort
+ // by the coefficient-wise sum of the normals. It should force the
+ // same elements to be consecutive.
+ std::sort(neighnorms.begin(), neighnorms.end(),
+ [](const Vec3d &v1, const Vec3d &v2) {
+ return v1.sum() < v2.sum();
});
-
+
+ auto lend = std::unique(neighnorms.begin(), neighnorms.end(),
+ [](const Vec3d &n1, const Vec3d &n2) {
+ // Compare normals for equivalence.
+ // This is controvers stuff.
+ auto deq = [](double a, double b) {
+ return std::abs(a - b) < 1e-3;
+ };
+ return deq(n1(X), n2(X)) &&
+ deq(n1(Y), n2(Y)) &&
+ deq(n1(Z), n2(Z));
+ });
+
+ if (!neighnorms.empty()) { // there were neighbors to count with
+ // sum up the normals and then normalize the result again.
+ // This unification seems to be enough.
+ Vec3d sumnorm(0, 0, 0);
+ sumnorm = std::accumulate(neighnorms.begin(), lend, sumnorm);
+ sumnorm.normalize();
+ ret.row(long(ridx)) = sumnorm;
+ } else { // point lies safely within its triangle
+ Eigen::Vector3d U = p2 - p1;
+ Eigen::Vector3d V = p3 - p1;
+ ret.row(long(ridx)) = U.cross(V).normalized();
+ }
+ });
+
return ret;
}
diff --git a/src/libslic3r/SLA/Hollowing.cpp b/src/libslic3r/SLA/Hollowing.cpp
index b224bc98c..c3763dbc9 100644
--- a/src/libslic3r/SLA/Hollowing.cpp
+++ b/src/libslic3r/SLA/Hollowing.cpp
@@ -1,5 +1,7 @@
#include <functional>
+#include <libslic3r/OpenVDBUtils.hpp>
+#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/Hollowing.hpp>
#include <libslic3r/SLA/Contour3D.hpp>
@@ -98,18 +100,30 @@ remove_cvref_t<Mesh> _generate_interior(Mesh &&mesh,
return omesh;
}
-TriangleMesh generate_interior(const TriangleMesh &mesh, const HollowingConfig &hc, const JobController &ctl)
+std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh & mesh,
+ const HollowingConfig &hc,
+ const JobController & ctl)
{
static const double MAX_OVERSAMPL = 7.;
- // 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 scales
- // the whole geometry down, and doesn't increase the number of voxels.
+ // 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
+ // scales the whole geometry down, and doesn't increase the number of
+ // voxels.
//
// max 8x upscale, min is native voxel size
auto voxel_scale = (1.0 + MAX_OVERSAMPL * hc.quality);
- return _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale, hc.closing_distance);
+ return std::make_unique<TriangleMesh>(
+ _generate_interior(mesh, ctl, hc.min_thickness, voxel_scale,
+ hc.closing_distance));
+}
+
+bool DrainHole::operator==(const DrainHole &sp) const
+{
+ return (m_pos == sp.m_pos) && (m_normal == sp.m_normal) &&
+ is_approx(m_radius, sp.m_radius) &&
+ is_approx(m_height, sp.m_height);
}
}} // namespace Slic3r::sla
diff --git a/src/libslic3r/SLA/Hollowing.hpp b/src/libslic3r/SLA/Hollowing.hpp
index 93a1f90fd..cb5c2bd15 100644
--- a/src/libslic3r/SLA/Hollowing.hpp
+++ b/src/libslic3r/SLA/Hollowing.hpp
@@ -1,10 +1,14 @@
#ifndef SLA_HOLLOWING_HPP
#define SLA_HOLLOWING_HPP
-#include <libslic3r/OpenVDBUtils.hpp>
+#include <memory>
+#include <libslic3r/SLA/Common.hpp>
#include <libslic3r/SLA/JobController.hpp>
namespace Slic3r {
+
+class TriangleMesh;
+
namespace sla {
struct HollowingConfig
@@ -33,24 +37,19 @@ struct DrainHole
, m_height(height)
{}
- bool operator==(const DrainHole &sp) const
- {
- return (m_pos == sp.m_pos) && (m_normal == sp.m_normal) &&
- is_approx(m_radius, sp.m_radius) &&
- is_approx(m_height, sp.m_height);
- }
+ bool operator==(const DrainHole &sp) const;
bool operator!=(const DrainHole &sp) const { return !(sp == (*this)); }
- template<class Archive> void serialize(Archive &ar)
+ template<class Archive> inline void serialize(Archive &ar)
{
ar(m_pos, m_normal, m_radius, m_height);
}
};
-TriangleMesh generate_interior(const TriangleMesh &mesh,
- const HollowingConfig & = {},
- const JobController &ctl = {});
+std::unique_ptr<TriangleMesh> generate_interior(const TriangleMesh &mesh,
+ const HollowingConfig & = {},
+ const JobController &ctl = {});
}
}
diff --git a/src/libslic3r/SLA/RasterWriter.cpp b/src/libslic3r/SLA/RasterWriter.cpp
index b3da0d2a5..0d55b769d 100644
--- a/src/libslic3r/SLA/RasterWriter.cpp
+++ b/src/libslic3r/SLA/RasterWriter.cpp
@@ -1,4 +1,6 @@
#include <libslic3r/SLA/RasterWriter.hpp>
+
+#include "libslic3r/PrintConfig.hpp"
#include <libslic3r/Zipper.hpp>
#include <libslic3r/Time.hpp>
diff --git a/src/libslic3r/SLA/RasterWriter.hpp b/src/libslic3r/SLA/RasterWriter.hpp
index a7792d55d..62ed44ca8 100644
--- a/src/libslic3r/SLA/RasterWriter.hpp
+++ b/src/libslic3r/SLA/RasterWriter.hpp
@@ -9,11 +9,13 @@
#include <map>
#include <array>
-#include "libslic3r/PrintConfig.hpp"
-
#include <libslic3r/SLA/Raster.hpp>
-namespace Slic3r { namespace sla {
+namespace Slic3r {
+
+class DynamicPrintConfig;
+
+namespace sla {
// API to write the zipped sla output layers and metadata.
// Implementation uses PNG raster output.
diff --git a/src/libslic3r/SLAPrint.cpp b/src/libslic3r/SLAPrint.cpp
index 05440ad52..53a0e8223 100644
--- a/src/libslic3r/SLAPrint.cpp
+++ b/src/libslic3r/SLAPrint.cpp
@@ -1,8 +1,6 @@
#include "SLAPrint.hpp"
-#include "SLA/SupportTree.hpp"
-#include "SLA/Pad.hpp"
-#include "SLA/SupportPointGenerator.hpp"
-#include "SLA/Hollowing.hpp"
+#include "SLAPrintSteps.hpp"
+
#include "ClipperUtils.hpp"
#include "Geometry.hpp"
#include "MTUtils.hpp"
@@ -14,9 +12,6 @@
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
-// For geometry algorithms with native Clipper types (no copies and conversions)
-#include <libnest2d/backends/clipper/geometries.hpp>
-
// #define SLAPRINT_DO_BENCHMARK
#ifdef SLAPRINT_DO_BENCHMARK
@@ -33,78 +28,86 @@
namespace Slic3r {
-class SLAPrintObject::SupportData : public sla::SupportableMesh
+
+bool is_zero_elevation(const SLAPrintObjectConfig &c)
{
-public:
- sla::SupportTree::UPtr support_tree_ptr; // the supports
- std::vector<ExPolygons> support_slices; // sliced supports
-
- inline SupportData(const TriangleMesh &t): sla::SupportableMesh{t, {}, {}} {}
-
- sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
- {
- support_tree_ptr = sla::SupportTree::create(*this, ctl);
- return support_tree_ptr;
- }
-};
+ return c.pad_enable.getBool() && c.pad_around_object.getBool();
+}
-class SLAPrintObject::HollowingData
+// Compile the argument for support creation from the static print config.
+sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c)
{
-public:
+ sla::SupportConfig scfg;
- TriangleMesh interior;
- // std::vector<drillpoints>
-};
-
-namespace {
+ scfg.enabled = c.supports_enable.getBool();
+ scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
+ scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
+ scfg.head_penetration_mm = c.support_head_penetration.getFloat();
+ scfg.head_width_mm = c.support_head_width.getFloat();
+ scfg.object_elevation_mm = is_zero_elevation(c) ?
+ 0. : c.support_object_elevation.getFloat();
+ scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
+ scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
+ scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
+ switch(c.support_pillar_connection_mode.getInt()) {
+ case slapcmZigZag:
+ scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break;
+ case slapcmCross:
+ scfg.pillar_connection_mode = sla::PillarConnectionMode::cross; break;
+ case slapcmDynamic:
+ scfg.pillar_connection_mode = sla::PillarConnectionMode::dynamic; break;
+ }
+ scfg.ground_facing_only = c.support_buildplate_only.getBool();
+ scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
+ scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
+ scfg.base_height_mm = c.support_base_height.getFloat();
+ scfg.pillar_base_safety_distance_mm =
+ c.support_base_safety_distance.getFloat() < EPSILON ?
+ scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
+
+ return scfg;
+}
-// should add up to 100 (%)
-const std::array<unsigned, slaposCount> OBJ_STEP_LEVELS =
-{
- 5, // slaposHollowing,
- 20, // slaposObjectSlice,
- 5, // slaposDrillHolesIfHollowed
- 20, // slaposSupportPoints,
- 10, // slaposSupportTree,
- 10, // slaposPad,
- 30, // slaposSliceSupports,
-};
-
-// Object step to status label. The labels are localized at the time of calling, thus supporting language switching.
-std::string OBJ_STEP_LABELS(size_t idx)
+sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c)
{
- switch (idx) {
- case slaposHollowing: return L("Hollowing out the model");
- case slaposObjectSlice: return L("Slicing model");
- case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed 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:;
+ sla::PadConfig::EmbedObject ret;
+
+ ret.enabled = is_zero_elevation(c);
+
+ if(ret.enabled) {
+ ret.everywhere = c.pad_around_object_everywhere.getBool();
+ ret.object_gap_mm = c.pad_object_gap.getFloat();
+ ret.stick_width_mm = c.pad_object_connector_width.getFloat();
+ ret.stick_stride_mm = c.pad_object_connector_stride.getFloat();
+ ret.stick_penetration_mm = c.pad_object_connector_penetration
+ .getFloat();
}
- assert(false);
- return "Out of bounds!";
-};
+
+ return ret;
+}
-// Should also add up to 100 (%)
-const std::array<unsigned, slapsCount> PRINT_STEP_LEVELS =
+sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c)
{
- 10, // slapsMergeSlicesAndEval
- 90, // slapsRasterize
-};
+ sla::PadConfig pcfg;
+
+ pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat();
+ pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0;
+
+ pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat();
+ pcfg.wall_height_mm = c.pad_wall_height.getFloat();
+ pcfg.brim_size_mm = c.pad_brim_size.getFloat();
+
+ // set builtin pad implicitly ON
+ pcfg.embed_object = builtin_pad_cfg(c);
+
+ return pcfg;
+}
-// Print step to status label. The labels are localized at the time of calling, thus supporting language switching.
-std::string PRINT_STEP_LABELS(size_t idx)
+bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg)
{
- switch (idx) {
- case slapsMergeSlicesAndEval: return L("Merging slices and calculating statistics");
- case slapsRasterize: return L("Rasterizing layers");
- default:;
- }
- assert(false); return "Out of bounds!";
-};
-
+ // An empty pad can only be created if embed_object mode is enabled
+ // and the pad is not forced everywhere
+ return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere);
}
void SLAPrint::clear()
@@ -590,87 +593,6 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const
return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config);
}
-namespace {
-
-bool is_zero_elevation(const SLAPrintObjectConfig &c) {
- return c.pad_enable.getBool() && c.pad_around_object.getBool();
-}
-
-// Compile the argument for support creation from the static print config.
-sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
- sla::SupportConfig scfg;
-
- scfg.enabled = c.supports_enable.getBool();
- scfg.head_front_radius_mm = 0.5*c.support_head_front_diameter.getFloat();
- scfg.head_back_radius_mm = 0.5*c.support_pillar_diameter.getFloat();
- scfg.head_penetration_mm = c.support_head_penetration.getFloat();
- scfg.head_width_mm = c.support_head_width.getFloat();
- scfg.object_elevation_mm = is_zero_elevation(c) ?
- 0. : c.support_object_elevation.getFloat();
- scfg.bridge_slope = c.support_critical_angle.getFloat() * PI / 180.0 ;
- scfg.max_bridge_length_mm = c.support_max_bridge_length.getFloat();
- scfg.max_pillar_link_distance_mm = c.support_max_pillar_link_distance.getFloat();
- switch(c.support_pillar_connection_mode.getInt()) {
- case slapcmZigZag:
- scfg.pillar_connection_mode = sla::PillarConnectionMode::zigzag; break;
- case slapcmCross:
- scfg.pillar_connection_mode = sla::PillarConnectionMode::cross; break;
- case slapcmDynamic:
- scfg.pillar_connection_mode = sla::PillarConnectionMode::dynamic; break;
- }
- scfg.ground_facing_only = c.support_buildplate_only.getBool();
- scfg.pillar_widening_factor = c.support_pillar_widening_factor.getFloat();
- scfg.base_radius_mm = 0.5*c.support_base_diameter.getFloat();
- scfg.base_height_mm = c.support_base_height.getFloat();
- scfg.pillar_base_safety_distance_mm =
- c.support_base_safety_distance.getFloat() < EPSILON ?
- scfg.safety_distance_mm : c.support_base_safety_distance.getFloat();
-
- return scfg;
-}
-
-sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c) {
- sla::PadConfig::EmbedObject ret;
-
- ret.enabled = is_zero_elevation(c);
-
- if(ret.enabled) {
- ret.everywhere = c.pad_around_object_everywhere.getBool();
- ret.object_gap_mm = c.pad_object_gap.getFloat();
- ret.stick_width_mm = c.pad_object_connector_width.getFloat();
- ret.stick_stride_mm = c.pad_object_connector_stride.getFloat();
- ret.stick_penetration_mm = c.pad_object_connector_penetration
- .getFloat();
- }
-
- return ret;
-}
-
-sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c) {
- sla::PadConfig pcfg;
-
- pcfg.wall_thickness_mm = c.pad_wall_thickness.getFloat();
- pcfg.wall_slope = c.pad_wall_slope.getFloat() * PI / 180.0;
-
- pcfg.max_merge_dist_mm = c.pad_max_merge_distance.getFloat();
- pcfg.wall_height_mm = c.pad_wall_height.getFloat();
- pcfg.brim_size_mm = c.pad_brim_size.getFloat();
-
- // set builtin pad implicitly ON
- pcfg.embed_object = builtin_pad_cfg(c);
-
- return pcfg;
-}
-
-bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg)
-{
- // An empty pad can only be created if embed_object mode is enabled
- // and the pad is not forced everywhere
- return !pad.empty() || (pcfg.embed_object.enabled && !pcfg.embed_object.everywhere);
-}
-
-}
-
std::string SLAPrint::validate() const
{
for(SLAPrintObject * po : m_objects) {
@@ -740,775 +662,12 @@ bool SLAPrint::invalidate_step(SLAPrintStep step)
void SLAPrint::process()
{
- using namespace sla;
- using ExPolygon = Slic3r::ExPolygon;
-
if(m_objects.empty()) return;
// Assumption: at this point the print objects should be populated only with
// the model objects we have to process and the instances are also filtered
-
- // shortcut to initial layer height
- double ilhd = m_material_config.initial_layer_height.getFloat();
- auto ilh = float(ilhd);
-
- coord_t ilhs = scaled(ilhd);
- const size_t objcount = m_objects.size();
-
- static const unsigned min_objstatus = 0; // where the per object operations start
- static const unsigned max_objstatus = 50; // where the per object operations end
-
- // the coefficient that multiplies the per object status values which
- // are set up for <0, 100>. They need to be scaled into the whole process
- const double ostepd = (max_objstatus - min_objstatus) / (objcount * 100.0);
- auto hollow_model = [](SLAPrintObject &po) {
-
- if (!po.m_config.hollowing_enable.getBool()) {
- BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
- po.m_hollowing_data.reset();
- return;
- } else {
- BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
- }
-
- if (!po.m_hollowing_data)
- po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
-
- 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();
- po.m_hollowing_data->interior =
- generate_interior(po.transformed_mesh(), {thickness, quality, closing_d});
-
- if (po.m_hollowing_data->interior.empty())
- BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
- };
-
- // 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).
-
- // Slicing the model object. This method is oversimplified and needs to
- // be compared with the fff slicing algorithm for verification
- auto slice_model = [this, ilhs, ilh](SLAPrintObject& po) {
-
- TriangleMesh hollowed_mesh;
-
- bool is_hollowing = po.m_config.hollowing_enable.getBool() &&
- po.m_hollowing_data;
-
- if (is_hollowing) {
- hollowed_mesh = po.transformed_mesh();
- hollowed_mesh.merge(po.m_hollowing_data->interior);
- hollowed_mesh.require_shared_vertices();
- }
-
- const TriangleMesh &mesh = is_hollowing ? hollowed_mesh :
- po.transformed_mesh();
-
- // We need to prepare the slice index...
-
- double lhd = 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();
- slicer.slice(po.m_model_height_levels,
- float(po.config().slice_closing_radius.value),
- &po.m_model_slices,
- [this](){ throw_if_canceled(); });
-
- auto mit = slindex_it;
- double doffs = 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(po.transformed_mesh()) );
- }
- };
-
- // In this step we check the slices, identify island and cover them with
- // support points. Then we sprinkle the rest of the mesh.
- auto support_points = [this, ostepd](SLAPrintObject& po) {
- // If supports are disabled, we can skip the model scan.
- if(!po.m_config.supports_enable.getBool()) return;
-
- if (!po.m_supportdata)
- po.m_supportdata.reset(
- new SLAPrintObject::SupportData(po.transformed_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) {
-
- // Hypothetical use of the slice index:
- // auto bb = po.transformed_mesh().bounding_box();
- // auto range = po.get_slice_records(bb.min(Z));
- // std::vector<float> heights; heights.reserve(range.size());
- // for(auto& record : range) heights.emplace_back(record.slice_level());
-
- // calculate heights of slices (slices are calculated already)
- const std::vector<float>& heights = po.m_model_height_levels;
-
- this->throw_if_canceled();
- 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 = ostepd * OBJ_STEP_LEVELS[slaposSupportPoints] / 100.0;
- double init = m_report_status.status();
-
- auto statuscb = [this, d, init](unsigned st)
- {
- double current = init + st * d;
- if(std::round(m_report_status.status()) < std::round(current))
- m_report_status(*this, current,
- OBJ_STEP_LABELS(slaposSupportPoints));
-
- };
-
- // Construction of this object does the calculation.
- this->throw_if_canceled();
- 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();
- this->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
- m_report_status(*this,
- -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);
- }
- };
-
- // In this step we create the supports
- auto support_tree = [this, ostepd](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);
-
- // scaling for the sub operations
- double d = ostepd * OBJ_STEP_LEVELS[slaposSupportTree] / 100.0;
- double init = m_report_status.status();
- JobController ctl;
-
- ctl.statuscb = [this, d, init](unsigned st, const std::string &logmsg) {
- double current = init + st * d;
- if (std::round(m_report_status.status()) < std::round(current))
- m_report_status(*this, 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()
- m_report_status(*this, -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";
-
- m_report_status(*this, -1, L("Visualizing supports"), rc);
- };
-
- // This step generates the sla base pad
- auto generate_pad = [this](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();
-
- // This call can get pretty time consuming
- auto thrfn = [this](){ throw_if_canceled(); };
-
- 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()),
- thrfn);
- }
-
- po.m_supportdata->support_tree_ptr->add_pad(bp, pcfg);
- auto &pad_mesh = po.m_supportdata->support_tree_ptr->retrieve_mesh(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();
- }
-
- po.throw_if_canceled();
- auto rc = SlicingStatus::RELOAD_SCENE;
- m_report_status(*this, -1, L("Visualizing supports"), rc);
- };
-
- // 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)
- auto slice_supports = [this](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) {
-
- std::vector<float> heights; heights.reserve(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_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.
- m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
- };
-
- // Merging the slices from all the print objects into one slice grid and
- // calculating print statistics from the merge result.
- auto merge_slices_and_eval_stats = [this, ilhs]() {
-
- // clear the rasterizer input
- m_printer_input.clear();
-
- size_t mx = 0;
- for(SLAPrintObject * o : m_objects) {
- if(auto m = o->get_slice_index().size() > mx) mx = m;
- }
-
- m_printer_input.reserve(mx);
-
- auto eps = coord_t(SCALED_EPSILON);
-
- for(SLAPrintObject * o : m_objects) {
- coord_t gndlvl = o->get_slice_index().front().print_level() - ilhs;
-
- for(const SliceRecord& slicerecord : o->get_slice_index()) {
- 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(m_printer_input.begin(),
- m_printer_input.end(),
- PrintLayer(lvlid));
-
- if(it == m_printer_input.end() || it->level() != lvlid)
- it = m_printer_input.insert(it, PrintLayer(lvlid));
-
-
- it->add(slicerecord);
- }
- }
-
- m_print_statistics.clear();
-
- using ClipperPoint = ClipperLib::IntPoint;
- using ClipperPolygon = ClipperLib::Polygon; // see clipper_polygon.hpp in libnest2d
- using ClipperPolygons = std::vector<ClipperPolygon>;
- namespace sl = libnest2d::shapelike; // For algorithms
-
- // Set up custom union and diff functions for clipper polygons
- auto 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);
- };
-
- auto 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);
- };
-
- // libnest calculates positive area for clockwise polygons, Slic3r is in counter-clockwise
- auto areafn = [](const ClipperPolygon& poly) { return - sl::area(poly); };
-
- const double area_fill = m_printer_config.area_fill.getFloat()*0.01;// 0.5 (50%);
- const double fast_tilt = m_printer_config.fast_tilt_time.getFloat();// 5.0;
- const double slow_tilt = m_printer_config.slow_tilt_time.getFloat();// 8.0;
-
- const double init_exp_time = m_material_config.initial_exposure_time.getFloat();
- const double exp_time = m_material_config.exposure_time.getFloat();
-
- const int fade_layers_cnt = m_default_object_config.faded_layers.getInt();// 10 // [3;20]
-
- const auto width = scaled<double>(m_printer_config.display_width.getFloat());
- const auto height = scaled<double>(m_printer_config.display_height.getFloat());
- const double display_area = width*height;
-
- // get polygons for all instances in the object
- auto get_all_polygons =
- [](const ExPolygons& input_polygons,
- const std::vector<SLAPrintObject::Instance>& instances,
- bool is_lefthanded)
- {
- ClipperPolygons polygons;
- 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;
- };
-
- 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;
-
- SpinMutex mutex;
- using Lock = std::lock_guard<SpinMutex>;
-
- // Going to parallel:
- auto printlayerfn = [this,
- // functions and read only vars
- get_all_polygons, polyunion, polydiff, 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](size_t sliced_layer_cnt)
- {
- PrintLayer& layer = m_printer_input[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()) {
- const SLAPrintObject *po = record.print_obj();
-
- const ExPolygons &modelslices = record.get_slice(soModel);
-
- bool is_lefth = record.print_obj()->is_left_handed();
- if (!modelslices.empty()) {
- ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
- for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
- }
-
- const ExPolygons &supportslices = record.get_slice(soSupport);
-
- if (!supportslices.empty()) {
- ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
- for(ClipperPolygon& p_tmp : v) 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);
- tbb::parallel_for<size_t, decltype(printlayerfn)>(0, m_printer_input.size(), printlayerfn);
-
- auto SCALING2 = SCALING_FACTOR * SCALING_FACTOR;
- m_print_statistics.support_used_material = supports_volume * SCALING2;
- m_print_statistics.objects_used_material = models_volume * SCALING2;
-
- // Estimated printing time
- // A layers count o the highest object
- if (m_printer_input.size() == 0)
- m_print_statistics.estimated_print_time = std::nan("");
- else
- m_print_statistics.estimated_print_time = estim_time;
-
- m_print_statistics.fast_layers_count = fast_layers;
- m_print_statistics.slow_layers_count = slow_layers;
-
- m_report_status(*this, -2, "", SlicingStatus::RELOAD_SLA_PREVIEW);
- };
-
- // Rasterizing the model objects, and their supports
- auto rasterize = [this]() {
- if(canceled()) return;
-
- // Set up the printer, allocate space for all the layers
- sla::RasterWriter &printer = init_printer();
-
- auto lvlcnt = unsigned(m_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 = m_report_status.status();
-
- double increment = (slot * sd) / m_printer_input.size();
- double dstatus = m_report_status.status();
-
- SpinMutex slck;
-
- // procedure to process one height level. This will run in parallel
- auto lvlfn =
- [this, &slck, &printer, increment, &dstatus, &pst]
- (unsigned level_id)
- {
- if(canceled()) return;
-
- PrintLayer& printlayer = m_printer_input[level_id];
-
- // 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
- {
- std::lock_guard<SpinMutex> lck(slck);
- dstatus += increment;
- double st = std::round(dstatus);
- if(st > pst) {
- m_report_status(*this, 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
- tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
-
- // Set statistics values to the printer
- sla::RasterWriter::PrintStatistics stats;
- stats.used_material = (m_print_statistics.objects_used_material +
- m_print_statistics.support_used_material) /
- 1000;
-
- int num_fade = m_default_object_config.faded_layers.getInt();
- stats.num_fade = num_fade >= 0 ? size_t(num_fade) : size_t(0);
- stats.num_fast = m_print_statistics.fast_layers_count;
- stats.num_slow = m_print_statistics.slow_layers_count;
- stats.estimated_print_time_s = m_print_statistics.estimated_print_time;
-
- m_printer->set_statistics(stats);
- };
-
- using slaposFn = std::function<void(SLAPrintObject&)>;
- using slapsFn = std::function<void(void)>;
-
- slaposFn pobj_program[] =
- {
- hollow_model, slice_model, [](SLAPrintObject&){}, support_points, support_tree, generate_pad, slice_supports
- };
+ Steps printsteps{this};
// We want to first process all objects...
std::vector<SLAPrintObjectStep> level1_obj_steps = {
@@ -1520,10 +679,9 @@ void SLAPrint::process()
slaposSliceSupports
};
- slapsFn print_program[] = { merge_slices_and_eval_stats, rasterize };
SLAPrintStep print_steps[] = { slapsMergeSlicesAndEval, slapsRasterize };
-
- double st = min_objstatus;
+
+ double st = Steps::min_objstatus;
BOOST_LOG_TRIVIAL(info) << "Start slicing process.";
@@ -1538,10 +696,10 @@ void SLAPrint::process()
std::array<double, slaposCount + slapsCount> step_times {};
auto apply_steps_on_objects =
- [this, &st, ostepd, &pobj_program, &step_times, &bench]
+ [this, &st, &printsteps, &step_times, &bench]
(const std::vector<SLAPrintObjectStep> &steps)
{
- unsigned incr = 0;
+ double incr = 0;
for (SLAPrintObject *po : m_objects) {
for (SLAPrintObjectStep step : steps) {
@@ -1551,19 +709,19 @@ void SLAPrint::process()
// throws the canceled signal.
throw_if_canceled();
- st += incr * ostepd;
+ st += incr;
if (po->m_stepmask[step] && po->set_started(step)) {
- m_report_status(*this, st, OBJ_STEP_LABELS(step));
+ m_report_status(*this, st, printsteps.label(step));
bench.start();
- pobj_program[step](*po);
+ printsteps.execute(step, *po);
bench.stop();
step_times[step] += bench.getElapsedSec();
throw_if_canceled();
po->set_done(step);
}
- incr = OBJ_STEP_LEVELS[step];
+ incr = printsteps.progressrange(step);
}
}
};
@@ -1573,23 +731,22 @@ void SLAPrint::process()
// this would disable the rasterization step
// std::fill(m_stepmask.begin(), m_stepmask.end(), false);
-
- double pstd = (100 - max_objstatus) / 100.0;
- st = max_objstatus;
+
+ st = Steps::max_objstatus;
for(SLAPrintStep currentstep : print_steps) {
throw_if_canceled();
if (m_stepmask[currentstep] && set_started(currentstep)) {
- m_report_status(*this, st, PRINT_STEP_LABELS(currentstep));
+ m_report_status(*this, st, printsteps.label(currentstep));
bench.start();
- print_program[currentstep]();
+ printsteps.execute(currentstep);
bench.stop();
step_times[slaposCount + currentstep] += bench.getElapsedSec();
throw_if_canceled();
set_done(currentstep);
}
- st += PRINT_STEP_LEVELS[currentstep] * pstd;
+ st += printsteps.progressrange(currentstep);
}
// If everything vent well
@@ -1598,10 +755,10 @@ void SLAPrint::process()
#ifdef SLAPRINT_DO_BENCHMARK
std::string csvbenchstr;
for (size_t i = 0; i < size_t(slaposCount); ++i)
- csvbenchstr += OBJ_STEP_LABELS(i) + ";";
+ csvbenchstr += printsteps.label(SLAPrintObjectStep(i)) + ";";
for (size_t i = 0; i < size_t(slapsCount); ++i)
- csvbenchstr += PRINT_STEP_LABELS(i) + ";";
+ csvbenchstr += printsteps.label(SLAPrintStep(i)) + ";";
csvbenchstr += "\n";
for (double t : step_times) csvbenchstr += std::to_string(t) + ";";
diff --git a/src/libslic3r/SLAPrint.hpp b/src/libslic3r/SLAPrint.hpp
index 6c8a6e26b..2edede109 100644
--- a/src/libslic3r/SLAPrint.hpp
+++ b/src/libslic3r/SLAPrint.hpp
@@ -3,8 +3,8 @@
#include <mutex>
#include "PrintBase.hpp"
-//#include "PrintExport.hpp"
#include "SLA/RasterWriter.hpp"
+#include "SLA/SupportTree.hpp"
#include "Point.hpp"
#include "MTUtils.hpp"
#include <libnest2d/backends/clipper/clipper_polygon.hpp>
@@ -292,10 +292,33 @@ private:
// Caching the transformed (m_trafo) raw mesh of the object
mutable CachedObject<TriangleMesh> m_transformed_rmesh;
- class SupportData;
+ class SupportData : public sla::SupportableMesh
+ {
+ public:
+ sla::SupportTree::UPtr support_tree_ptr; // the supports
+ std::vector<ExPolygons> support_slices; // sliced supports
+
+ inline SupportData(const TriangleMesh &t)
+ : sla::SupportableMesh{t, {}, {}}
+ {}
+
+ sla::SupportTree::UPtr &create_support_tree(const sla::JobController &ctl)
+ {
+ support_tree_ptr = sla::SupportTree::create(*this, ctl);
+ return support_tree_ptr;
+ }
+ };
+
std::unique_ptr<SupportData> m_supportdata;
- class HollowingData;
+ class HollowingData
+ {
+ public:
+
+ TriangleMesh interior;
+ // std::vector<drillpoints>
+ };
+
std::unique_ptr<HollowingData> m_hollowing_data;
};
@@ -346,7 +369,9 @@ class SLAPrint : public PrintBaseWithState<SLAPrintStep, slapsCount>
{
private: // Prevents erroneous use by other classes.
typedef PrintBaseWithState<SLAPrintStep, slapsCount> Inherited;
-
+
+ class Steps; // See SLAPrintSteps.cpp
+
public:
SLAPrint(): m_stepmask(slapsCount, true) {}
@@ -402,8 +427,8 @@ public:
template<class Container> void transformed_slices(Container&& c) {
m_transformed_slices = std::forward<Container>(c);
}
-
- friend void SLAPrint::process();
+
+ friend class SLAPrint::Steps;
public:
@@ -479,6 +504,19 @@ private:
friend SLAPrintObject;
};
+// Helper functions:
+
+bool is_zero_elevation(const SLAPrintObjectConfig &c);
+
+sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c);
+
+sla::PadConfig::EmbedObject builtin_pad_cfg(const SLAPrintObjectConfig& c);
+
+sla::PadConfig make_pad_cfg(const SLAPrintObjectConfig& c);
+
+bool validate_pad(const TriangleMesh &pad, const sla::PadConfig &pcfg);
+
+
} // namespace Slic3r
#endif /* slic3r_SLAPrint_hpp_ */
diff --git a/src/libslic3r/SLAPrintSteps.cpp b/src/libslic3r/SLAPrintSteps.cpp
new file mode 100644
index 000000000..09d449576
--- /dev/null
+++ b/src/libslic3r/SLAPrintSteps.cpp
@@ -0,0 +1,848 @@
+#include <libslic3r/SLAPrintSteps.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 = {
+ 5, // slaposHollowing,
+ 20, // slaposObjectSlice,
+ 5, // slaposDrillHolesIfHollowed
+ 20, // slaposSupportPoints,
+ 10, // slaposSupportTree,
+ 10, // slaposPad,
+ 30, // slaposSliceSupports,
+};
+
+std::string OBJ_STEP_LABELS(size_t idx)
+{
+ switch (idx) {
+ case slaposHollowing: return L("Hollowing out the model");
+ case slaposObjectSlice: return L("Slicing model");
+ case slaposDrillHolesIfHollowed: return L("Drilling holes into hollowed 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}
+ , 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)
+{
+
+ if (!po.m_config.hollowing_enable.getBool()) {
+ BOOST_LOG_TRIVIAL(info) << "Skipping hollowing step!";
+ po.m_hollowing_data.reset();
+ return;
+ } else {
+ BOOST_LOG_TRIVIAL(info) << "Performing hollowing step!";
+ }
+
+ if (!po.m_hollowing_data)
+ po.m_hollowing_data.reset(new SLAPrintObject::HollowingData());
+
+ 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) po.m_hollowing_data->interior = *meshptr;
+
+ if (po.m_hollowing_data->interior.empty())
+ BOOST_LOG_TRIVIAL(warning) << "Hollowed interior is empty!";
+}
+
+// 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)
+{
+
+ TriangleMesh hollowed_mesh;
+
+ bool is_hollowing = po.m_config.hollowing_enable.getBool() &&
+ po.m_hollowing_data;
+
+ if (is_hollowing) {
+ hollowed_mesh = po.transformed_mesh();
+ hollowed_mesh.merge(po.m_hollowing_data->interior);
+ hollowed_mesh.require_shared_vertices();
+ }
+
+ const TriangleMesh &mesh = is_hollowing ? hollowed_mesh :
+ po.transformed_mesh();
+
+ // 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();
+ slicer.slice(po.m_model_height_levels,
+ float(po.config().slice_closing_radius.value),
+ &po.m_model_slices,
+ [this](){ m_print->throw_if_canceled(); });
+
+ 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(po.transformed_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;
+
+ if (!po.m_supportdata)
+ po.m_supportdata.reset(
+ new SLAPrintObject::SupportData(po.transformed_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;
+
+ 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);
+
+ // 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) {
+
+ std::vector<float> heights; heights.reserve(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 ExPolygons & input_polygons,
+ const std::vector<SLAPrintObject::Instance> &instances,
+ bool is_lefthanded)
+{
+ namespace sl = libnest2d::sl;
+
+ ClipperPolygons polygons;
+ 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()) {
+ 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()) {
+ const SLAPrintObject *po = record.print_obj();
+
+ const ExPolygons &modelslices = record.get_slice(soModel);
+
+ bool is_lefth = record.print_obj()->is_left_handed();
+ if (!modelslices.empty()) {
+ ClipperPolygons v = get_all_polygons(modelslices, po->instances(), is_lefth);
+ for(ClipperPolygon& p_tmp : v) model_polygons.emplace_back(std::move(p_tmp));
+ }
+
+ const ExPolygons &supportslices = record.get_slice(soSupport);
+
+ if (!supportslices.empty()) {
+ ClipperPolygons v = get_all_polygons(supportslices, po->instances(), is_lefth);
+ for(ClipperPolygon& p_tmp : v) 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 slaposObjectSlice: slice_model(obj); break;
+ case slaposDrillHolesIfHollowed: 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);
+ }
+}
+
+}
diff --git a/src/libslic3r/SLAPrintSteps.hpp b/src/libslic3r/SLAPrintSteps.hpp
new file mode 100644
index 000000000..c62558671
--- /dev/null
+++ b/src/libslic3r/SLAPrintSteps.hpp
@@ -0,0 +1,68 @@
+#ifndef SLAPRINTSTEPS_HPP
+#define SLAPRINTSTEPS_HPP
+
+#include <libslic3r/SLAPrint.hpp>
+
+#include <libslic3r/SLA/Hollowing.hpp>
+#include <libslic3r/SLA/SupportTree.hpp>
+
+namespace Slic3r {
+
+class SLAPrint::Steps
+{
+private:
+ SLAPrint *m_print = nullptr;
+
+public:
+ // where the per object operations start and end
+ static const constexpr unsigned min_objstatus = 0;
+ static const constexpr unsigned max_objstatus = 50;
+
+private:
+ const size_t objcount;
+
+ // shortcut to initial layer height
+ const double ilhd;
+ const float ilh;
+ const coord_t ilhs;
+
+ // the coefficient that multiplies the per object status values which
+ // are set up for <0, 100>. They need to be scaled into the whole process
+ const double objectstep_scale;
+
+ template<class...Args> void report_status(Args&&...args)
+ {
+ m_print->m_report_status(*m_print, std::forward<Args>(args)...);
+ }
+
+ double current_status() const { return m_print->m_report_status.status(); }
+ void throw_if_canceled() const { m_print->throw_if_canceled(); }
+ bool canceled() const { return m_print->canceled(); }
+ void initialize_printer_input();
+
+public:
+ Steps(SLAPrint *print);
+
+ void hollow_model(SLAPrintObject &po);
+ void slice_model(SLAPrintObject& po);
+ void support_points(SLAPrintObject& po);
+ void support_tree(SLAPrintObject& po);
+ void generate_pad(SLAPrintObject& po);
+ void slice_supports(SLAPrintObject& po);
+
+ void merge_slices_and_eval_stats();
+ void rasterize();
+
+ void execute(SLAPrintObjectStep step, SLAPrintObject &obj);
+ void execute(SLAPrintStep step);
+
+ static std::string label(SLAPrintObjectStep step);
+ static std::string label(SLAPrintStep step);
+
+ double progressrange(SLAPrintObjectStep step) const;
+ double progressrange(SLAPrintStep step) const;
+};
+
+}
+
+#endif // SLAPRINTSTEPS_HPP
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 1d4926d92..615d971b4 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -2882,11 +2882,12 @@ void Plater::priv::HollowJob::process()
if (st < 100) update_status(int(st), s);
};
- TriangleMesh omesh = sla::generate_interior(*m_object_mesh, m_cfg, ctl);
+ std::unique_ptr<TriangleMesh> omesh =
+ sla::generate_interior(*m_object_mesh, m_cfg, ctl);
- if (!omesh.empty()) {
+ if (omesh && !omesh->empty()) {
m_output_mesh.reset(new TriangleMesh{*m_object_mesh});
- m_output_mesh->merge(omesh);
+ m_output_mesh->merge(*omesh);
m_output_mesh->require_shared_vertices();
update_status(90, _(L("Indexing hollowed object")));
diff --git a/tests/libslic3r/test_hollowing.cpp b/tests/libslic3r/test_hollowing.cpp
index 5e7f1a568..0cb1ac343 100644
--- a/tests/libslic3r/test_hollowing.cpp
+++ b/tests/libslic3r/test_hollowing.cpp
@@ -2,6 +2,7 @@
#include <fstream>
#include <catch2/catch.hpp>
+#include <libslic3r/TriangleMesh.hpp>
#include "libslic3r/SLA/Hollowing.hpp"
#include <openvdb/tools/Filter.h>
#include "libslic3r/Format/OBJ.hpp"
@@ -28,13 +29,14 @@ TEST_CASE("Negative 3D offset should produce smaller object.", "[Hollowing]")
Benchmark bench;
bench.start();
- Slic3r::TriangleMesh out_mesh = Slic3r::sla::generate_interior(in_mesh);
+ std::unique_ptr<Slic3r::TriangleMesh> out_mesh_ptr =
+ Slic3r::sla::generate_interior(in_mesh);
bench.stop();
std::cout << "Elapsed processing time: " << bench.getElapsedSec() << std::endl;
- in_mesh.merge(out_mesh);
+ if (out_mesh_ptr) in_mesh.merge(*out_mesh_ptr);
in_mesh.require_shared_vertices();
in_mesh.WriteOBJFile("merged_out.obj");
}