Merge branch 'master' into tm_colldetection_upgr

This commit is contained in:
tamasmeszaros 2019-02-25 13:24:43 +01:00
commit 03079381e1
119 changed files with 11942 additions and 8619 deletions

View file

@ -69,7 +69,9 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
if (surface.is_solid() && (!surface.is_bridge() || layerm.layer()->id() == 0)) {
group_attrib[i].is_solid = true;
group_attrib[i].flow_width = (surface.surface_type == stTop) ? top_solid_infill_flow.width : solid_infill_flow.width;
group_attrib[i].pattern = surface.is_external() ? layerm.region()->config().external_fill_pattern.value : ipRectilinear;
group_attrib[i].pattern = surface.is_external() ?
(surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) :
ipRectilinear;
}
}
// Loop through solid groups, find compatible groups and append them to this one.
@ -161,7 +163,7 @@ void make_fill(LayerRegion &layerm, ExtrusionEntityCollection &out)
if (surface.is_solid()) {
density = 100.;
fill_pattern = (surface.is_external() && ! is_bridge) ?
layerm.region()->config().external_fill_pattern.value :
(surface.is_top() ? layerm.region()->config().top_fill_pattern.value : layerm.region()->config().bottom_fill_pattern.value) :
ipRectilinear;
} else if (density <= 0)
continue;

View file

@ -323,7 +323,7 @@ namespace Slic3r {
typedef std::map<int, ObjectMetadata> IdToMetadataMap;
typedef std::map<int, Geometry> IdToGeometryMap;
typedef std::map<int, std::vector<coordf_t>> IdToLayerHeightsProfileMap;
typedef std::map<int, std::vector<Vec3f>> IdToSlaSupportPointsMap;
typedef std::map<int, std::vector<sla::SupportPoint>> IdToSlaSupportPointsMap;
// Version of the 3mf file
unsigned int m_version;
@ -776,10 +776,19 @@ namespace Slic3r {
std::vector<std::string> objects;
boost::split(objects, buffer, boost::is_any_of("\n"), boost::token_compress_off);
// Info on format versioning - see 3mf.hpp
int version = 0;
if (!objects.empty() && objects[0].find("support_points_format_version=") != std::string::npos) {
objects[0].erase(objects[0].begin(), objects[0].begin() + 30); // removes the string
version = std::stoi(objects[0]);
objects.erase(objects.begin()); // pop the header
}
for (const std::string& object : objects)
{
std::vector<std::string> object_data;
boost::split(object_data, object, boost::is_any_of("|"), boost::token_compress_off);
if (object_data.size() != 2)
{
add_error("Error while reading object data");
@ -811,10 +820,24 @@ namespace Slic3r {
std::vector<std::string> object_data_points;
boost::split(object_data_points, object_data[1], boost::is_any_of(" "), boost::token_compress_off);
std::vector<Vec3f> sla_support_points;
std::vector<sla::SupportPoint> sla_support_points;
for (unsigned int i=0; i<object_data_points.size(); i+=3)
sla_support_points.push_back(Vec3d(std::atof(object_data_points[i+0].c_str()), std::atof(object_data_points[i+1].c_str()), std::atof(object_data_points[i+2].c_str())).cast<float>());
if (version == 0) {
for (unsigned int i=0; i<object_data_points.size(); i+=3)
sla_support_points.emplace_back(std::atof(object_data_points[i+0].c_str()),
std::atof(object_data_points[i+1].c_str()),
std::atof(object_data_points[i+2].c_str()),
0.4f,
false);
}
if (version == 1) {
for (unsigned int i=0; i<object_data_points.size(); i+=5)
sla_support_points.emplace_back(std::atof(object_data_points[i+0].c_str()),
std::atof(object_data_points[i+1].c_str()),
std::atof(object_data_points[i+2].c_str()),
std::atof(object_data_points[i+3].c_str()),
std::atof(object_data_points[i+4].c_str()));
}
if (!sla_support_points.empty())
m_sla_support_points.insert(IdToSlaSupportPointsMap::value_type(object_id, sla_support_points));
@ -1483,7 +1506,7 @@ namespace Slic3r {
if (metadata.key == NAME_KEY)
volume->name = metadata.value;
else if ((metadata.key == MODIFIER_KEY) && (metadata.value == "1"))
volume->set_type(ModelVolume::PARAMETER_MODIFIER);
volume->set_type(ModelVolumeType::PARAMETER_MODIFIER);
else if (metadata.key == VOLUME_TYPE_KEY)
volume->set_type(ModelVolume::type_from_string(metadata.value));
else
@ -1961,7 +1984,7 @@ namespace Slic3r {
for (const ModelObject* object : model.objects)
{
++count;
const std::vector<Vec3f>& sla_support_points = object->sla_support_points;
const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
if (!sla_support_points.empty())
{
sprintf(buffer, "object_id=%d|", count);
@ -1970,7 +1993,7 @@ namespace Slic3r {
// Store the layer height profile as a single space separated list.
for (size_t i = 0; i < sla_support_points.size(); ++i)
{
sprintf(buffer, (i==0 ? "%f %f %f" : " %f %f %f"), sla_support_points[i](0), sla_support_points[i](1), sla_support_points[i](2));
sprintf(buffer, (i==0 ? "%f %f %f %f %f" : " %f %f %f %f %f"), sla_support_points[i].pos(0), sla_support_points[i].pos(1), sla_support_points[i].pos(2), sla_support_points[i].head_front_radius, (float)sla_support_points[i].is_new_island);
out += buffer;
}
out += "\n";
@ -1979,6 +2002,9 @@ namespace Slic3r {
if (!out.empty())
{
// Adds version header at the beginning:
out = std::string("support_points_format_version=") + std::to_string(support_points_format_version) + std::string("\n") + out;
if (!mz_zip_writer_add_mem(&archive, SLA_SUPPORT_POINTS_FILE.c_str(), (const void*)out.data(), out.length(), MZ_DEFAULT_COMPRESSION))
{
add_error("Unable to add sla support points file to archive");

View file

@ -3,6 +3,23 @@
namespace Slic3r {
/* The format for saving the SLA points was changing in the past. This enum holds the latest version that is being currently used.
* Examples of the Slic3r_PE_sla_support_points.txt for historically used versions:
* version 0 : object_id=1|-12.055421 -2.658771 10.000000
object_id=2|-14.051745 -3.570338 5.000000
// no header and x,y,z positions of the points)
* version 1 : ThreeMF_support_points_version=1
object_id=1|-12.055421 -2.658771 10.000000 0.4 0.0
object_id=2|-14.051745 -3.570338 5.000000 0.6 1.0
// introduced header with version number; x,y,z,head_size,is_new_island)
*/
enum {
support_points_format_version = 1
};
class Model;
class DynamicPrintConfig;

View file

@ -583,7 +583,7 @@ void AMFParserContext::endElement(const char * /* name */)
else if (m_path.size() == 3 && m_path[1] == NODE_TYPE_OBJECT && m_object && strcmp(opt_key, "sla_support_points") == 0) {
// Parse object's layer height profile, a semicolon separated list of floats.
unsigned char coord_idx = 0;
Vec3f point(Vec3f::Zero());
Eigen::Matrix<float, 5, 1, Eigen::DontAlign> point(Eigen::Matrix<float, 5, 1, Eigen::DontAlign>::Zero());
char *p = const_cast<char*>(m_value[1].c_str());
for (;;) {
char *end = strchr(p, ';');
@ -591,8 +591,8 @@ void AMFParserContext::endElement(const char * /* name */)
*end = 0;
point(coord_idx) = atof(p);
if (++coord_idx == 3) {
m_object->sla_support_points.push_back(point);
if (++coord_idx == 5) {
m_object->sla_support_points.push_back(sla::SupportPoint(point));
coord_idx = 0;
}
if (end == nullptr)
@ -604,7 +604,7 @@ void AMFParserContext::endElement(const char * /* name */)
if (strcmp(opt_key, "modifier") == 0) {
// Is this volume a modifier volume?
// "modifier" flag comes first in the XML file, so it may be later overwritten by the "type" flag.
m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolume::PARAMETER_MODIFIER : ModelVolume::MODEL_PART);
m_volume->set_type((atoi(m_value[1].c_str()) == 1) ? ModelVolumeType::PARAMETER_MODIFIER : ModelVolumeType::MODEL_PART);
} else if (strcmp(opt_key, "volume_type") == 0) {
m_volume->set_type(ModelVolume::type_from_string(m_value[1]));
}
@ -900,14 +900,14 @@ bool store_amf(const char *path, Model *model, const DynamicPrintConfig *config)
}
//FIXME Store the layer height ranges (ModelObject::layer_height_ranges)
const std::vector<Vec3f>& sla_support_points = object->sla_support_points;
const std::vector<sla::SupportPoint>& sla_support_points = object->sla_support_points;
if (!sla_support_points.empty()) {
// Store the SLA supports as a single semicolon separated list.
stream << " <metadata type=\"slic3r.sla_support_points\">";
for (size_t i = 0; i < sla_support_points.size(); ++i) {
if (i != 0)
stream << ";";
stream << sla_support_points[i](0) << ";" << sla_support_points[i](1) << ";" << sla_support_points[i](2);
stream << sla_support_points[i].pos(0) << ";" << sla_support_points[i].pos(1) << ";" << sla_support_points[i].pos(2) << ";" << sla_support_points[i].head_front_radius << ";" << sla_support_points[i].is_new_island;
}
stream << "\n </metadata>\n";
}

View file

@ -1480,32 +1480,32 @@ const TriangleMesh& ModelVolume::get_convex_hull() const
return m_convex_hull;
}
ModelVolume::Type ModelVolume::type_from_string(const std::string &s)
ModelVolumeType ModelVolume::type_from_string(const std::string &s)
{
// Legacy support
if (s == "1")
return PARAMETER_MODIFIER;
return ModelVolumeType::PARAMETER_MODIFIER;
// New type (supporting the support enforcers & blockers)
if (s == "ModelPart")
return MODEL_PART;
return ModelVolumeType::MODEL_PART;
if (s == "ParameterModifier")
return PARAMETER_MODIFIER;
return ModelVolumeType::PARAMETER_MODIFIER;
if (s == "SupportEnforcer")
return SUPPORT_ENFORCER;
return ModelVolumeType::SUPPORT_ENFORCER;
if (s == "SupportBlocker")
return SUPPORT_BLOCKER;
return ModelVolumeType::SUPPORT_BLOCKER;
assert(s == "0");
// Default value if invalud type string received.
return MODEL_PART;
return ModelVolumeType::MODEL_PART;
}
std::string ModelVolume::type_to_string(const Type t)
std::string ModelVolume::type_to_string(const ModelVolumeType t)
{
switch (t) {
case MODEL_PART: return "ModelPart";
case PARAMETER_MODIFIER: return "ParameterModifier";
case SUPPORT_ENFORCER: return "SupportEnforcer";
case SUPPORT_BLOCKER: return "SupportBlocker";
case ModelVolumeType::MODEL_PART: return "ModelPart";
case ModelVolumeType::PARAMETER_MODIFIER: return "ParameterModifier";
case ModelVolumeType::SUPPORT_ENFORCER: return "SupportEnforcer";
case ModelVolumeType::SUPPORT_BLOCKER: return "SupportBlocker";
default:
assert(false);
return "ModelPart";
@ -1671,7 +1671,7 @@ bool model_object_list_extended(const Model &model_old, const Model &model_new)
return true;
}
bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type)
bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type)
{
bool modifiers_differ = false;
size_t i_old, i_new;

View file

@ -12,6 +12,7 @@
#include <utility>
#include <vector>
#include "Geometry.hpp"
#include <libslic3r/SLA/SLACommon.hpp>
namespace Slic3r {
@ -48,6 +49,8 @@ struct ModelID
bool operator<=(const ModelID &rhs) const { return this->id <= rhs.id; }
bool operator>=(const ModelID &rhs) const { return this->id >= rhs.id; }
bool valid() const { return id != 0; }
size_t id;
};
@ -175,7 +178,8 @@ public:
// This vector holds position of selected support points for SLA. The data are
// saved in mesh coordinates to allow using them for several instances.
std::vector<Vec3f> sla_support_points;
// The format is (x, y, z, point_size, supports_island)
std::vector<sla::SupportPoint> sla_support_points;
/* This vector accumulates the total translation applied to the object by the
center_around_origin() method. Callers might want to apply the same translation
@ -291,6 +295,15 @@ private:
mutable bool m_raw_mesh_bounding_box_valid;
};
// Declared outside of ModelVolume, so it could be forward declared.
enum class ModelVolumeType : int {
INVALID = -1,
MODEL_PART = 0,
PARAMETER_MODIFIER,
SUPPORT_ENFORCER,
SUPPORT_BLOCKER,
};
// An object STL, or a modifier volume, over which a different set of parameters shall be applied.
// ModelVolume instances are owned by a ModelObject.
class ModelVolume : public ModelBase
@ -303,23 +316,15 @@ public:
// overriding the global Slic3r settings and the ModelObject settings.
DynamicPrintConfig config;
enum Type {
MODEL_TYPE_INVALID = -1,
MODEL_PART = 0,
PARAMETER_MODIFIER,
SUPPORT_ENFORCER,
SUPPORT_BLOCKER,
};
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; };
Type type() const { return m_type; }
void set_type(const Type t) { m_type = t; }
bool is_model_part() const { return m_type == MODEL_PART; }
bool is_modifier() const { return m_type == PARAMETER_MODIFIER; }
bool is_support_enforcer() const { return m_type == SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == SUPPORT_BLOCKER || m_type == SUPPORT_ENFORCER; }
ModelVolumeType type() const { return m_type; }
void set_type(const ModelVolumeType t) { m_type = t; }
bool is_model_part() const { return m_type == ModelVolumeType::MODEL_PART; }
bool is_modifier() const { return m_type == ModelVolumeType::PARAMETER_MODIFIER; }
bool is_support_enforcer() const { return m_type == ModelVolumeType::SUPPORT_ENFORCER; }
bool is_support_blocker() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER; }
bool is_support_modifier() const { return m_type == ModelVolumeType::SUPPORT_BLOCKER || m_type == ModelVolumeType::SUPPORT_ENFORCER; }
t_model_material_id material_id() const { return m_material_id; }
void set_material_id(t_model_material_id material_id);
ModelMaterial* material() const;
@ -353,8 +358,8 @@ public:
const TriangleMesh& get_convex_hull() const;
// Helpers for loading / storing into AMF / 3MF files.
static Type type_from_string(const std::string &s);
static std::string type_to_string(const Type t);
static ModelVolumeType type_from_string(const std::string &s);
static std::string type_to_string(const ModelVolumeType t);
const Geometry::Transformation& get_transformation() const { return m_transformation; }
void set_transformation(const Geometry::Transformation& transformation) { m_transformation = transformation; }
@ -399,7 +404,7 @@ private:
// Parent object owning this ModelVolume.
ModelObject* object;
// Is it an object to be printed, or a modifier volume?
Type m_type;
ModelVolumeType m_type;
t_model_material_id m_material_id;
// The convex hull of this model's mesh.
TriangleMesh m_convex_hull;
@ -411,13 +416,13 @@ private:
// 1 -> is splittable
int m_is_splittable {-1};
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(MODEL_PART), object(object)
ModelVolume(ModelObject *object, const TriangleMesh &mesh) : mesh(mesh), m_type(ModelVolumeType::MODEL_PART), object(object)
{
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(MODEL_PART), object(object) {}
mesh(std::move(mesh)), m_convex_hull(std::move(convex_hull)), m_type(ModelVolumeType::MODEL_PART), object(object) {}
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
ModelVolume(ModelObject *object, const ModelVolume &other) :
@ -629,7 +634,7 @@ extern bool model_object_list_extended(const Model &model_old, const Model &mode
// Test whether the new ModelObject contains a different set of volumes (or sorted in a different order)
// than the old ModelObject.
extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolume::Type type);
extern bool model_volume_list_changed(const ModelObject &model_object_old, const ModelObject &model_object_new, const ModelVolumeType type);
#ifndef NDEBUG
// Verify whether the IDs of Model / ModelObject / ModelVolume / ModelInstance / ModelMaterial are valid and unique.

View file

@ -22,6 +22,7 @@ typedef Point Vector;
// Vector types with a fixed point coordinate base type.
typedef Eigen::Matrix<coord_t, 2, 1, Eigen::DontAlign> Vec2crd;
typedef Eigen::Matrix<coord_t, 3, 1, Eigen::DontAlign> Vec3crd;
typedef Eigen::Matrix<int, 2, 1, Eigen::DontAlign> Vec2i;
typedef Eigen::Matrix<int, 3, 1, Eigen::DontAlign> Vec3i;
typedef Eigen::Matrix<int64_t, 2, 1, Eigen::DontAlign> Vec2i64;
typedef Eigen::Matrix<int64_t, 3, 1, Eigen::DontAlign> Vec3i64;

View file

@ -280,7 +280,7 @@ bool Print::is_step_done(PrintObjectStep step) const
return false;
tbb::mutex::scoped_lock lock(this->state_mutex());
for (const PrintObject *object : m_objects)
if (! object->m_state.is_done_unguarded(step))
if (! object->is_step_done_unguarded(step))
return false;
return true;
}
@ -549,10 +549,14 @@ void Print::model_volume_list_update_supports(ModelObject &model_object_dst, con
assert(! it->second); // not consumed yet
it->second = true;
ModelVolume *model_volume_dst = const_cast<ModelVolume*>(it->first);
assert(model_volume_dst->type() == model_volume_src->type());
// For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
assert((model_volume_dst->is_support_modifier() && model_volume_src->is_support_modifier()) || model_volume_dst->type() == model_volume_src->type());
model_object_dst.volumes.emplace_back(model_volume_dst);
if (model_volume_dst->is_support_modifier())
model_volume_dst->set_transformation(model_volume_src->get_transformation());
if (model_volume_dst->is_support_modifier()) {
// For support modifiers, the type may have been switched from blocker to enforcer and vice versa.
model_volume_dst->set_type(model_volume_src->type());
model_volume_dst->set_transformation(model_volume_src->get_transformation());
}
assert(model_volume_dst->get_matrix().isApprox(model_volume_src->get_matrix()));
} else {
// The volume was not found in the old list. Create a new copy.
@ -567,7 +571,7 @@ void Print::model_volume_list_update_supports(ModelObject &model_object_dst, con
delete mv_with_status.first;
}
static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolume::Type type)
static inline void model_volume_list_copy_configs(ModelObject &model_object_dst, const ModelObject &model_object_src, const ModelVolumeType type)
{
size_t i_src, i_dst;
for (i_src = 0, i_dst = 0; i_src < model_object_src.volumes.size() && i_dst < model_object_dst.volumes.size();) {
@ -713,7 +717,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
if (model.id() != m_model.id()) {
// Kill everything, initialize from scratch.
// Stop background processing.
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(this->invalidate_all_steps());
for (PrintObject *object : m_objects) {
model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted);
@ -745,7 +749,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
} else {
// Reorder the objects, add new objects.
// First stop background processing before shuffling or deleting the PrintObjects in the object list.
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(this->invalidate_step(psGCodeExport));
// Second create a new list of objects.
std::vector<ModelObject*> model_objects_old(std::move(m_model.objects));
@ -837,10 +841,10 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
assert(it_status->status == ModelObjectStatus::Old || it_status->status == ModelObjectStatus::Moved);
const ModelObject &model_object_new = *model.objects[idx_model_object];
// Check whether a model part volume was added or removed, their transformations or order changed.
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART);
bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::PARAMETER_MODIFIER);
bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_BLOCKER);
bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::SUPPORT_ENFORCER);
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
bool modifiers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::PARAMETER_MODIFIER);
bool support_blockers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_BLOCKER);
bool support_enforcers_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::SUPPORT_ENFORCER);
if (model_parts_differ || modifiers_differ ||
model_object.origin_translation != model_object_new.origin_translation ||
model_object.layer_height_ranges != model_object_new.layer_height_ranges ||
@ -855,7 +859,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
model_object.assign_copy(model_object_new);
} else if (support_blockers_differ || support_enforcers_differ) {
// First stop background processing before shuffling or deleting the ModelVolumes in the ModelObject's list.
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(false);
// Invalidate just the supports step.
auto range = print_object_status.equal_range(PrintObjectStatus(model_object.id()));
@ -882,8 +886,8 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
}
// Synchronize (just copy) the remaining data of ModelVolumes (name, config).
//FIXME What to do with m_material_id?
model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolume::MODEL_PART);
model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolume::PARAMETER_MODIFIER);
model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::MODEL_PART);
model_volume_list_copy_configs(model_object /* dst */, model_object_new /* src */, ModelVolumeType::PARAMETER_MODIFIER);
// Copy the ModelObject name, input_file and instances. The instances will compared against PrintObject instances in the next step.
model_object.name = model_object_new.name;
model_object.input_file = model_object_new.input_file;
@ -956,7 +960,7 @@ Print::ApplyStatus Print::apply(const Model &model, const DynamicPrintConfig &co
}
}
if (m_objects != print_objects_new) {
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(this->invalidate_all_steps());
m_objects = print_objects_new;
// Delete the PrintObjects marked as Unknown or Deleted.
@ -1502,7 +1506,8 @@ void Print::export_gcode(const std::string &path_template, GCodePreviewData *pre
// The following call may die if the output_filename_format template substitution fails.
std::string path = this->output_filepath(path_template);
std::string message = "Exporting G-code";
if (! path.empty()) {
if (! path.empty() && preview_data == nullptr) {
// Only show the path if preview_data is not set -> running from command line.
message += " to ";
message += path;
}
@ -1863,7 +1868,7 @@ std::string Print::output_filename() const
DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders();
return this->PrintBase::output_filename(m_config.output_filename_format.value, "gcode", &config);
}
/*
// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes
// and removing spaces.
static std::string short_time(const std::string &time)
@ -1903,7 +1908,7 @@ static std::string short_time(const std::string &time)
::sprintf(buffer, "%ds", seconds);
return buffer;
}
*/
DynamicConfig PrintStatistics::config() const
{
DynamicConfig config;

View file

@ -62,6 +62,10 @@ public:
return state;
}
bool is_started(StepType step, tbb::mutex &mtx) const {
return this->state_with_timestamp(step, mtx).state == STARTED;
}
bool is_done(StepType step, tbb::mutex &mtx) const {
return this->state_with_timestamp(step, mtx).state == DONE;
}
@ -70,6 +74,10 @@ public:
return m_state[step];
}
bool is_started_unguarded(StepType step) const {
return this->state_with_timestamp_unguarded(step).state == STARTED;
}
bool is_done_unguarded(StepType step) const {
return this->state_with_timestamp_unguarded(step).state == DONE;
}
@ -235,7 +243,24 @@ public:
virtual ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) = 0;
const Model& model() const { return m_model; }
struct TaskParams {
TaskParams() : single_model_object(0), single_model_instance_only(false), to_object_step(-1), to_print_step(-1) {}
// If non-empty, limit the processing to this ModelObject.
ModelID single_model_object;
// If set, only process single_model_object. Otherwise process everything, but single_model_object first.
bool single_model_instance_only;
// If non-negative, stop processing at the successive object step.
int to_object_step;
// If non-negative, stop processing at the successive print step.
int to_print_step;
};
// After calling the apply() function, call set_task() to limit the task to be processed by process().
virtual void set_task(const TaskParams &params) {}
// Perform the calculation. This is the only method that is to be called at a worker thread.
virtual void process() = 0;
// Clean up after process() finished, either with success, error or if canceled.
// The adjustments on the Print / PrintObject data due to set_task() are to be reverted here.
virtual void finalize() {}
struct SlicingStatus {
SlicingStatus(int percent, const std::string &text, unsigned int flags = 0) : percent(percent), text(text), flags(flags) {}
@ -244,8 +269,9 @@ public:
// Bitmap of flags.
enum FlagBits {
DEFAULT,
NO_RELOAD_SCENE = 0,
RELOAD_SCENE = 1,
NO_RELOAD_SCENE = 0,
RELOAD_SCENE = 1 << 1,
RELOAD_SLA_SUPPORT_POINTS = 1 << 2,
};
// Bitmap of FlagBits
unsigned int flags;
@ -300,7 +326,7 @@ protected:
tbb::mutex& state_mutex() const { return m_state_mutex; }
std::function<void()> cancel_callback() { return m_cancel_callback; }
void call_cancell_callback() { m_cancel_callback(); }
void call_cancel_callback() { m_cancel_callback(); }
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.
@ -349,6 +375,9 @@ protected:
bool invalidate_all_steps()
{ return m_state.invalidate_all(this->cancel_callback()); }
bool is_step_started_unguarded(PrintStepEnum step) const { return m_state.is_started_unguarded(step); }
bool is_step_done_unguarded(PrintStepEnum step) const { return m_state.is_done_unguarded(step); }
private:
PrintState<PrintStepEnum, COUNT> m_state;
};
@ -382,6 +411,9 @@ protected:
bool invalidate_all_steps()
{ return m_state.invalidate_all(PrintObjectBase::cancel_callback(m_print)); }
bool is_step_started_unguarded(PrintObjectStepEnum step) const { return m_state.is_started_unguarded(step); }
bool is_step_done_unguarded(PrintObjectStepEnum step) const { return m_state.is_done_unguarded(step); }
protected:
// If the background processing stop was requested, throw CanceledException.
// To be called by the worker thread and its sub-threads (mostly launched on the TBB thread pool) regularly.

View file

@ -362,12 +362,11 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->default_value = new ConfigOptionBool(false);
def = this->add("external_fill_pattern", coEnum);
def->label = L("Top/bottom fill pattern");
auto def_top_fill_pattern = def = this->add("top_fill_pattern", coEnum);
def->label = L("Top fill pattern");
def->category = L("Infill");
def->tooltip = L("Fill pattern for top/bottom infill. This only affects the external visible layer, "
"and not its adjacent solid shells.");
def->cli = "external-fill-pattern|solid-fill-pattern=s";
def->tooltip = L("Fill pattern for top infill. This only affects the top visible layer, and not its adjacent solid shells.");
def->cli = "top-fill-pattern|external-fill-pattern|solid-fill-pattern=s";
def->enum_keys_map = &ConfigOptionEnum<InfillPattern>::get_enum_values();
def->enum_values.push_back("rectilinear");
def->enum_values.push_back("concentric");
@ -379,8 +378,15 @@ void PrintConfigDef::init_fff_params()
def->enum_labels.push_back(L("Hilbert Curve"));
def->enum_labels.push_back(L("Archimedean Chords"));
def->enum_labels.push_back(L("Octagram Spiral"));
// solid_fill_pattern is an obsolete equivalent to external_fill_pattern.
def->aliases = { "solid_fill_pattern" };
// solid_fill_pattern is an obsolete equivalent to top_fill_pattern/bottom_fill_pattern.
def->aliases = { "solid_fill_pattern", "external_fill_pattern" };
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
def = this->add("bottom_fill_pattern", coEnum);
*def = *def_top_fill_pattern;
def->label = L("Bottom Pattern");
def->tooltip = L("Fill pattern for bottom infill. This only affects the bottom external visible layer, and not its adjacent solid shells.");
def->cli = "bottom-fill-pattern|external-fill-pattern|solid-fill-pattern=s";
def->default_value = new ConfigOptionEnum<InfillPattern>(ipRectilinear);
def = this->add("external_perimeter_extrusion_width", coFloatOrPercent);
@ -2435,6 +2441,32 @@ void PrintConfigDef::init_sla_params()
def->enum_labels.push_back(L("Portrait"));
def->default_value = new ConfigOptionEnum<SLADisplayOrientation>(sladoPortrait);
def = this->add("fast_tilt_time", coFloat);
def->label = L("Fast");
def->full_label = L("Fast tilt");
def->tooltip = L("Time of the fast tilt");
def->sidetext = L("s");
def->min = 0;
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(5.);
def = this->add("slow_tilt_time", coFloat);
def->label = L("Slow");
def->full_label = L("Slow tilt");
def->tooltip = L("Time of the slow tilt");
def->sidetext = L("s");
def->min = 0;
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(8.);
def = this->add("area_fill", coFloat);
def->label = L("Area fill");
def->tooltip = L("The percentage of the bed area. \nIf the print area exceeds the specified value, \nthen a slow tilt will be used, otherwise - a fast tilt");
def->sidetext = L("%");
def->min = 0;
def->mode = comExpert;
def->default_value = new ConfigOptionFloat(50.);
def = this->add("printer_correction", coFloats);
def->full_label = L("Printer scaling correction");
def->tooltip = L("Printer scaling correction");
@ -2450,6 +2482,14 @@ void PrintConfigDef::init_sla_params()
def->min = 0;
def->default_value = new ConfigOptionFloat(0.3);
def = this->add("faded_layers", coInt);
def->label = L("Faded layers");
def->tooltip = L("Number of the layers needed for the exposure time fade from initial exposure time to the exposure time");
def->min = 3;
def->max = 20;
def->mode = comExpert;
def->default_value = new ConfigOptionInt(10);
def = this->add("exposure_time", coFloat);
def->label = L("Exposure time");
def->tooltip = L("Exposure time");
@ -2630,28 +2670,19 @@ void PrintConfigDef::init_sla_params()
def->min = 0;
def->default_value = new ConfigOptionFloat(5.0);
def = this->add("support_density_at_horizontal", coInt);
def->label = L("Density on horizontal surfaces");
def = this->add("support_points_density_relative", coInt);
def->label = L("Support points density");
def->category = L("Supports");
def->tooltip = L("How many support points (approximately) should be placed on horizontal surface.");
def->sidetext = L("points per square dm");
def->tooltip = L("This is a relative measure of support points density.");
def->sidetext = L("%");
def->cli = "";
def->min = 0;
def->default_value = new ConfigOptionInt(500);
def->default_value = new ConfigOptionInt(100);
def = this->add("support_density_at_45", coInt);
def->label = L("Density on surfaces at 45 degrees");
def = this->add("support_points_minimal_distance", coFloat);
def->label = L("Minimal distance of the support points");
def->category = L("Supports");
def->tooltip = L("How many support points (approximately) should be placed on surface sloping at 45 degrees.");
def->sidetext = L("points per square dm");
def->cli = "";
def->min = 0;
def->default_value = new ConfigOptionInt(250);
def = this->add("support_minimal_z", coFloat);
def->label = L("Minimal support point height");
def->category = L("Supports");
def->tooltip = L("No support points will be placed lower than this value from the bottom.");
def->tooltip = L("No support points will be placed closer than this threshold.");
def->sidetext = L("mm");
def->cli = "";
def->min = 0;
@ -2699,6 +2730,17 @@ void PrintConfigDef::init_sla_params()
def->cli = "";
def->min = 0;
def->default_value = new ConfigOptionFloat(1.0);
def = this->add("pad_wall_tilt", coFloat);
def->label = L("Pad wall tilt");
def->category = L("Pad");
def->tooltip = L("The tilt of the pad wall relative to the bed plane. "
"90 degrees means straight walls.");
def->sidetext = L("degrees");
def->cli = "";
def->min = 0.1; // What should be the minimum?
def->max = 90;
def->default_value = new ConfigOptionFloat(45.0);
}
void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &value)
@ -2914,13 +2956,17 @@ std::string FullPrintConfig::validate()
if (! print_config_def.get("fill_pattern")->has_enum_value(this->fill_pattern.serialize()))
return "Invalid value for --fill-pattern";
// --external-fill-pattern
if (! print_config_def.get("external_fill_pattern")->has_enum_value(this->external_fill_pattern.serialize()))
return "Invalid value for --external-fill-pattern";
// --top-fill-pattern
if (! print_config_def.get("top_fill_pattern")->has_enum_value(this->top_fill_pattern.serialize()))
return "Invalid value for --top-fill-pattern";
// --bottom-fill-pattern
if (! print_config_def.get("bottom_fill_pattern")->has_enum_value(this->bottom_fill_pattern.serialize()))
return "Invalid value for --bottom-fill-pattern";
// --fill-density
if (fabs(this->fill_density.value - 100.) < EPSILON &&
! print_config_def.get("external_fill_pattern")->has_enum_value(this->fill_pattern.serialize()))
! print_config_def.get("top_fill_pattern")->has_enum_value(this->fill_pattern.serialize()))
return "The selected fill pattern is not supposed to work at 100% density";
// --infill-every-layers

View file

@ -462,7 +462,8 @@ public:
ConfigOptionFloat bridge_flow_ratio;
ConfigOptionFloat bridge_speed;
ConfigOptionBool ensure_vertical_shell_thickness;
ConfigOptionEnum<InfillPattern> external_fill_pattern;
ConfigOptionEnum<InfillPattern> top_fill_pattern;
ConfigOptionEnum<InfillPattern> bottom_fill_pattern;
ConfigOptionFloatOrPercent external_perimeter_extrusion_width;
ConfigOptionFloatOrPercent external_perimeter_speed;
ConfigOptionBool external_perimeters_first;
@ -504,7 +505,8 @@ protected:
OPT_PTR(bridge_flow_ratio);
OPT_PTR(bridge_speed);
OPT_PTR(ensure_vertical_shell_thickness);
OPT_PTR(external_fill_pattern);
OPT_PTR(top_fill_pattern);
OPT_PTR(bottom_fill_pattern);
OPT_PTR(external_perimeter_extrusion_width);
OPT_PTR(external_perimeter_speed);
OPT_PTR(external_perimeters_first);
@ -958,6 +960,9 @@ class SLAPrintObjectConfig : public StaticPrintConfig
public:
ConfigOptionFloat layer_height;
//Number of the layers needed for the exposure time fade [3;20]
ConfigOptionInt faded_layers /*= 10*/;
// Enabling or disabling support creation
ConfigOptionBool supports_enable;
@ -1002,9 +1007,8 @@ public:
ConfigOptionFloat support_object_elevation /*= 5.0*/;
/////// Following options influence automatic support points placement:
ConfigOptionInt support_density_at_horizontal;
ConfigOptionInt support_density_at_45;
ConfigOptionFloat support_minimal_z;
ConfigOptionInt support_points_density_relative;
ConfigOptionFloat support_points_minimal_distance;
// Now for the base pool (pad) /////////////////////////////////////////////
@ -1024,10 +1028,14 @@ public:
// The smoothing radius of the pad edges
ConfigOptionFloat pad_edge_radius /*= 1*/;
// The tilt of the pad wall...
ConfigOptionFloat pad_wall_tilt;
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
OPT_PTR(layer_height);
OPT_PTR(faded_layers);
OPT_PTR(supports_enable);
OPT_PTR(support_head_front_diameter);
OPT_PTR(support_head_penetration);
@ -1040,15 +1048,15 @@ protected:
OPT_PTR(support_base_height);
OPT_PTR(support_critical_angle);
OPT_PTR(support_max_bridge_length);
OPT_PTR(support_density_at_horizontal);
OPT_PTR(support_density_at_45);
OPT_PTR(support_minimal_z);
OPT_PTR(support_points_density_relative);
OPT_PTR(support_points_minimal_distance);
OPT_PTR(support_object_elevation);
OPT_PTR(pad_enable);
OPT_PTR(pad_wall_thickness);
OPT_PTR(pad_wall_height);
OPT_PTR(pad_max_merge_distance);
OPT_PTR(pad_edge_radius);
OPT_PTR(pad_wall_tilt);
}
};
@ -1085,6 +1093,9 @@ public:
ConfigOptionInt display_pixels_y;
ConfigOptionEnum<SLADisplayOrientation> display_orientation;
ConfigOptionFloats printer_correction;
ConfigOptionFloat fast_tilt_time;
ConfigOptionFloat slow_tilt_time;
ConfigOptionFloat area_fill;
protected:
void initialize(StaticCacheBase &cache, const char *base_ptr)
{
@ -1097,6 +1108,9 @@ protected:
OPT_PTR(display_pixels_y);
OPT_PTR(display_orientation);
OPT_PTR(printer_correction);
OPT_PTR(fast_tilt_time);
OPT_PTR(slow_tilt_time);
OPT_PTR(area_fill);
}
};

View file

@ -14,6 +14,17 @@
namespace Slic3r {
// Used for addressing parameters of FilePrinter::set_statistics()
enum ePrintStatistics
{
psUsedMaterial = 0,
psNumFade,
psNumSlow,
psNumFast,
psCnt
};
enum class FilePrinterFormat {
SLA_PNGZIP,
SVG
@ -118,32 +129,39 @@ template<> class FilePrinter<FilePrinterFormat::SLA_PNGZIP>
double m_layer_height = .0;
Raster::Origin m_o = Raster::Origin::TOP_LEFT;
double m_used_material = 0.0;
int m_cnt_fade_layers = 0;
int m_cnt_slow_layers = 0;
int m_cnt_fast_layers = 0;
std::string createIniContent(const std::string& projectname) {
double layer_height = m_layer_height;
// double layer_height = m_layer_height;
using std::string;
using std::to_string;
auto expt_str = to_string(m_exp_time_s);
auto expt_first_str = to_string(m_exp_time_first_s);
auto stepnum_str = to_string(static_cast<unsigned>(800*layer_height));
auto layerh_str = to_string(layer_height);
// auto stepnum_str = to_string(static_cast<unsigned>(800*layer_height));
auto layerh_str = to_string(m_layer_height);
const std::string cnt_fade_layers = to_string(m_cnt_fade_layers);
const std::string cnt_slow_layers = to_string(m_cnt_slow_layers);
const std::string cnt_fast_layers = to_string(m_cnt_fast_layers);
const std::string used_material = to_string(m_used_material);
return string(
"action = print\n"
"jobDir = ") + projectname + "\n" +
"expTime = " + expt_str + "\n"
"expTimeFirst = " + expt_first_str + "\n"
"stepNum = " + stepnum_str + "\n"
"wifiOn = 1\n"
"tiltSlow = 60\n"
"tiltFast = 15\n"
"numFade = 10\n"
"startdelay = 0\n"
"numFade = " + cnt_fade_layers + "\n"
"layerHeight = " + layerh_str + "\n"
"noteInfo = "
"expTime="+expt_str+"+resinType=generic+layerHeight="
+layerh_str+"+printer=DWARF3\n";
"expTime = "+expt_str+" + resinType = generic+layerHeight = "
+layerh_str+" + printer = DWARF3\n"
"usedMaterial = " + used_material + "\n"
"numSlow = " + cnt_slow_layers + "\n"
"numFast = " + cnt_fast_layers + "\n";
}
public:
@ -277,6 +295,17 @@ public:
out.close();
m_layers_rst[i].first.reset();
}
void set_statistics(const std::vector<double> statistics)
{
if (statistics.size() != psCnt)
return;
m_used_material = statistics[psUsedMaterial];
m_cnt_fade_layers = int(statistics[psNumFade]);
m_cnt_slow_layers = int(statistics[psNumSlow]);
m_cnt_fast_layers = int(statistics[psNumFast]);
}
};
}

View file

@ -498,7 +498,8 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_
|| opt_key == "bridge_angle") {
steps.emplace_back(posPrepareInfill);
} else if (
opt_key == "external_fill_pattern"
opt_key == "top_fill_pattern"
|| opt_key == "bottom_fill_pattern"
|| opt_key == "external_fill_link_max_length"
|| opt_key == "fill_angle"
|| opt_key == "fill_pattern"

View file

@ -1,47 +1,23 @@
#include "igl/random_points_on_mesh.h"
#include "igl/AABB.h"
#include <tbb/parallel_for.h>
#include "SLAAutoSupports.hpp"
#include "Model.hpp"
#include "ExPolygon.hpp"
#include "SVG.hpp"
#include "Point.hpp"
#include "ClipperUtils.hpp"
#include "Tesselate.hpp"
#include "libslic3r.h"
#include <iostream>
#include <random>
namespace Slic3r {
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
const Config& config, std::function<void(void)> throw_on_cancel)
: m_config(config), m_V(emesh.V()), m_F(emesh.F()), m_throw_on_cancel(throw_on_cancel)
{
// FIXME: It might be safer to get rid of the rand() calls altogether, because it is probably
// not always thread-safe and can be slow if it is.
srand(time(NULL)); // rand() is used by igl::random_point_on_mesh
// Find all separate islands that will need support. The coord_t number denotes height
// of a point just below the mesh (so that we can later project the point precisely
// on the mesh by raycasting (done by igl) and not risking we will place the point inside).
std::vector<std::pair<ExPolygon, coord_t>> islands = find_islands(slices, heights);
// Uniformly cover each of the islands with support points.
for (const auto& island : islands) {
std::vector<Vec3d> points = uniformly_cover(island);
m_throw_on_cancel();
project_upward_onto_mesh(points);
m_output.insert(m_output.end(), points.begin(), points.end());
m_throw_on_cancel();
}
// We are done with the islands. Let's sprinkle the rest of the mesh.
// The function appends to m_output.
sprinkle_mesh(mesh);
}
float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
/*float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2)
{
n1.normalize();
n2.normalize();
@ -59,115 +35,6 @@ float SLAAutoSupports::approximate_geodesic_distance(const Vec3d& p1, const Vec3
}
void SLAAutoSupports::sprinkle_mesh(const TriangleMesh& mesh)
{
std::vector<Vec3d> points;
// Loads the ModelObject raw_mesh and transforms it by first instance's transformation matrix (disregarding translation).
// Instances only differ in z-rotation, so it does not matter which of them will be used for the calculation.
// The supports point will be calculated on this mesh (so scaling ang vertical direction is correctly accounted for).
// Results will be inverse-transformed to raw_mesh coordinates.
//TriangleMesh mesh = m_model_object.raw_mesh();
//Transform3d transformation_matrix = m_model_object.instances[0]->get_matrix(true/*dont_translate*/);
//mesh.transform(transformation_matrix);
// Check that the object is thick enough to produce any support points
BoundingBoxf3 bb = mesh.bounding_box();
if (bb.size()(2) < m_config.minimal_z)
return;
// All points that we curretly have must be transformed too, so distance to them is correcly calculated.
//for (Vec3f& point : m_model_object.sla_support_points)
// point = transformation_matrix.cast<float>() * point;
// In order to calculate distance to already placed points, we must keep know which facet the point lies on.
std::vector<Vec3d> facets_normals;
// Only points belonging to islands were added so far - they all lie on horizontal surfaces:
for (unsigned int i=0; i<m_output.size(); ++i)
facets_normals.push_back(Vec3d(0,0,-1));
// The AABB hierarchy will be used to find normals of already placed points.
// The points added automatically will just push_back the new normal on the fly.
/*igl::AABB<Eigen::MatrixXf,3> aabb;
aabb.init(V, F);
for (unsigned int i=0; i<m_model_object.sla_support_points.size(); ++i) {
int facet_idx = 0;
Eigen::Matrix<float, 1, 3> dump;
Eigen::MatrixXf query_point = m_model_object.sla_support_points[i];
aabb.squared_distance(V, F, query_point, facet_idx, dump);
Vec3f a1 = V.row(F(facet_idx,1)) - V.row(F(facet_idx,0));
Vec3f a2 = V.row(F(facet_idx,2)) - V.row(F(facet_idx,0));
Vec3f normal = a1.cross(a2);
normal.normalize();
facets_normals.push_back(normal);
}*/
// New potential support point is randomly generated on the mesh and distance to all already placed points is calculated.
// In case it is never smaller than certain limit (depends on the new point's facet normal), the point is accepted.
// The process stops after certain number of points is refused in a row.
Vec3d point;
Vec3d normal;
int added_points = 0;
int refused_points = 0;
const int refused_limit = 30;
// Angle at which the density reaches zero:
const float threshold_angle = std::min(M_PI_2, M_PI_4 * acos(0.f/m_config.density_at_horizontal) / acos(m_config.density_at_45/m_config.density_at_horizontal));
size_t cancel_test_cntr = 0;
while (refused_points < refused_limit) {
if (++ cancel_test_cntr == 500) {
// Don't call the cancellation routine too often as the multi-core cache synchronization
// may be pretty expensive.
m_throw_on_cancel();
cancel_test_cntr = 0;
}
// Place a random point on the mesh and calculate corresponding facet's normal:
Eigen::VectorXi FI;
Eigen::MatrixXd B;
igl::random_points_on_mesh(1, m_V, m_F, B, FI);
point = B(0,0)*m_V.row(m_F(FI(0),0)) +
B(0,1)*m_V.row(m_F(FI(0),1)) +
B(0,2)*m_V.row(m_F(FI(0),2));
if (point(2) - bb.min(2) < m_config.minimal_z)
continue;
Vec3d a1 = m_V.row(m_F(FI(0),1)) - m_V.row(m_F(FI(0),0));
Vec3d a2 = m_V.row(m_F(FI(0),2)) - m_V.row(m_F(FI(0),0));
normal = a1.cross(a2);
normal.normalize();
// calculate angle between the normal and vertical:
float angle = angle_from_normal(normal.cast<float>());
if (angle > threshold_angle)
continue;
const float limit = distance_limit(angle);
bool add_it = true;
for (unsigned int i=0; i<points.size(); ++i) {
if (approximate_geodesic_distance(points[i], point, facets_normals[i], normal) < limit) {
add_it = false;
++refused_points;
break;
}
}
if (add_it) {
points.push_back(point.cast<double>());
facets_normals.push_back(normal);
++added_points;
refused_points = 0;
}
}
m_output.insert(m_output.end(), points.begin(), points.end());
// Now transform all support points to mesh coordinates:
//for (Vec3f& point : m_model_object.sla_support_points)
// point = transformation_matrix.inverse().cast<float>() * point;
}
float SLAAutoSupports::get_required_density(float angle) const
{
// calculation would be density_0 * cos(angle). To provide one more degree of freedom, we will scale the angle
@ -179,10 +46,470 @@ float SLAAutoSupports::get_required_density(float angle) const
float SLAAutoSupports::distance_limit(float angle) const
{
return 1./(2.4*get_required_density(angle));
}*/
SLAAutoSupports::SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
const Config& config, std::function<void(void)> throw_on_cancel)
: m_config(config), m_emesh(emesh), m_throw_on_cancel(throw_on_cancel)
{
process(slices, heights);
project_onto_mesh(m_output);
}
void SLAAutoSupports::project_onto_mesh(std::vector<sla::SupportPoint>& points) const
{
// The function makes sure that all the points are really exactly placed on the mesh.
igl::Hit hit_up{0, 0, 0.f, 0.f, 0.f};
igl::Hit hit_down{0, 0, 0.f, 0.f, 0.f};
// Use a reasonable granularity to account for the worker thread synchronization cost.
tbb::parallel_for(tbb::blocked_range<size_t>(0, points.size(), 64),
[this, &points](const tbb::blocked_range<size_t>& range) {
for (size_t point_id = range.begin(); point_id < range.end(); ++ point_id) {
if ((point_id % 16) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
m_throw_on_cancel();
Vec3f& p = points[point_id].pos;
// Project the point upward and downward and choose the closer intersection with the mesh.
//bool up = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., 1.), m_V, m_F, hit_up);
//bool down = igl::ray_mesh_intersect(p.cast<float>(), Vec3f(0., 0., -1.), m_V, m_F, hit_down);
sla::EigenMesh3D::hit_result hit_up = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., 1.));
sla::EigenMesh3D::hit_result hit_down = m_emesh.query_ray_hit(p.cast<double>(), Vec3d(0., 0., -1.));
bool up = hit_up.face() != -1;
bool down = hit_down.face() != -1;
if (!up && !down)
continue;
sla::EigenMesh3D::hit_result& hit = (!down || (hit_up.distance() < hit_down.distance())) ? hit_up : hit_down;
//int fid = hit.face();
//Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
//p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<float>();
p = p + (hit.distance() * hit.direction()).cast<float>();
}
});
}
static std::vector<SLAAutoSupports::MyLayer> make_layers(
const std::vector<ExPolygons>& slices, const std::vector<float>& heights,
std::function<void(void)> throw_on_cancel)
{
assert(slices.size() == heights.size());
// Allocate empty layers.
std::vector<SLAAutoSupports::MyLayer> layers;
layers.reserve(slices.size());
for (size_t i = 0; i < slices.size(); ++ i)
layers.emplace_back(i, heights[i]);
// FIXME: calculate actual pixel area from printer config:
//const float pixel_area = pow(wxGetApp().preset_bundle->project_config.option<ConfigOptionFloat>("display_width") / wxGetApp().preset_bundle->project_config.option<ConfigOptionInt>("display_pixels_x"), 2.f); //
const float pixel_area = pow(0.047f, 2.f);
// Use a reasonable granularity to account for the worker thread synchronization cost.
tbb::parallel_for(tbb::blocked_range<size_t>(0, layers.size(), 32),
[&layers, &slices, &heights, pixel_area, throw_on_cancel](const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++ layer_id) {
if ((layer_id % 8) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
throw_on_cancel();
SLAAutoSupports::MyLayer &layer = layers[layer_id];
const ExPolygons &islands = slices[layer_id];
//FIXME WTF?
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
layer.islands.reserve(islands.size());
for (const ExPolygon &island : islands) {
float area = float(island.area() * SCALING_FACTOR * SCALING_FACTOR);
if (area >= pixel_area)
//FIXME this is not a correct centroid of a polygon with holes.
layer.islands.emplace_back(layer, island, get_extents(island.contour), Slic3r::unscale(island.contour.centroid()).cast<float>(), area, height);
}
}
});
// Calculate overlap of successive layers. Link overlapping islands.
tbb::parallel_for(tbb::blocked_range<size_t>(1, layers.size(), 8),
[&layers, &heights, throw_on_cancel](const tbb::blocked_range<size_t>& range) {
for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
if ((layer_id % 2) == 0)
// Don't call the following function too often as it flushes CPU write caches due to synchronization primitves.
throw_on_cancel();
SLAAutoSupports::MyLayer &layer_above = layers[layer_id];
SLAAutoSupports::MyLayer &layer_below = layers[layer_id - 1];
//FIXME WTF?
const float height = (layer_id>2 ? heights[layer_id-3] : heights[0]-(heights[1]-heights[0]));
const float layer_height = (layer_id!=0 ? heights[layer_id]-heights[layer_id-1] : heights[0]);
const float safe_angle = 5.f * (float(M_PI)/180.f); // smaller number - less supports
const float between_layers_offset = float(scale_(layer_height / std::tan(safe_angle)));
//FIXME This has a quadratic time complexity, it will be excessively slow for many tiny islands.
for (SLAAutoSupports::Structure &top : layer_above.islands) {
for (SLAAutoSupports::Structure &bottom : layer_below.islands) {
float overlap_area = top.overlap_area(bottom);
if (overlap_area > 0) {
top.islands_below.emplace_back(&bottom, overlap_area);
bottom.islands_above.emplace_back(&top, overlap_area);
}
}
if (! top.islands_below.empty()) {
Polygons top_polygons = to_polygons(*top.polygon);
Polygons bottom_polygons = top.polygons_below();
top.overhangs = diff_ex(top_polygons, bottom_polygons);
if (! top.overhangs.empty()) {
top.overhangs_area = 0.f;
std::vector<std::pair<ExPolygon*, float>> expolys_with_areas;
for (ExPolygon &ex : top.overhangs) {
float area = float(ex.area());
expolys_with_areas.emplace_back(&ex, area);
top.overhangs_area += area;
}
std::sort(expolys_with_areas.begin(), expolys_with_areas.end(),
[](const std::pair<ExPolygon*, float> &p1, const std::pair<ExPolygon*, float> &p2)
{ return p1.second > p2.second; });
ExPolygons overhangs_sorted;
for (auto &p : expolys_with_areas)
overhangs_sorted.emplace_back(std::move(*p.first));
top.overhangs = std::move(overhangs_sorted);
top.overhangs_area *= float(SCALING_FACTOR * SCALING_FACTOR);
top.dangling_areas = diff_ex(top_polygons, offset(bottom_polygons, between_layers_offset));
}
}
}
}
});
return layers;
}
void SLAAutoSupports::process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights)
{
#ifdef SLA_AUTOSUPPORTS_DEBUG
std::vector<std::pair<ExPolygon, coord_t>> islands;
#endif /* SLA_AUTOSUPPORTS_DEBUG */
std::vector<SLAAutoSupports::MyLayer> layers = make_layers(slices, heights, m_throw_on_cancel);
PointGrid3D point_grid;
point_grid.cell_size = Vec3f(10.f, 10.f, 10.f);
for (unsigned int layer_id = 0; layer_id < layers.size(); ++ layer_id) {
SLAAutoSupports::MyLayer *layer_top = &layers[layer_id];
SLAAutoSupports::MyLayer *layer_bottom = (layer_id > 0) ? &layers[layer_id - 1] : nullptr;
std::vector<float> support_force_bottom;
if (layer_bottom != nullptr) {
support_force_bottom.assign(layer_bottom->islands.size(), 0.f);
for (size_t i = 0; i < layer_bottom->islands.size(); ++ i)
support_force_bottom[i] = layer_bottom->islands[i].supports_force_total();
}
for (Structure &top : layer_top->islands)
for (Structure::Link &bottom_link : top.islands_below) {
Structure &bottom = *bottom_link.island;
float centroids_dist = (bottom.centroid - top.centroid).norm();
// Penalization resulting from centroid offset:
// bottom.supports_force *= std::min(1.f, 1.f - std::min(1.f, (1600.f * layer_height) * centroids_dist * centroids_dist / bottom.area));
float &support_force = support_force_bottom[&bottom - layer_bottom->islands.data()];
//FIXME this condition does not reflect a bifurcation into a one large island and one tiny island well, it incorrectly resets the support force to zero.
// One should rather work with the overlap area vs overhang area.
// support_force *= std::min(1.f, 1.f - std::min(1.f, 0.1f * centroids_dist * centroids_dist / bottom.area));
// Penalization resulting from increasing polygon area:
support_force *= std::min(1.f, 20.f * bottom.area / top.area);
}
// Let's assign proper support force to each of them:
if (layer_id > 0) {
for (Structure &below : layer_bottom->islands) {
float below_support_force = support_force_bottom[&below - layer_bottom->islands.data()];
float above_overlap_area = 0.f;
for (Structure::Link &above_link : below.islands_above)
above_overlap_area += above_link.overlap_area;
for (Structure::Link &above_link : below.islands_above)
above_link.island->supports_force_inherited += below_support_force * above_link.overlap_area / above_overlap_area;
}
}
// Now iterate over all polygons and append new points if needed.
for (Structure &s : layer_top->islands) {
// Penalization resulting from large diff from the last layer:
// s.supports_force_inherited /= std::max(1.f, (layer_height / 0.3f) * e_area / s.area);
s.supports_force_inherited /= std::max(1.f, 0.17f * (s.overhangs_area) / s.area);
float force_deficit = s.support_force_deficit(m_config.tear_pressure());
if (s.islands_below.empty()) { // completely new island - needs support no doubt
uniformly_cover({ *s.polygon }, s, point_grid, true);
} else if (! s.dangling_areas.empty()) {
// Let's see if there's anything that overlaps enough to need supports:
// What we now have in polygons needs support, regardless of what the forces are, so we can add them.
//FIXME is it an island point or not? Vojtech thinks it is.
uniformly_cover(s.dangling_areas, s, point_grid);
} else if (! s.overhangs.empty()) {
//FIXME add the support force deficit as a parameter, only cover until the defficiency is covered.
uniformly_cover(s.overhangs, s, point_grid);
}
}
m_throw_on_cancel();
#ifdef SLA_AUTOSUPPORTS_DEBUG
/*std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
output_expolygons(diff, "diff" + layer_num_str + ".svg");
if (!islands.empty())
output_expolygons(islands, "islands" + layer_num_str + ".svg");*/
#endif /* SLA_AUTOSUPPORTS_DEBUG */
}
}
std::vector<Vec2f> sample_expolygon(const ExPolygon &expoly, float samples_per_mm2, std::mt19937 &rng)
{
// Triangulate the polygon with holes into triplets of 3D points.
std::vector<Vec2f> triangles = Slic3r::triangulate_expolygon_2f(expoly);
std::vector<Vec2f> out;
if (! triangles.empty())
{
// Calculate area of each triangle.
std::vector<float> areas;
areas.reserve(triangles.size() / 3);
for (size_t i = 0; i < triangles.size(); ) {
const Vec2f &a = triangles[i ++];
const Vec2f v1 = triangles[i ++] - a;
const Vec2f v2 = triangles[i ++] - a;
areas.emplace_back(0.5f * std::abs(cross2(v1, v2)));
if (i != 3)
// Prefix sum of the areas.
areas.back() += areas[areas.size() - 2];
}
size_t num_samples = size_t(ceil(areas.back() * samples_per_mm2));
std::uniform_real_distribution<> random_triangle(0., double(areas.back()));
std::uniform_real_distribution<> random_float(0., 1.);
for (size_t i = 0; i < num_samples; ++ i) {
double r = random_triangle(rng);
size_t idx_triangle = std::min<size_t>(std::upper_bound(areas.begin(), areas.end(), (float)r) - areas.begin(), areas.size() - 1) * 3;
// Select a random point on the triangle.
double u = float(sqrt(random_float(rng)));
double v = float(random_float(rng));
const Vec2f &a = triangles[idx_triangle ++];
const Vec2f &b = triangles[idx_triangle++];
const Vec2f &c = triangles[idx_triangle];
const Vec2f x = a * (1.f - u) + b * (u * (1.f - v)) + c * (v * u);
out.emplace_back(x);
}
}
return out;
}
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygon &expoly, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
{
std::vector<Vec2f> out = sample_expolygon(expoly, samples_per_mm2, rng);
double point_stepping_scaled = scale_(1.f) / samples_per_mm_boundary;
for (size_t i_contour = 0; i_contour <= expoly.holes.size(); ++ i_contour) {
const Polygon &contour = (i_contour == 0) ? expoly.contour : expoly.holes[i_contour - 1];
const Points pts = contour.equally_spaced_points(point_stepping_scaled);
for (size_t i = 0; i < pts.size(); ++ i)
out.emplace_back(unscale<float>(pts[i].x()), unscale<float>(pts[i].y()));
}
return out;
}
std::vector<Vec2f> sample_expolygon_with_boundary(const ExPolygons &expolys, float samples_per_mm2, float samples_per_mm_boundary, std::mt19937 &rng)
{
std::vector<Vec2f> out;
for (const ExPolygon &expoly : expolys)
append(out, sample_expolygon_with_boundary(expoly, samples_per_mm2, samples_per_mm_boundary, rng));
return out;
}
template<typename REFUSE_FUNCTION>
static inline std::vector<Vec2f> poisson_disk_from_samples(const std::vector<Vec2f> &raw_samples, float radius, REFUSE_FUNCTION refuse_function)
{
Vec2f corner_min(std::numeric_limits<float>::max(), std::numeric_limits<float>::max());
for (const Vec2f &pt : raw_samples) {
corner_min.x() = std::min(corner_min.x(), pt.x());
corner_min.y() = std::min(corner_min.y(), pt.y());
}
// Assign the raw samples to grid cells, sort the grid cells lexicographically.
struct RawSample {
Vec2f coord;
Vec2i cell_id;
};
std::vector<RawSample> raw_samples_sorted;
RawSample sample;
for (const Vec2f &pt : raw_samples) {
sample.coord = pt;
sample.cell_id = ((pt - corner_min) / radius).cast<int>();
raw_samples_sorted.emplace_back(sample);
}
std::sort(raw_samples_sorted.begin(), raw_samples_sorted.end(), [](const RawSample &lhs, const RawSample &rhs)
{ return lhs.cell_id.x() < rhs.cell_id.x() || (lhs.cell_id.x() == rhs.cell_id.x() && lhs.cell_id.y() < rhs.cell_id.y()); });
struct PoissonDiskGridEntry {
// Resulting output sample points for this cell:
enum {
max_positions = 4
};
Vec2f poisson_samples[max_positions];
int num_poisson_samples = 0;
// Index into raw_samples:
int first_sample_idx;
int sample_cnt;
};
struct CellIDHash {
std::size_t operator()(const Vec2i &cell_id) const {
return std::hash<int>()(cell_id.x()) ^ std::hash<int>()(cell_id.y() * 593);
}
};
// Map from cell IDs to hash_data. Each hash_data points to the range in raw_samples corresponding to that cell.
// (We could just store the samples in hash_data. This implementation is an artifact of the reference paper, which
// is optimizing for GPU acceleration that we haven't implemented currently.)
typedef std::unordered_map<Vec2i, PoissonDiskGridEntry, CellIDHash> Cells;
Cells cells;
{
typename Cells::iterator last_cell_id_it;
Vec2i last_cell_id(-1, -1);
for (int i = 0; i < raw_samples_sorted.size(); ++ i) {
const RawSample &sample = raw_samples_sorted[i];
if (sample.cell_id == last_cell_id) {
// This sample is in the same cell as the previous, so just increase the count. Cells are
// always contiguous, since we've sorted raw_samples_sorted by cell ID.
++ last_cell_id_it->second.sample_cnt;
} else {
// This is a new cell.
PoissonDiskGridEntry data;
data.first_sample_idx = i;
data.sample_cnt = 1;
auto result = cells.insert({sample.cell_id, data});
last_cell_id = sample.cell_id;
last_cell_id_it = result.first;
}
}
}
const int max_trials = 5;
const float radius_squared = radius * radius;
for (int trial = 0; trial < max_trials; ++ trial) {
// Create sample points for each entry in cells.
for (auto &it : cells) {
const Vec2i &cell_id = it.first;
PoissonDiskGridEntry &cell_data = it.second;
// This cell's raw sample points start at first_sample_idx. On trial 0, try the first one. On trial 1, try first_sample_idx + 1.
int next_sample_idx = cell_data.first_sample_idx + trial;
if (trial >= cell_data.sample_cnt)
// There are no more points to try for this cell.
continue;
const RawSample &candidate = raw_samples_sorted[next_sample_idx];
// See if this point conflicts with any other points in this cell, or with any points in
// neighboring cells. Note that it's possible to have more than one point in the same cell.
bool conflict = refuse_function(candidate.coord);
for (int i = -1; i < 2 && ! conflict; ++ i) {
for (int j = -1; j < 2; ++ j) {
const auto &it_neighbor = cells.find(cell_id + Vec2i(i, j));
if (it_neighbor != cells.end()) {
const PoissonDiskGridEntry &neighbor = it_neighbor->second;
for (int i_sample = 0; i_sample < neighbor.num_poisson_samples; ++ i_sample)
if ((neighbor.poisson_samples[i_sample] - candidate.coord).squaredNorm() < radius_squared) {
conflict = true;
break;
}
}
}
}
if (! conflict) {
// Store the new sample.
assert(cell_data.num_poisson_samples < cell_data.max_positions);
if (cell_data.num_poisson_samples < cell_data.max_positions)
cell_data.poisson_samples[cell_data.num_poisson_samples ++] = candidate.coord;
}
}
}
// Copy the results to the output.
std::vector<Vec2f> out;
for (const auto& it : cells)
for (int i = 0; i < it.second.num_poisson_samples; ++ i)
out.emplace_back(it.second.poisson_samples[i]);
return out;
}
void SLAAutoSupports::uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island, bool just_one)
{
//int num_of_points = std::max(1, (int)((island.area()*pow(SCALING_FACTOR, 2) * m_config.tear_pressure)/m_config.support_force));
const float support_force_deficit = structure.support_force_deficit(m_config.tear_pressure());
if (support_force_deficit < 0)
return;
// Number of newly added points.
const size_t poisson_samples_target = size_t(ceil(support_force_deficit / m_config.support_force()));
const float density_horizontal = m_config.tear_pressure() / m_config.support_force();
//FIXME why?
float poisson_radius = std::max(m_config.minimal_distance, 1.f / (5.f * density_horizontal));
// const float poisson_radius = 1.f / (15.f * density_horizontal);
const float samples_per_mm2 = 30.f / (float(M_PI) * poisson_radius * poisson_radius);
// Minimum distance between samples, in 3D space.
// float min_spacing = poisson_radius / 3.f;
float min_spacing = poisson_radius;
//FIXME share the random generator. The random generator may be not so cheap to initialize, also we don't want the random generator to be restarted for each polygon.
std::random_device rd;
std::mt19937 rng(rd());
std::vector<Vec2f> raw_samples = sample_expolygon_with_boundary(islands, samples_per_mm2, 5.f / poisson_radius, rng);
std::vector<Vec2f> poisson_samples;
for (size_t iter = 0; iter < 4; ++ iter) {
poisson_samples = poisson_disk_from_samples(raw_samples, poisson_radius,
[&structure, &grid3d, min_spacing](const Vec2f &pos) {
return grid3d.collides_with(pos, &structure, min_spacing);
});
if (poisson_samples.size() >= poisson_samples_target || m_config.minimal_distance > poisson_radius-EPSILON)
break;
float coeff = 0.5f;
if (poisson_samples.size() * 2 > poisson_samples_target)
coeff = float(poisson_samples.size()) / float(poisson_samples_target);
poisson_radius = std::max(m_config.minimal_distance, poisson_radius * coeff);
min_spacing = std::max(m_config.minimal_distance, min_spacing * coeff);
}
#ifdef SLA_AUTOSUPPORTS_DEBUG
{
static int irun = 0;
Slic3r::SVG svg(debug_out_path("SLA_supports-uniformly_cover-%d.svg", irun ++), get_extents(islands));
for (const ExPolygon &island : islands)
svg.draw(island);
for (const Vec2f &pt : raw_samples)
svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "red");
for (const Vec2f &pt : poisson_samples)
svg.draw(Point(scale_(pt.x()), scale_(pt.y())), "blue");
}
#endif /* NDEBUG */
// assert(! poisson_samples.empty());
if (poisson_samples_target < poisson_samples.size()) {
std::shuffle(poisson_samples.begin(), poisson_samples.end(), rng);
poisson_samples.erase(poisson_samples.begin() + poisson_samples_target, poisson_samples.end());
}
for (const Vec2f &pt : poisson_samples) {
m_output.emplace_back(float(pt(0)), float(pt(1)), structure.height, 0.2f, is_new_island);
structure.supports_force_this_layer += m_config.support_force();
grid3d.insert(pt, &structure);
}
}
#ifdef SLA_AUTOSUPPORTS_DEBUG
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string filename) const
void SLAAutoSupports::output_structures(const std::vector<Structure>& structures)
{
for (unsigned int i=0 ; i<structures.size(); ++i) {
std::stringstream ss;
ss << structures[i].unique_id.count() << "_" << std::setw(10) << std::setfill('0') << 1000 + (int)structures[i].height/1000 << ".png";
output_expolygons(std::vector<ExPolygon>{*structures[i].polygon}, ss.str());
}
}
void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, const std::string &filename)
{
BoundingBox bb(Point(-30000000, -30000000), Point(30000000, 30000000));
Slic3r::SVG svg_cummulative(filename, bb);
@ -198,138 +525,6 @@ void SLAAutoSupports::output_expolygons(const ExPolygons& expolys, std::string f
svg_cummulative.draw_outline(expolys[i].holes, "blue", scale_(0.05));
}
}
#endif /* SLA_AUTOSUPPORTS_DEBUG */
std::vector<std::pair<ExPolygon, coord_t>> SLAAutoSupports::find_islands(const std::vector<ExPolygons>& slices, const std::vector<float>& heights) const
{
std::vector<std::pair<ExPolygon, coord_t>> islands;
struct PointAccessor {
const Point* operator()(const Point &pt) const { return &pt; }
};
typedef ClosestPointInRadiusLookup<Point, PointAccessor> ClosestPointLookupType;
for (unsigned int i = 0; i<slices.size(); ++i) {
const ExPolygons& expolys_top = slices[i];
const ExPolygons& expolys_bottom = (i == 0 ? ExPolygons() : slices[i-1]);
std::string layer_num_str = std::string((i<10 ? "0" : "")) + std::string((i<100 ? "0" : "")) + std::to_string(i);
#ifdef SLA_AUTOSUPPORTS_DEBUG
output_expolygons(expolys_top, "top" + layer_num_str + ".svg");
#endif /* SLA_AUTOSUPPORTS_DEBUG */
ExPolygons diff = diff_ex(expolys_top, expolys_bottom);
#ifdef SLA_AUTOSUPPORTS_DEBUG
output_expolygons(diff, "diff" + layer_num_str + ".svg");
#endif /* SLA_AUTOSUPPORTS_DEBUG */
ClosestPointLookupType cpl(SCALED_EPSILON);
for (const ExPolygon& expol : expolys_top) {
for (const Point& p : expol.contour.points)
cpl.insert(p);
for (const Polygon& hole : expol.holes)
for (const Point& p : hole.points)
cpl.insert(p);
// the lookup structure now contains all points from the top slice
}
for (const ExPolygon& polygon : diff) {
// we want to check all boundary points of the diff polygon
bool island = true;
for (const Point& p : polygon.contour.points) {
if (cpl.find(p).second != 0) { // the point belongs to the bottom slice - this cannot be an island
island = false;
goto NO_ISLAND;
}
}
for (const Polygon& hole : polygon.holes)
for (const Point& p : hole.points)
if (cpl.find(p).second != 0) {
island = false;
goto NO_ISLAND;
}
if (island) { // all points of the diff polygon are from the top slice
islands.push_back(std::make_pair(polygon, scale_(i!=0 ? heights[i-1] : heights[0]-(heights[1]-heights[0]))));
}
NO_ISLAND: ;// continue with next ExPolygon
}
#ifdef SLA_AUTOSUPPORTS_DEBUG
//if (!islands.empty())
// output_expolygons(islands, "islands" + layer_num_str + ".svg");
#endif /* SLA_AUTOSUPPORTS_DEBUG */
m_throw_on_cancel();
}
return islands;
}
std::vector<Vec3d> SLAAutoSupports::uniformly_cover(const std::pair<ExPolygon, coord_t>& island)
{
int num_of_points = std::max(1, (int)(island.first.area()*pow(SCALING_FACTOR, 2) * get_required_density(0)));
// In case there is just one point to place, we'll place it into the polygon's centroid (unless it lies in a hole).
if (num_of_points == 1) {
Point out(island.first.contour.centroid());
for (const auto& hole : island.first.holes)
if (hole.contains(out))
goto HOLE_HIT;
return std::vector<Vec3d>{unscale(out(0), out(1), island.second)};
}
HOLE_HIT:
// In this case either the centroid lies in a hole, or there are multiple points
// to place. We will cover the island another way.
// For now we'll just place the points randomly not too close to the others.
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0., 1.);
std::vector<Vec3d> island_new_points;
const BoundingBox& bb = get_extents(island.first);
const int refused_limit = 30;
int refused_points = 0;
while (refused_points < refused_limit) {
Point out(bb.min(0) + bb.size()(0) * dis(gen),
bb.min(1) + bb.size()(1) * dis(gen)) ;
Vec3d unscaled_out = unscale(out(0), out(1), island.second);
bool add_it = true;
if (!island.first.contour.contains(out))
add_it = false;
else
for (const Polygon& hole : island.first.holes)
if (hole.contains(out))
add_it = false;
if (add_it) {
for (const Vec3d& p : island_new_points) {
if ((p - unscaled_out).squaredNorm() < distance_limit(0)) {
add_it = false;
++refused_points;
break;
}
}
}
if (add_it)
island_new_points.emplace_back(unscaled_out);
}
return island_new_points;
}
void SLAAutoSupports::project_upward_onto_mesh(std::vector<Vec3d>& points) const
{
Vec3f dir(0., 0., 1.);
igl::Hit hit{0, 0, 0.f, 0.f, 0.f};
for (Vec3d& p : points) {
igl::ray_mesh_intersect(p.cast<float>(), dir, m_V, m_F, hit);
int fid = hit.id;
Vec3f bc(1-hit.u-hit.v, hit.u, hit.v);
p = (bc(0) * m_V.row(m_F(fid, 0)) + bc(1) * m_V.row(m_F(fid, 1)) + bc(2)*m_V.row(m_F(fid, 2))).cast<double>();
}
}
#endif
} // namespace Slic3r

View file

@ -1,9 +1,12 @@
#ifndef SLAAUTOSUPPORTS_HPP_
#define SLAAUTOSUPPORTS_HPP_
#include <libslic3r/ClipperUtils.hpp>
#include <libslic3r/Point.hpp>
#include <libslic3r/TriangleMesh.hpp>
#include <libslic3r/SLA/SLASupportTree.hpp>
#include <libslic3r/SLA/SLACommon.hpp>
#include <boost/container/small_vector.hpp>
// #define SLA_AUTOSUPPORTS_DEBUG
@ -12,36 +15,184 @@ namespace Slic3r {
class SLAAutoSupports {
public:
struct Config {
float density_at_horizontal;
float density_at_45;
float minimal_z;
float density_relative;
float minimal_distance;
///////////////
inline float support_force() const { return 10.f / density_relative; } // a force one point can support (arbitrary force unit)
inline float tear_pressure() const { return 1.f; } // pressure that the display exerts (the force unit per mm2)
};
SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel);
const std::vector<Vec3d>& output() { return m_output; }
SLAAutoSupports(const TriangleMesh& mesh, const sla::EigenMesh3D& emesh, const std::vector<ExPolygons>& slices,
const std::vector<float>& heights, const Config& config, std::function<void(void)> throw_on_cancel);
const std::vector<sla::SupportPoint>& output() { return m_output; }
private:
std::vector<Vec3d> m_output;
std::vector<Vec3d> m_normals;
TriangleMesh mesh;
static float angle_from_normal(const stl_normal& normal) { return acos((-normal.normalized())(2)); }
float get_required_density(float angle) const;
float distance_limit(float angle) const;
static float approximate_geodesic_distance(const Vec3d& p1, const Vec3d& p2, Vec3d& n1, Vec3d& n2);
std::vector<std::pair<ExPolygon, coord_t>> find_islands(const std::vector<ExPolygons>& slices, const std::vector<float>& heights) const;
void sprinkle_mesh(const TriangleMesh& mesh);
std::vector<Vec3d> uniformly_cover(const std::pair<ExPolygon, coord_t>& island);
void project_upward_onto_mesh(std::vector<Vec3d>& points) const;
struct MyLayer;
struct Structure {
Structure(MyLayer &layer, const ExPolygon& poly, const BoundingBox &bbox, const Vec2f &centroid, float area, float h) :
layer(&layer), polygon(&poly), bbox(bbox), centroid(centroid), area(area), height(h)
#ifdef SLA_AUTOSUPPORTS_DEBUG
void output_expolygons(const ExPolygons& expolys, std::string filename) const;
, unique_id(std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()))
#endif /* SLA_AUTOSUPPORTS_DEBUG */
{}
MyLayer *layer;
const ExPolygon* polygon = nullptr;
const BoundingBox bbox;
const Vec2f centroid = Vec2f::Zero();
const float area = 0.f;
float height = 0;
// How well is this ExPolygon held to the print base?
// Positive number, the higher the better.
float supports_force_this_layer = 0.f;
float supports_force_inherited = 0.f;
float supports_force_total() const { return this->supports_force_this_layer + this->supports_force_inherited; }
#ifdef SLA_AUTOSUPPORTS_DEBUG
std::chrono::milliseconds unique_id;
#endif /* SLA_AUTOSUPPORTS_DEBUG */
struct Link {
Link(Structure *island, float overlap_area) : island(island), overlap_area(overlap_area) {}
Structure *island;
float overlap_area;
};
#ifdef NDEBUG
// In release mode, use the optimized container.
boost::container::small_vector<Link, 4> islands_above;
boost::container::small_vector<Link, 4> islands_below;
#else
// In debug mode, use the standard vector, which is well handled by debugger visualizer.
std::vector<Link> islands_above;
std::vector<Link> islands_below;
#endif
ExPolygons dangling_areas;
ExPolygons overhangs;
float overhangs_area;
bool overlaps(const Structure &rhs) const {
return this->bbox.overlap(rhs.bbox) && (this->polygon->overlaps(*rhs.polygon) || rhs.polygon->overlaps(*this->polygon));
}
float overlap_area(const Structure &rhs) const {
double out = 0.;
if (this->bbox.overlap(rhs.bbox)) {
Polygons polys = intersection(to_polygons(*this->polygon), to_polygons(*rhs.polygon), false);
for (const Polygon &poly : polys)
out += poly.area();
}
return float(out);
}
float area_below() const {
float area = 0.f;
for (const Link &below : this->islands_below)
area += below.island->area;
return area;
}
Polygons polygons_below() const {
size_t cnt = 0;
for (const Link &below : this->islands_below)
cnt += 1 + below.island->polygon->holes.size();
Polygons out;
out.reserve(cnt);
for (const Link &below : this->islands_below) {
out.emplace_back(below.island->polygon->contour);
append(out, below.island->polygon->holes);
}
return out;
}
ExPolygons expolygons_below() const {
ExPolygons out;
out.reserve(this->islands_below.size());
for (const Link &below : this->islands_below)
out.emplace_back(*below.island->polygon);
return out;
}
// Positive deficit of the supports. If negative, this area is well supported. If positive, more supports need to be added.
float support_force_deficit(const float tear_pressure) const { return this->area * tear_pressure - this->supports_force_total(); }
};
struct MyLayer {
MyLayer(const size_t layer_id, coordf_t print_z) : layer_id(layer_id), print_z(print_z) {}
size_t layer_id;
coordf_t print_z;
std::vector<Structure> islands;
};
struct RichSupportPoint {
Vec3f position;
Structure *island;
};
struct PointGrid3D {
struct GridHash {
std::size_t operator()(const Vec3i &cell_id) const {
return std::hash<int>()(cell_id.x()) ^ std::hash<int>()(cell_id.y() * 593) ^ std::hash<int>()(cell_id.z() * 7919);
}
};
typedef std::unordered_multimap<Vec3i, RichSupportPoint, GridHash> Grid;
Vec3f cell_size;
Grid grid;
Vec3i cell_id(const Vec3f &pos) {
return Vec3i(int(floor(pos.x() / cell_size.x())),
int(floor(pos.y() / cell_size.y())),
int(floor(pos.z() / cell_size.z())));
}
void insert(const Vec2f &pos, Structure *island) {
RichSupportPoint pt;
pt.position = Vec3f(pos.x(), pos.y(), float(island->layer->print_z));
pt.island = island;
grid.emplace(cell_id(pt.position), pt);
}
bool collides_with(const Vec2f &pos, Structure *island, float radius) {
Vec3f pos3d(pos.x(), pos.y(), float(island->layer->print_z));
Vec3i cell = cell_id(pos3d);
std::pair<Grid::const_iterator, Grid::const_iterator> it_pair = grid.equal_range(cell);
if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
return true;
for (int i = -1; i < 2; ++ i)
for (int j = -1; j < 2; ++ j)
for (int k = -1; k < 1; ++ k) {
if (i == 0 && j == 0 && k == 0)
continue;
it_pair = grid.equal_range(cell + Vec3i(i, j, k));
if (collides_with(pos3d, radius, it_pair.first, it_pair.second))
return true;
}
return false;
}
private:
bool collides_with(const Vec3f &pos, float radius, Grid::const_iterator it_begin, Grid::const_iterator it_end) {
for (Grid::const_iterator it = it_begin; it != it_end; ++ it) {
float dist2 = (it->second.position - pos).squaredNorm();
if (dist2 < radius * radius)
return true;
}
return false;
}
};
private:
std::vector<sla::SupportPoint> m_output;
SLAAutoSupports::Config m_config;
float m_supports_force_total = 0.f;
void process(const std::vector<ExPolygons>& slices, const std::vector<float>& heights);
void uniformly_cover(const ExPolygons& islands, Structure& structure, PointGrid3D &grid3d, bool is_new_island = false, bool just_one = false);
void project_onto_mesh(std::vector<sla::SupportPoint>& points) const;
#ifdef SLA_AUTOSUPPORTS_DEBUG
static void output_expolygons(const ExPolygons& expolys, const std::string &filename);
static void output_structures(const std::vector<Structure> &structures);
#endif // SLA_AUTOSUPPORTS_DEBUG
std::function<void(void)> m_throw_on_cancel;
const Eigen::MatrixXd& m_V;
const Eigen::MatrixXi& m_F;
const sla::EigenMesh3D& m_emesh;
};

View file

@ -4,78 +4,177 @@
#include "boost/log/trivial.hpp"
#include "SLABoostAdapter.hpp"
#include "ClipperUtils.hpp"
#include "Tesselate.hpp"
// For debugging:
//#include <fstream>
//#include <libnest2d/tools/benchmark.h>
//#include "SVG.hpp"
//#include "benchmark.h"
namespace Slic3r { namespace sla {
/// Convert the triangulation output to an intermediate mesh.
Contour3D convert(const Polygons& triangles, coord_t z, bool dir) {
Pointf3s points;
points.reserve(3*triangles.size());
Indices indices;
indices.reserve(points.size());
for(auto& tr : triangles) {
auto c = coord_t(points.size()), b = c++, a = c++;
if(dir) indices.emplace_back(a, b, c);
else indices.emplace_back(c, b, a);
for(auto& p : tr.points) {
points.emplace_back(unscale(x(p), y(p), z));
}
}
return {points, indices};
}
Contour3D walls(const ExPolygon& floor_plate, const ExPolygon& ceiling,
double floor_z_mm, double ceiling_z_mm,
ThrowOnCancel thr)
/// This function will return a triangulation of a sheet connecting an upper
/// and a lower plate given as input polygons. It will not triangulate the
/// plates themselves only the sheet. The caller has to specify the lower and
/// upper z levels in world coordinates as well as the offset difference
/// between the sheets. If the lower_z_mm is higher than upper_z_mm or the
/// offset difference is negative, the resulting triangle orientation will be
/// reversed.
///
/// IMPORTANT: This is not a universal triangulation algorithm. It assumes
/// that the lower and upper polygons are offsetted versions of the same
/// original polygon. In general, it assumes that one of the polygons is
/// completely inside the other. The offset difference is the reference
/// distance from the inner polygon's perimeter to the outer polygon's
/// perimeter. The real distance will be variable as the clipper offset has
/// different strategies (rounding, etc...). This algorithm should have
/// O(2n + 3m) complexity where n is the number of upper vertices and m is the
/// number of lower vertices.
Contour3D walls(const Polygon& lower, const Polygon& upper,
double lower_z_mm, double upper_z_mm,
double offset_difference_mm, ThrowOnCancel thr)
{
using std::transform; using std::back_inserter;
ExPolygon poly;
poly.contour.points = floor_plate.contour.points;
poly.holes.emplace_back(ceiling.contour);
auto& h = poly.holes.front();
std::reverse(h.points.begin(), h.points.end());
Polygons tri = triangulate(poly);
Contour3D ret;
ret.points.reserve(tri.size() * 3);
double fz = floor_z_mm;
double cz = ceiling_z_mm;
auto& rp = ret.points;
auto& rpi = ret.indices;
ret.indices.reserve(tri.size() * 3);
if(upper.points.size() < 3 || lower.size() < 3) return ret;
coord_t idx = 0;
// The concept of the algorithm is relatively simple. It will try to find
// the closest vertices from the upper and the lower polygon and use those
// as starting points. Then it will create the triangles sequentially using
// an edge from the upper polygon and a vertex from the lower or vice versa,
// depending on the resulting triangle's quality.
// The quality is measured by a scalar value. So far it looks like it is
// enough to derive it from the slope of the triangle's two edges connecting
// the upper and the lower part. A reference slope is calculated from the
// height and the offset difference.
auto hlines = h.lines();
auto is_upper = [&hlines](const Point& p) {
return std::any_of(hlines.begin(), hlines.end(),
[&p](const Line& l) {
return l.distance_to(p) < mm(1e-6);
});
// Offset in the index array for the ceiling
const auto offs = upper.points.size();
// Shorthand for the vertex arrays
auto& upoints = upper.points, &lpoints = lower.points;
auto& rpts = ret.points; auto& rfaces = ret.indices;
// If the Z levels are flipped, or the offset difference is negative, we
// will interpret that as the triangles normals should be inverted.
bool inverted = upper_z_mm < lower_z_mm || offset_difference_mm < 0;
// Copy the points into the mesh, convert them from 2D to 3D
rpts.reserve(upoints.size() + lpoints.size());
rfaces.reserve(2*upoints.size() + 2*lpoints.size());
const double sf = SCALING_FACTOR;
for(auto& p : upoints) rpts.emplace_back(p.x()*sf, p.y()*sf, upper_z_mm);
for(auto& p : lpoints) rpts.emplace_back(p.x()*sf, p.y()*sf, lower_z_mm);
// Create pointing indices into vertex arrays. u-upper, l-lower
size_t uidx = 0, lidx = offs, unextidx = 1, lnextidx = offs + 1;
// Simple squared distance calculation.
auto distfn = [](const Vec3d& p1, const Vec3d& p2) {
auto p = p1 - p2; return p.transpose() * p;
};
std::for_each(tri.begin(), tri.end(),
[&rp, &rpi, thr, &idx, is_upper, fz, cz](const Polygon& pp)
{
thr(); // may throw if cancellation was requested
// We need to find the closest point on lower polygon to the first point on
// the upper polygon. These will be our starting points.
double distmin = std::numeric_limits<double>::max();
for(size_t l = lidx; l < rpts.size(); ++l) {
thr();
double d = distfn(rpts[l], rpts[uidx]);
if(d < distmin) { lidx = l; distmin = d; }
}
for(auto& p : pp.points)
if(is_upper(p))
rp.emplace_back(unscale(x(p), y(p), mm(cz)));
else rp.emplace_back(unscale(x(p), y(p), mm(fz)));
// Set up lnextidx to be ahead of lidx in cyclic mode
lnextidx = lidx + 1;
if(lnextidx == rpts.size()) lnextidx = offs;
coord_t a = idx++, b = idx++, c = idx++;
if(fz > cz) rpi.emplace_back(c, b, a);
else rpi.emplace_back(a, b, c);
});
// This will be the flip switch to toggle between upper and lower triangle
// creation mode
enum class Proceed {
UPPER, // A segment from the upper polygon and one vertex from the lower
LOWER // A segment from the lower polygon and one vertex from the upper
} proceed = Proceed::UPPER;
// Flags to help evaluating loop termination.
bool ustarted = false, lstarted = false;
// The variables for the fitness values, one for the actual and one for the
// previous.
double current_fit = 0, prev_fit = 0;
// Every triangle of the wall has two edges connecting the upper plate with
// the lower plate. From the length of these two edges and the zdiff we
// can calculate the momentary squared offset distance at a particular
// position on the wall. The average of the differences from the reference
// (squared) offset distance will give us the driving fitness value.
const double offsdiff2 = std::pow(offset_difference_mm, 2);
const double zdiff2 = std::pow(upper_z_mm - lower_z_mm, 2);
// Mark the current vertex iterator positions. If the iterators return to
// the same position, the loop can be terminated.
size_t uendidx = uidx, lendidx = lidx;
do { thr(); // check throw if canceled
prev_fit = current_fit;
switch(proceed) { // proceed depending on the current state
case Proceed::UPPER:
if(!ustarted || uidx != uendidx) { // there are vertices remaining
// Get the 3D vertices in order
const Vec3d& p_up1 = rpts[size_t(uidx)];
const Vec3d& p_low = rpts[size_t(lidx)];
const Vec3d& p_up2 = rpts[size_t(unextidx)];
// Calculate fitness: the average of the two connecting edges
double a = offsdiff2 - (distfn(p_up1, p_low) - zdiff2);
double b = offsdiff2 - (distfn(p_up2, p_low) - zdiff2);
current_fit = (std::abs(a) + std::abs(b)) / 2;
if(current_fit > prev_fit) { // fit is worse than previously
proceed = Proceed::LOWER;
} else { // good to go, create the triangle
inverted? rfaces.emplace_back(unextidx, lidx, uidx) :
rfaces.emplace_back(uidx, lidx, unextidx) ;
// Increment the iterators, rotate if necessary
++uidx; ++unextidx;
if(unextidx == offs) unextidx = 0;
if(uidx == offs) uidx = 0;
ustarted = true; // mark the movement of the iterators
// so that the comparison to uendidx can be made correctly
}
} else proceed = Proceed::LOWER;
break;
case Proceed::LOWER:
// Mode with lower segment, upper vertex. Same structure:
if(!lstarted || lidx != lendidx) {
const Vec3d& p_low1 = rpts[size_t(lidx)];
const Vec3d& p_low2 = rpts[size_t(lnextidx)];
const Vec3d& p_up = rpts[size_t(uidx)];
double a = offsdiff2 - (distfn(p_up, p_low1) - zdiff2);
double b = offsdiff2 - (distfn(p_up, p_low2) - zdiff2);
current_fit = (std::abs(a) + std::abs(b)) / 2;
if(current_fit > prev_fit) {
proceed = Proceed::UPPER;
} else {
inverted? rfaces.emplace_back(uidx, lnextidx, lidx) :
rfaces.emplace_back(lidx, lnextidx, uidx);
++lidx; ++lnextidx;
if(lnextidx == rpts.size()) lnextidx = offs;
if(lidx == rpts.size()) lidx = offs;
lstarted = true;
}
} else proceed = Proceed::UPPER;
break;
} // end of switch
} while(!ustarted || !lstarted || uidx != uendidx || lidx != lendidx);
return ret;
}
@ -207,20 +306,31 @@ ExPolygons unify(const ExPolygons& shapes) {
/// Only a debug function to generate top and bottom plates from a 2D shape.
/// It is not used in the algorithm directly.
inline Contour3D roofs(const ExPolygon& poly, coord_t z_distance) {
Polygons triangles = triangulate(poly);
auto lower = convert(triangles, 0, false);
auto upper = convert(triangles, z_distance, true);
lower.merge(upper);
return lower;
auto lower = triangulate_expolygon_3d(poly);
auto upper = triangulate_expolygon_3d(poly, z_distance*SCALING_FACTOR, true);
Contour3D ret;
ret.merge(lower); ret.merge(upper);
return ret;
}
/// This method will create a rounded edge around a flat polygon in 3d space.
/// 'base_plate' parameter is the target plate.
/// 'radius' is the radius of the edges.
/// 'degrees' is tells how much of a circle should be created as the rounding.
/// It should be in degrees, not radians.
/// 'ceilheight_mm' is the Z coordinate of the flat polygon in 3D space.
/// 'dir' Is the direction of the round edges: inward or outward
/// 'thr' Throws if a cancel signal was received
/// 'last_offset' An auxiliary output variable to save the last offsetted
/// version of 'base_plate'
/// 'last_height' An auxiliary output to save the last z coordinate of the
/// offsetted base_plate. In other words, where the rounded edges end.
Contour3D round_edges(const ExPolygon& base_plate,
double radius_mm,
double degrees,
double ceilheight_mm,
bool dir,
ThrowOnCancel throw_on_cancel,
ThrowOnCancel thr,
ExPolygon& last_offset, double& last_height)
{
auto ob = base_plate;
@ -236,10 +346,10 @@ Contour3D round_edges(const ExPolygon& base_plate,
// we use sin for x distance because we interpret the angle starting from
// PI/2
int tos = degrees < 90?
int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps;
int(radius_mm*std::cos(degrees * PI / 180 - PI/2) / stepx) : steps;
for(int i = 1; i <= tos; ++i) {
throw_on_cancel();
thr();
ob = base_plate;
@ -252,7 +362,8 @@ Contour3D round_edges(const ExPolygon& base_plate,
wh = ceilheight_mm - radius_mm + stepy;
Contour3D pwalls;
pwalls = walls(ob, ob_prev, wh, wh_prev, throw_on_cancel);
double prev_x = xx - (i - 1) * stepx;
pwalls = walls(ob.contour, ob_prev.contour, wh, wh_prev, s*prev_x, thr);
curvedwalls.merge(pwalls);
ob_prev = ob;
@ -264,7 +375,7 @@ Contour3D round_edges(const ExPolygon& base_plate,
int tos = int(tox / stepx);
for(int i = 1; i <= tos; ++i) {
throw_on_cancel();
thr();
ob = base_plate;
double r2 = radius_mm * radius_mm;
@ -275,7 +386,9 @@ Contour3D round_edges(const ExPolygon& base_plate,
wh = ceilheight_mm - radius_mm - stepy;
Contour3D pwalls;
pwalls = walls(ob_prev, ob, wh_prev, wh, throw_on_cancel);
double prev_x = xx - radius_mm + (i - 1)*stepx;
pwalls =
walls(ob_prev.contour, ob.contour, wh_prev, wh, s*prev_x, thr);
curvedwalls.merge(pwalls);
ob_prev = ob;
@ -291,15 +404,17 @@ Contour3D round_edges(const ExPolygon& base_plate,
/// Generating the concave part of the 3D pool with the bottom plate and the
/// side walls.
Contour3D inner_bed(const ExPolygon& poly, double depth_mm,
double begin_h_mm = 0) {
Polygons triangles = triangulate(poly);
Contour3D inner_bed(const ExPolygon& poly,
double depth_mm,
double begin_h_mm = 0)
{
Contour3D bottom;
Pointf3s triangles = triangulate_expolygon_3d(poly, -depth_mm + begin_h_mm);
bottom.merge(triangles);
coord_t depth = mm(depth_mm);
coord_t begin_h = mm(begin_h_mm);
auto bottom = convert(triangles, -depth + begin_h, false);
auto lines = poly.lines();
// Generate outer walls
@ -469,6 +584,9 @@ void base_plate(const TriangleMesh &mesh, ExPolygons &output, float h,
void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
const PoolConfig& cfg)
{
// for debugging:
// Benchmark bench;
// bench.start();
double mergedist = 2*(1.8*cfg.min_wall_thickness_mm + 4*cfg.edge_radius_mm)+
cfg.max_merge_distance_mm;
@ -478,27 +596,28 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
// serve as the bottom plate of the pad. We will offset this concave hull
// and then offset back the result with clipper with rounding edges ON. This
// trick will create a nice rounded pad shape.
auto concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel);
ExPolygons concavehs = concave_hull(ground_layer, mergedist, cfg.throw_on_cancel);
const double thickness = cfg.min_wall_thickness_mm;
const double wingheight = cfg.min_wall_height_mm;
const double fullheight = wingheight + thickness;
const double tilt = PI/4;
const double tilt = cfg.wall_tilt;
const double wingdist = wingheight / std::tan(tilt);
// scaled values
const coord_t s_thickness = mm(thickness);
const coord_t s_eradius = mm(cfg.edge_radius_mm);
const coord_t s_safety_dist = 2*s_eradius + coord_t(0.8*s_thickness);
// const coord_t wheight = mm(cfg.min_wall_height_mm);
coord_t s_wingdist = mm(wingdist);
const coord_t s_wingdist = mm(wingdist);
auto& thrcl = cfg.throw_on_cancel;
Contour3D pool;
for(ExPolygon& concaveh : concavehs) {
if(concaveh.contour.points.empty()) return;
// Get rif of any holes in the concave hull output.
// Get rid of any holes in the concave hull output.
concaveh.holes.clear();
// Here lies the trick that does the smooting only with clipper offset
@ -508,15 +627,22 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
auto outer_base = concaveh;
outer_base.holes.clear();
offset(outer_base, s_safety_dist + s_wingdist + s_thickness);
auto inner_base = outer_base;
offset(inner_base, -(s_thickness + s_wingdist));
ExPolygon bottom_poly = outer_base;
bottom_poly.holes.clear();
if(s_wingdist > 0) offset(bottom_poly, -s_wingdist);
// Punching a hole in the top plate for the cavity
ExPolygon top_poly;
ExPolygon middle_base;
ExPolygon inner_base;
top_poly.contour = outer_base.contour;
if(wingheight > 0) {
inner_base = outer_base;
offset(inner_base, -(s_thickness + s_wingdist + s_eradius));
middle_base = outer_base;
offset(middle_base, -s_thickness);
top_poly.holes.emplace_back(middle_base.contour);
@ -524,8 +650,6 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
std::reverse(tph.begin(), tph.end());
}
Contour3D pool;
ExPolygon ob = outer_base; double wh = 0;
// now we will calculate the angle or portion of the circle from
@ -557,60 +681,53 @@ void create_base_pool(const ExPolygons &ground_layer, TriangleMesh& out,
// Generate the smoothed edge geometry
auto walledges = round_edges(ob,
r,
phi,
0, // z position of the input plane
true,
thrcl,
ob, wh);
pool.merge(walledges);
pool.merge(round_edges(ob,
r,
phi,
0, // z position of the input plane
true,
thrcl,
ob, wh));
// Now that we have the rounded edge connencting the top plate with
// Now that we have the rounded edge connecting the top plate with
// the outer side walls, we can generate and merge the sidewall geometry
auto pwalls = walls(ob, inner_base, wh, -fullheight, thrcl);
pool.merge(pwalls);
pool.merge(walls(ob.contour, bottom_poly.contour, wh, -fullheight,
wingdist, thrcl));
if(wingheight > 0) {
// Generate the smoothed edge geometry
auto cavityedges = round_edges(middle_base,
r,
phi - 90, // from tangent lines
0,
false,
thrcl,
ob, wh);
pool.merge(cavityedges);
pool.merge(round_edges(middle_base,
r,
phi - 90, // from tangent lines
0, // z position of the input plane
false,
thrcl,
ob, wh));
// Next is the cavity walls connecting to the top plate's
// artificially created hole.
auto cavitywalls = walls(inner_base, ob, -wingheight, wh, thrcl);
pool.merge(cavitywalls);
pool.merge(walls(inner_base.contour, ob.contour, -wingheight,
wh, -wingdist, thrcl));
}
// Now we need to triangulate the top and bottom plates as well as the
// cavity bottom plate which is the same as the bottom plate but it is
// eleveted by the thickness.
Polygons top_triangles, bottom_triangles;
// elevated by the thickness.
pool.merge(triangulate_expolygon_3d(top_poly));
pool.merge(triangulate_expolygon_3d(bottom_poly, -fullheight, true));
triangulate(top_poly, top_triangles);
triangulate(inner_base, bottom_triangles);
if(wingheight > 0)
pool.merge(triangulate_expolygon_3d(inner_base, -wingheight));
auto top_plate = convert(top_triangles, 0, false);
auto bottom_plate = convert(bottom_triangles, -mm(fullheight), true);
pool.merge(top_plate);
pool.merge(bottom_plate);
if(wingheight > 0) {
Polygons middle_triangles;
triangulate(inner_base, middle_triangles);
auto middle_plate = convert(middle_triangles, -mm(wingheight), false);
pool.merge(middle_plate);
}
out.merge(mesh(pool));
}
// For debugging:
// bench.stop();
// std::cout << "Pad creation time: " << bench.getElapsedSec() << std::endl;
// std::fstream fout("pad_debug.obj", std::fstream::out);
// if(fout.good()) pool.to_obj(fout);
out.merge(mesh(pool));
}
}

View file

@ -3,6 +3,7 @@
#include <vector>
#include <functional>
#include <cmath>
namespace Slic3r {
@ -27,15 +28,17 @@ struct PoolConfig {
double min_wall_height_mm = 5;
double max_merge_distance_mm = 50;
double edge_radius_mm = 1;
double wall_tilt = std::atan(1.0); // Universal constant for Pi/4
ThrowOnCancel throw_on_cancel = [](){};
inline PoolConfig() {}
inline PoolConfig(double wt, double wh, double md, double er):
inline PoolConfig(double wt, double wh, double md, double er, double tilt):
min_wall_thickness_mm(wt),
min_wall_height_mm(wh),
max_merge_distance_mm(md),
edge_radius_mm(er) {}
edge_radius_mm(er),
wall_tilt(tilt) {}
};
/// Calculate the pool for the mesh for SLA printing

View file

@ -36,14 +36,6 @@ inline coord_t x(const Vec3crd& p) { return p(0); }
inline coord_t y(const Vec3crd& p) { return p(1); }
inline coord_t z(const Vec3crd& p) { return p(2); }
inline void triangulate(const ExPolygon& expoly, Polygons& triangles) {
expoly.triangulate_p2t(&triangles);
}
inline Polygons triangulate(const ExPolygon& expoly) {
Polygons tri; triangulate(expoly, tri); return tri;
}
using Indices = std::vector<Vec3crd>;
/// Intermediate struct for a 3D mesh
@ -63,6 +55,15 @@ struct Contour3D {
}
}
void merge(const Pointf3s& triangles) {
const size_t offs = points.size();
points.insert(points.end(), triangles.begin(), triangles.end());
indices.reserve(indices.size() + points.size() / 3);
for(int i = (int)offs; i < (int)points.size(); i += 3)
indices.emplace_back(i, i + 1, i + 2);
}
// Write the index triangle structure to OBJ file for debugging purposes.
void to_obj(std::ostream& stream) {
for(auto& p : points) {
@ -75,13 +76,9 @@ struct Contour3D {
}
};
//using PointSet = Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::DontAlign>; //Eigen::MatrixXd;
using ClusterEl = std::vector<unsigned>;
using ClusteredPoints = std::vector<ClusterEl>;
/// Convert the triangulation output to an intermediate mesh.
Contour3D convert(const Polygons& triangles, coord_t z, bool dir);
/// Mesh from an existing contour.
inline TriangleMesh mesh(const Contour3D& ctour) {
return {ctour.points, ctour.indices};

View file

@ -0,0 +1,137 @@
#ifndef SLACOMMON_HPP
#define SLACOMMON_HPP
#include <Eigen/Geometry>
// #define SLIC3R_SLA_NEEDS_WINDTREE
namespace Slic3r {
// Typedefs from Point.hpp
typedef Eigen::Matrix<float, 3, 1, Eigen::DontAlign> Vec3f;
typedef Eigen::Matrix<double, 3, 1, Eigen::DontAlign> Vec3d;
class TriangleMesh;
namespace sla {
struct SupportPoint {
Vec3f pos;
float head_front_radius;
bool is_new_island;
SupportPoint() :
pos(Vec3f::Zero()), head_front_radius(0.f), is_new_island(false) {}
SupportPoint(float pos_x, float pos_y, float pos_z, float head_radius, bool new_island) :
pos(pos_x, pos_y, pos_z), head_front_radius(head_radius), is_new_island(new_island) {}
SupportPoint(Vec3f position, float head_radius, bool new_island) :
pos(position), head_front_radius(head_radius), is_new_island(new_island) {}
SupportPoint(Eigen::Matrix<float, 5, 1, Eigen::DontAlign> data) :
pos(data(0), data(1), data(2)), head_front_radius(data(3)), is_new_island(data(4) != 0.f) {}
bool operator==(const SupportPoint& sp) const { return (pos==sp.pos) && head_front_radius==sp.head_front_radius && is_new_island==sp.is_new_island; }
bool operator!=(const SupportPoint& sp) const { return !(sp == (*this)); }
};
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree
/*struct EigenMesh3D {
Eigen::MatrixXd V;
Eigen::MatrixXi F;
double ground_level = 0;
};*/
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree
class EigenMesh3D {
class AABBImpl;
Eigen::MatrixXd m_V;
Eigen::MatrixXi m_F;
double m_ground_level = 0;
std::unique_ptr<AABBImpl> m_aabb;
public:
EigenMesh3D(const TriangleMesh&);
EigenMesh3D(const EigenMesh3D& other);
EigenMesh3D& operator=(const EigenMesh3D&);
~EigenMesh3D();
inline double ground_level() const { return m_ground_level; }
inline const Eigen::MatrixXd& V() const { return m_V; }
inline const Eigen::MatrixXi& F() const { return m_F; }
// Result of a raycast
class hit_result {
double m_t = std::numeric_limits<double>::infinity();
int m_face_id = -1;
const EigenMesh3D& m_mesh;
Vec3d m_dir;
inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
friend class EigenMesh3D;
public:
inline double distance() const { return m_t; }
inline const Vec3d& direction() const { return m_dir; }
inline int face() const { return m_face_id; }
inline Vec3d normal() const {
if(m_face_id < 0) return {};
auto trindex = m_mesh.m_F.row(m_face_id);
const Vec3d& p1 = m_mesh.V().row(trindex(0));
const Vec3d& p2 = m_mesh.V().row(trindex(1));
const Vec3d& p3 = m_mesh.V().row(trindex(2));
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
return U.cross(V).normalized();
}
inline bool is_inside() {
return m_face_id >= 0 && normal().dot(m_dir) > 0;
}
};
// Casting a ray on the mesh, returns the distance where the hit occures.
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
class si_result {
double m_value;
int m_fidx;
Vec3d m_p;
si_result(double val, int i, const Vec3d& c):
m_value(val), m_fidx(i), m_p(c) {}
friend class EigenMesh3D;
public:
si_result() = delete;
double value() const { return m_value; }
operator double() const { return m_value; }
const Vec3d& point_on_mesh() const { return m_p; }
int F_idx() const { return m_fidx; }
};
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
// The signed distance from a point to the mesh. Outputs the distance,
// the index of the triangle and the closest point in mesh coordinate space.
si_result signed_distance(const Vec3d& p) const;
bool inside(const Vec3d& p) const;
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
};
} // namespace sla
} // namespace Slic3r
#endif // SLASUPPORTTREE_HPP

View file

@ -551,10 +551,16 @@ enum { // For indexing Eigen vectors as v(X), v(Y), v(Z) instead of numbers
X, Y, Z
};
PointSet to_point_set(const std::vector<Vec3d> &v)
PointSet to_point_set(const std::vector<SupportPoint> &v)
{
PointSet ret(v.size(), 3);
{ long i = 0; for(const Vec3d& p : v) ret.row(i++) = p; }
long i = 0;
for(const SupportPoint& support_point : v) {
ret.row(i)(0) = support_point.pos(0);
ret.row(i)(1) = support_point.pos(1);
ret.row(i)(2) = support_point.pos(2);
++i;
}
return ret;
}
@ -679,6 +685,7 @@ double pinhead_mesh_intersect(const Vec3d& s,
return *mit;
}
// Checking bridge (pillar and stick as well) intersection with the model. If
// the function is used for headless sticks, the ins_check parameter have to be
// true as the beginning of the stick might be inside the model geometry.

View file

@ -7,6 +7,9 @@
#include <memory>
#include <Eigen/Geometry>
#include "SLACommon.hpp"
namespace Slic3r {
// Needed types from Point.hpp
@ -105,86 +108,6 @@ struct Controller {
std::function<void(void)> cancelfn = [](){};
};
/// An index-triangle structure for libIGL functions. Also serves as an
/// alternative (raw) input format for the SLASupportTree
class EigenMesh3D {
class AABBImpl;
Eigen::MatrixXd m_V;
Eigen::MatrixXi m_F;
double m_ground_level = 0;
std::unique_ptr<AABBImpl> m_aabb;
public:
EigenMesh3D(const TriangleMesh&);
EigenMesh3D(const EigenMesh3D& other);
EigenMesh3D& operator=(const EigenMesh3D&);
~EigenMesh3D();
inline double ground_level() const { return m_ground_level; }
inline const Eigen::MatrixXd& V() const { return m_V; }
inline const Eigen::MatrixXi& F() const { return m_F; }
// Result of a raycast
class hit_result {
double m_t = std::numeric_limits<double>::infinity();
int m_face_id = -1;
const EigenMesh3D& m_mesh;
Vec3d m_dir;
inline hit_result(const EigenMesh3D& em): m_mesh(em) {}
friend class EigenMesh3D;
public:
inline double distance() const { return m_t; }
inline int face() const { return m_face_id; }
inline Vec3d normal() const {
if(m_face_id < 0) return {};
auto trindex = m_mesh.m_F.row(m_face_id);
const Vec3d& p1 = m_mesh.V().row(trindex(0));
const Vec3d& p2 = m_mesh.V().row(trindex(1));
const Vec3d& p3 = m_mesh.V().row(trindex(2));
Eigen::Vector3d U = p2 - p1;
Eigen::Vector3d V = p3 - p1;
return U.cross(V).normalized();
}
inline bool is_inside() {
return m_face_id >= 0 && normal().dot(m_dir) > 0;
}
};
// Casting a ray on the mesh, returns the distance where the hit occures.
hit_result query_ray_hit(const Vec3d &s, const Vec3d &dir) const;
class si_result {
double m_value;
int m_fidx;
Vec3d m_p;
si_result(double val, int i, const Vec3d& c):
m_value(val), m_fidx(i), m_p(c) {}
friend class EigenMesh3D;
public:
si_result() = delete;
double value() const { return m_value; }
operator double() const { return m_value; }
const Vec3d& point_on_mesh() const { return m_p; }
int F_idx() const { return m_fidx; }
};
// The signed distance from a point to the mesh. Outputs the distance,
// the index of the triangle and the closest point in mesh coordinate space.
si_result signed_distance(const Vec3d& p) const;
bool inside(const Vec3d& p) const;
};
using PointSet = Eigen::MatrixXd;
//EigenMesh3D to_eigenmesh(const TriangleMesh& m);
@ -193,7 +116,7 @@ using PointSet = Eigen::MatrixXd;
//EigenMesh3D to_eigenmesh(const ModelObject& model);
// Simple conversion of 'vector of points' to an Eigen matrix
PointSet to_point_set(const std::vector<Vec3d>&);
PointSet to_point_set(const std::vector<sla::SupportPoint>&);
/* ************************************************************************** */

View file

@ -95,7 +95,9 @@ size_t SpatIndex::size() const
class EigenMesh3D::AABBImpl: public igl::AABB<Eigen::MatrixXd, 3> {
public:
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
igl::WindingNumberAABB<Vec3d, Eigen::MatrixXd, Eigen::MatrixXi> windtree;
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
};
EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
@ -136,7 +138,9 @@ EigenMesh3D::EigenMesh3D(const TriangleMesh& tmesh): m_aabb(new AABBImpl()) {
// Build the AABB accelaration tree
m_aabb->init(m_V, m_F);
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
m_aabb->windtree.set_mesh(m_V, m_F);
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
}
EigenMesh3D::~EigenMesh3D() {}
@ -168,6 +172,7 @@ EigenMesh3D::query_ray_hit(const Vec3d &s, const Vec3d &dir) const
return ret;
}
#ifdef SLIC3R_SLA_NEEDS_WINDTREE
EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
double sign = 0; double sqdst = 0; int i = 0; Vec3d c;
igl::signed_distance_winding_number(*m_aabb, m_V, m_F, m_aabb->windtree,
@ -179,6 +184,7 @@ EigenMesh3D::si_result EigenMesh3D::signed_distance(const Vec3d &p) const {
bool EigenMesh3D::inside(const Vec3d &p) const {
return m_aabb->windtree.inside(p);
}
#endif /* SLIC3R_SLA_NEEDS_WINDTREE */
/* ****************************************************************************
* Misc functions
@ -199,9 +205,11 @@ template<class Vec> double distance(const Vec& pp1, const Vec& pp2) {
return std::sqrt(p.transpose() * p);
}
PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
PointSet normals(const PointSet& points,
const EigenMesh3D& mesh,
double eps,
std::function<void()> throw_on_cancel) {
std::function<void()> throw_on_cancel)
{
if(points.rows() == 0 || mesh.V().rows() == 0 || mesh.F().rows() == 0)
return {};
@ -222,7 +230,7 @@ PointSet normals(const PointSet& points, const EigenMesh3D& mesh,
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 than all the other triangles using the same two points
// 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

View file

@ -2,12 +2,14 @@
#include "SLA/SLASupportTree.hpp"
#include "SLA/SLABasePool.hpp"
#include "SLA/SLAAutoSupports.hpp"
#include "ClipperUtils.hpp"
#include "MTUtils.hpp"
#include <unordered_set>
#include <numeric>
#include <tbb/parallel_for.h>
#include <boost/filesystem/path.hpp>
#include <boost/log/trivial.hpp>
//#include <tbb/spin_mutex.h>//#include "tbb/mutex.h"
@ -25,7 +27,7 @@ using SupportTreePtr = std::unique_ptr<sla::SLASupportTree>;
class SLAPrintObject::SupportData {
public:
sla::EigenMesh3D emesh; // index-triangle representation
sla::PointSet support_points; // all the support points (manual/auto)
std::vector<sla::SupportPoint> support_points; // all the support points (manual/auto)
SupportTreePtr support_tree_ptr; // the supports
SlicedSupports support_slices; // sliced supports
std::vector<LevelID> level_ids;
@ -51,7 +53,7 @@ const std::array<std::string, slaposCount> OBJ_STEP_LABELS =
L("Slicing model"), // slaposObjectSlice,
L("Generating support points"), // slaposSupportPoints,
L("Generating support tree"), // slaposSupportTree,
L("Generating base pool"), // slaposBasePool,
L("Generating pad"), // slaposBasePool,
L("Slicing supports"), // slaposSliceSupports,
L("Slicing supports") // slaposIndexSlices,
};
@ -182,7 +184,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
if (model.id() != m_model.id()) {
// Kill everything, initialize from scratch.
// Stop background processing.
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(this->invalidate_all_steps());
for (SLAPrintObject *object : m_objects) {
model_object_status.emplace(object->model_object()->id(), ModelObjectStatus::Deleted);
@ -211,7 +213,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
} else {
// Reorder the objects, add new objects.
// First stop background processing before shuffling or deleting the PrintObjects in the object list.
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(this->invalidate_step(slapsRasterize));
// Second create a new list of objects.
std::vector<ModelObject*> model_objects_old(std::move(m_model.objects));
@ -308,7 +310,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
if (it_print_object_status != print_object_status.end() && it_print_object_status->id != model_object.id())
it_print_object_status = print_object_status.end();
// Check whether a model part volume was added or removed, their transformations or order changed.
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolume::MODEL_PART);
bool model_parts_differ = model_volume_list_changed(model_object, model_object_new, ModelVolumeType::MODEL_PART);
bool sla_trafo_differs = model_object.instances.empty() != model_object_new.instances.empty() ||
(! model_object.instances.empty() && ! sla_trafo(model_object).isApprox(sla_trafo(model_object_new)));
if (model_parts_differ || sla_trafo_differs) {
@ -354,14 +356,18 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
std::vector<SLAPrintObject::Instance> new_instances = sla_instances(model_object);
if (it_print_object_status != print_object_status.end() && it_print_object_status->status != PrintObjectStatus::Deleted) {
// The SLAPrintObject is already there.
if (new_instances != it_print_object_status->print_object->instances()) {
// Instances changed.
it_print_object_status->print_object->set_instances(new_instances);
update_apply_status(this->invalidate_step(slapsRasterize));
}
print_objects_new.emplace_back(it_print_object_status->print_object);
const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused;
} else {
if (new_instances.empty()) {
const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Deleted;
} else {
if (new_instances != it_print_object_status->print_object->instances()) {
// Instances changed.
it_print_object_status->print_object->set_instances(new_instances);
update_apply_status(this->invalidate_step(slapsRasterize));
}
print_objects_new.emplace_back(it_print_object_status->print_object);
const_cast<PrintObjectStatus&>(*it_print_object_status).status = PrintObjectStatus::Reused;
}
} else if (! new_instances.empty()) {
auto print_object = new SLAPrintObject(this, &model_object);
// FIXME: this invalidates the transformed mesh in SLAPrintObject
@ -376,7 +382,7 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
}
if (m_objects != print_objects_new) {
this->call_cancell_callback();
this->call_cancel_callback();
update_apply_status(this->invalidate_all_steps());
m_objects = print_objects_new;
// Delete the PrintObjects marked as Unknown or Deleted.
@ -398,6 +404,113 @@ SLAPrint::ApplyStatus SLAPrint::apply(const Model &model, const DynamicPrintConf
return static_cast<ApplyStatus>(apply_status);
}
// After calling the apply() function, set_task() may be called to limit the task to be processed by process().
void SLAPrint::set_task(const TaskParams &params)
{
// Grab the lock for the Print / PrintObject milestones.
tbb::mutex::scoped_lock lock(this->state_mutex());
int n_object_steps = int(params.to_object_step) + 1;
if (n_object_steps == 0)
n_object_steps = (int)slaposCount;
if (params.single_model_object.valid()) {
// Find the print object to be processed with priority.
SLAPrintObject *print_object = nullptr;
size_t idx_print_object = 0;
for (; idx_print_object < m_objects.size(); ++ idx_print_object)
if (m_objects[idx_print_object]->model_object()->id() == params.single_model_object) {
print_object = m_objects[idx_print_object];
break;
}
assert(print_object != nullptr);
// Find out whether the priority print object is being currently processed.
bool running = false;
for (int istep = 0; istep < n_object_steps; ++ istep) {
if (! print_object->m_stepmask[istep])
// Step was skipped, cancel.
break;
if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) {
// No step was skipped, and a wanted step is being processed. Don't cancel.
running = true;
break;
}
}
if (! running)
this->call_cancel_callback();
// Now the background process is either stopped, or it is inside one of the print object steps to be calculated anyway.
if (params.single_model_instance_only) {
// Suppress all the steps of other instances.
for (SLAPrintObject *po : m_objects)
for (int istep = 0; istep < (int)slaposCount; ++ istep)
po->m_stepmask[istep] = false;
} else if (! running) {
// Swap the print objects, so that the selected print_object is first in the row.
// At this point the background processing must be stopped, so it is safe to shuffle print objects.
if (idx_print_object != 0)
std::swap(m_objects.front(), m_objects[idx_print_object]);
}
// and set the steps for the current object.
for (int istep = 0; istep < n_object_steps; ++ istep)
print_object->m_stepmask[istep] = true;
for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep)
print_object->m_stepmask[istep] = false;
} else {
// Slicing all objects.
bool running = false;
for (SLAPrintObject *print_object : m_objects)
for (int istep = 0; istep < n_object_steps; ++ istep) {
if (! print_object->m_stepmask[istep]) {
// Step may have been skipped. Restart.
goto loop_end;
}
if (print_object->is_step_started_unguarded(SLAPrintObjectStep(istep))) {
// This step is running, and the state cannot be changed due to the this->state_mutex() being locked.
// It is safe to manipulate m_stepmask of other SLAPrintObjects and SLAPrint now.
running = true;
goto loop_end;
}
}
loop_end:
if (! running)
this->call_cancel_callback();
for (SLAPrintObject *po : m_objects) {
for (int istep = 0; istep < n_object_steps; ++ istep)
po->m_stepmask[istep] = true;
for (int istep = n_object_steps; istep < (int)slaposCount; ++ istep)
po->m_stepmask[istep] = false;
}
}
if (params.to_object_step != -1 || params.to_print_step != -1) {
// Limit the print steps.
size_t istep = (params.to_object_step != -1) ? 0 : size_t(params.to_print_step) + 1;
for (; istep < m_stepmask.size(); ++ istep)
m_stepmask[istep] = false;
}
}
// Clean up after process() finished, either with success, error or if canceled.
// The adjustments on the SLAPrint / SLAPrintObject data due to set_task() are to be reverted here.
void SLAPrint::finalize()
{
for (SLAPrintObject *po : m_objects)
for (int istep = 0; istep < (int)slaposCount; ++ istep)
po->m_stepmask[istep] = true;
for (int istep = 0; istep < (int)slapsCount; ++ istep)
m_stepmask[istep] = true;
}
// Generate a recommended output file name based on the format template, default extension, and template parameters
// (timestamps, object placeholders derived from the model, current placeholder prameters and print statistics.
// Use the final print statistics if available, or just keep the print statistics placeholders if not available yet (before the output is finalized).
std::string SLAPrint::output_filename() const
{
DynamicConfig config = this->finished() ? this->print_statistics().config() : this->print_statistics().placeholders();
return this->PrintBase::output_filename(m_print_config.output_filename_format.value, "zip", &config);
}
namespace {
// Compile the argument for support creation from the static print config.
sla::SupportConfig make_support_cfg(const SLAPrintObjectConfig& c) {
@ -472,7 +585,7 @@ void SLAPrint::process()
const size_t objcount = m_objects.size();
const unsigned min_objstatus = 0; // where the per object operations start
const unsigned max_objstatus = 80; // where the per object operations end
const unsigned max_objstatus = PRINT_STEP_LEVELS[slapsRasterize]; // 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
@ -532,9 +645,8 @@ void SLAPrint::process()
this->throw_if_canceled();
SLAAutoSupports::Config config;
const SLAPrintObjectConfig& cfg = po.config();
config.minimal_z = float(cfg.support_minimal_z);
config.density_at_45 = cfg.support_density_at_45 / 10000.f;
config.density_at_horizontal = cfg.support_density_at_horizontal / 10000.f;
config.density_relative = float(cfg.support_points_density_relative / 100.f); // the config value is in percents
config.minimal_distance = float(cfg.support_points_minimal_distance);
// Construction of this object does the calculation.
this->throw_if_canceled();
@ -546,17 +658,19 @@ void SLAPrint::process()
[this]() { throw_if_canceled(); });
// Now let's extract the result.
const std::vector<Vec3d>& points = auto_supports.output();
const std::vector<sla::SupportPoint>& points = auto_supports.output();
this->throw_if_canceled();
po.m_supportdata->support_points = sla::to_point_set(points);
po.m_supportdata->support_points = points;
BOOST_LOG_TRIVIAL(debug) << "Automatic support points: "
<< po.m_supportdata->support_points.rows();
<< po.m_supportdata->support_points.size();
// Using RELOAD_SLA_SUPPORT_POINTS to tell the Plater to pass the update status to GLGizmoSlaSupports
report_status(*this, -1, L("Generating support points"), SlicingStatus::RELOAD_SLA_SUPPORT_POINTS);
}
else {
// There are some points on the front-end, no calculation will be done.
po.m_supportdata->support_points =
sla::to_point_set(po.transformed_support_points());
po.m_supportdata->support_points = po.transformed_support_points();
}
};
@ -587,6 +701,8 @@ void SLAPrint::process()
ctl.statuscb = [this, init, d](unsigned st, const std::string& msg)
{
//FIXME this status line scaling does not seem to be correct.
// How does it account for an increasing object index?
report_status(*this, int(init + st*d), msg);
};
@ -594,7 +710,7 @@ void SLAPrint::process()
ctl.cancelfn = [this]() { throw_if_canceled(); };
po.m_supportdata->support_tree_ptr.reset(
new SLASupportTree(po.m_supportdata->support_points,
new SLASupportTree(sla::to_point_set(po.m_supportdata->support_points),
po.m_supportdata->emesh, scfg, ctl));
// Create the unified mesh
@ -605,7 +721,7 @@ void SLAPrint::process()
po.m_supportdata->support_tree_ptr->merged_mesh();
BOOST_LOG_TRIVIAL(debug) << "Processed support point count "
<< po.m_supportdata->support_points.rows();
<< po.m_supportdata->support_points.size();
// Check the mesh for later troubleshooting.
if(po.support_mesh().empty())
@ -635,11 +751,13 @@ void SLAPrint::process()
double wt = po.m_config.pad_wall_thickness.getFloat();
double h = po.m_config.pad_wall_height.getFloat();
double md = po.m_config.pad_max_merge_distance.getFloat();
double er = po.m_config.pad_edge_radius.getFloat();
// Radius is disabled for now...
double er = 0; // po.m_config.pad_edge_radius.getFloat();
double tilt = po.m_config.pad_wall_tilt.getFloat() * PI / 180.0;
double lh = po.m_config.layer_height.getFloat();
double elevation = po.m_config.support_object_elevation.getFloat();
if(!po.m_config.supports_enable.getBool()) elevation = 0;
sla::PoolConfig pcfg(wt, h, md, er);
sla::PoolConfig pcfg(wt, h, md, er, tilt);
ExPolygons bp;
double pad_h = sla::get_pad_fullheight(pcfg);
@ -650,8 +768,7 @@ void SLAPrint::process()
if(elevation < pad_h) {
// we have to count with the model geometry for the base plate
sla::base_plate(trmesh, bp, float(pad_h), float(lh),
thrfn);
sla::base_plate(trmesh, bp, float(pad_h), float(lh), thrfn);
}
pcfg.throw_on_cancel = thrfn;
@ -878,21 +995,20 @@ void SLAPrint::process()
// Print all the layers in parallel
tbb::parallel_for<unsigned, decltype(lvlfn)>(0, lvlcnt, lvlfn);
// Fill statistics
this->fill_statistics();
// Set statistics values to the printer
m_printer->set_statistics({(m_print_statistics.objects_used_material + m_print_statistics.support_used_material)/1000,
double(m_default_object_config.faded_layers.getInt()),
double(m_print_statistics.slow_layers_count),
double(m_print_statistics.fast_layers_count)
});
};
using slaposFn = std::function<void(SLAPrintObject&)>;
using slapsFn = std::function<void(void)>;
// This is the actual order of steps done on each PrintObject
std::array<SLAPrintObjectStep, slaposCount> objectsteps = {
slaposObjectSlice, // SupportPoints will need this step
slaposSupportPoints,
slaposSupportTree,
slaposBasePool,
slaposSliceSupports,
slaposIndexSlices
};
std::array<slaposFn, slaposCount> pobj_program =
{
slice_model,
@ -916,28 +1032,32 @@ void SLAPrint::process()
// TODO: this loop could run in parallel but should not exhaust all the CPU
// power available
for(SLAPrintObject * po : m_objects) {
// Calculate the support structures first before slicing the supports, so that the preview will get displayed ASAP for all objects.
std::vector<SLAPrintObjectStep> step_ranges = { slaposObjectSlice, slaposSliceSupports, slaposCount };
for (size_t idx_range = 0; idx_range + 1 < step_ranges.size(); ++ idx_range) {
for(SLAPrintObject * po : m_objects) {
BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name;
BOOST_LOG_TRIVIAL(info) << "Slicing object " << po->model_object()->name;
for(size_t s = 0; s < objectsteps.size(); ++s) {
auto currentstep = objectsteps[s];
for (int s = (int)step_ranges[idx_range]; s < (int)step_ranges[idx_range + 1]; ++s) {
auto currentstep = (SLAPrintObjectStep)s;
// Cancellation checking. Each step will check for cancellation
// on its own and return earlier gracefully. Just after it returns
// execution gets to this point and throws the canceled signal.
throw_if_canceled();
st += unsigned(incr * ostepd);
if(po->m_stepmask[currentstep] && po->set_started(currentstep)) {
report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]);
pobj_program[currentstep](*po);
// Cancellation checking. Each step will check for cancellation
// on its own and return earlier gracefully. Just after it returns
// execution gets to this point and throws the canceled signal.
throw_if_canceled();
po->set_done(currentstep);
}
incr = OBJ_STEP_LEVELS[currentstep];
st += unsigned(incr * ostepd);
if(po->m_stepmask[currentstep] && po->set_started(currentstep)) {
report_status(*this, int(st), OBJ_STEP_LABELS[currentstep]);
pobj_program[currentstep](*po);
throw_if_canceled();
po->set_done(currentstep);
}
incr = OBJ_STEP_LEVELS[currentstep];
}
}
}
@ -994,7 +1114,10 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
"bed_shape",
"max_print_height",
"printer_technology",
"output_filename_format"
"output_filename_format",
"fast_tilt_time",
"slow_tilt_time",
"area_fill"
};
std::vector<SLAPrintStep> steps;
@ -1027,6 +1150,166 @@ bool SLAPrint::invalidate_state_by_config_options(const std::vector<t_config_opt
return invalidated;
}
void SLAPrint::fill_statistics()
{
const double init_layer_height = m_material_config.initial_layer_height.getFloat();
const double layer_height = m_default_object_config.layer_height.getFloat();
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 double width = m_printer_config.display_width.getFloat() / SCALING_FACTOR;
const double height = m_printer_config.display_height.getFloat() / SCALING_FACTOR;
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) {
const size_t inst_cnt = instances.size();
size_t polygon_cnt = 0;
for (const ExPolygon& polygon : input_polygons)
polygon_cnt += polygon.holes.size() + 1;
Polygons polygons;
polygons.reserve(polygon_cnt * inst_cnt);
for (const ExPolygon& polygon : input_polygons) {
for (size_t i = 0; i < inst_cnt; ++i)
{
ExPolygon tmp = polygon;
tmp.rotate(Geometry::rad2deg(instances[i].rotation));
tmp.translate(instances[i].shift.x(), instances[i].shift.y());
polygons_append(polygons, to_polygons(std::move(tmp)));
}
}
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;
// find highest object
// Which is a better bet? To compare by max_z or by number of layers in the index?
double max_z = 0.;
size_t max_layers_cnt = 0;
size_t highest_obj_idx = 0;
for (SLAPrintObject *&po : m_objects) {
const SLAPrintObject::SliceIndex& slice_index = po->get_slice_index();
if (! slice_index.empty()) {
double z = (-- slice_index.end())->first;
size_t cnt = slice_index.size();
//if (z > max_z) {
if (cnt > max_layers_cnt) {
max_layers_cnt = cnt;
max_z = z;
highest_obj_idx = &po - &m_objects.front();
}
}
}
const SLAPrintObject * highest_obj = m_objects[highest_obj_idx];
const SLAPrintObject::SliceIndex& highest_obj_slice_index = highest_obj->get_slice_index();
const double delta_fade_time = (init_exp_time - exp_time) / (fade_layers_cnt + 1);
double fade_layer_time = init_exp_time;
int sliced_layer_cnt = 0;
for (const auto& layer : highest_obj_slice_index)
{
const double l_height = (layer.first == highest_obj_slice_index.begin()->first) ? init_layer_height : layer_height;
// Calculation of the consumed material
Polygons model_polygons;
Polygons supports_polygons;
for (SLAPrintObject * po : m_objects)
{
const SLAPrintObject::SliceRecord *record = nullptr;
{
const SLAPrintObject::SliceIndex& index = po->get_slice_index();
auto key = layer.first;
const SLAPrintObject::SliceIndex::const_iterator it_key = index.lower_bound(key - float(EPSILON));
if (it_key == index.end() || it_key->first > key + EPSILON)
continue;
record = &it_key->second;
}
if (record->model_slices_idx != SLAPrintObject::SliceRecord::NONE)
append(model_polygons, get_all_polygons(po->get_model_slices()[record->model_slices_idx], po->instances()));
if (record->support_slices_idx != SLAPrintObject::SliceRecord::NONE)
append(supports_polygons, get_all_polygons(po->get_support_slices()[record->support_slices_idx], po->instances()));
}
model_polygons = union_(model_polygons);
double layer_model_area = 0;
for (const Polygon& polygon : model_polygons)
layer_model_area += polygon.area();
if (layer_model_area != 0)
models_volume += layer_model_area * l_height;
if (!supports_polygons.empty() && !model_polygons.empty())
supports_polygons = diff(supports_polygons, model_polygons);
double layer_support_area = 0;
for (const Polygon& polygon : supports_polygons)
layer_support_area += polygon.area();
if (layer_support_area != 0)
supports_volume += layer_support_area * l_height;
// 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;
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;
sliced_layer_cnt++;
}
m_print_statistics.support_used_material = supports_volume * SCALING_FACTOR * SCALING_FACTOR;
m_print_statistics.objects_used_material = models_volume * SCALING_FACTOR * SCALING_FACTOR;
// Estimated printing time
// A layers count o the highest object
if (max_layers_cnt == 0)
m_print_statistics.estimated_print_time = "N/A";
else
m_print_statistics.estimated_print_time = get_time_dhms(float(estim_time));
m_print_statistics.fast_layers_count = fast_layers;
m_print_statistics.slow_layers_count = slow_layers;
}
// Returns true if an object step is done on all objects and there's at least one object.
bool SLAPrint::is_step_done(SLAPrintObjectStep step) const
{
@ -1034,7 +1317,7 @@ bool SLAPrint::is_step_done(SLAPrintObjectStep step) const
return false;
tbb::mutex::scoped_lock lock(this->state_mutex());
for (const SLAPrintObject *object : m_objects)
if (! object->m_state.is_done_unguarded(step))
if (! object->is_step_done_unguarded(step))
return false;
return true;
}
@ -1060,9 +1343,13 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
std::vector<SLAPrintObjectStep> steps;
bool invalidated = false;
for (const t_config_option_key &opt_key : opt_keys) {
if (opt_key == "layer_height") {
if ( opt_key == "layer_height"
|| opt_key == "faded_layers") {
steps.emplace_back(slaposObjectSlice);
} else if (opt_key == "supports_enable") {
} else if (
opt_key == "supports_enable"
|| opt_key == "support_points_density_relative"
|| opt_key == "support_points_minimal_distance") {
steps.emplace_back(slaposSupportPoints);
} else if (
opt_key == "support_head_front_diameter"
@ -1082,6 +1369,7 @@ bool SLAPrintObject::invalidate_state_by_config_options(const std::vector<t_conf
|| opt_key == "pad_wall_thickness"
|| opt_key == "pad_wall_height"
|| opt_key == "pad_max_merge_distance"
|| opt_key == "pad_wall_tilt"
|| opt_key == "pad_edge_radius") {
steps.emplace_back(slaposBasePool);
} else {
@ -1165,7 +1453,7 @@ const std::vector<ExPolygons> EMPTY_SLICES;
const TriangleMesh EMPTY_MESH;
}
const Eigen::MatrixXd& SLAPrintObject::get_support_points() const
const std::vector<sla::SupportPoint>& SLAPrintObject::get_support_points() const
{
return m_supportdata->support_points;
}
@ -1244,17 +1532,60 @@ const TriangleMesh &SLAPrintObject::transformed_mesh() const {
return m_transformed_rmesh.get();
}
std::vector<Vec3d> SLAPrintObject::transformed_support_points() const
std::vector<sla::SupportPoint> SLAPrintObject::transformed_support_points() const
{
assert(m_model_object != nullptr);
auto& spts = m_model_object->sla_support_points;
std::vector<sla::SupportPoint>& spts = m_model_object->sla_support_points;
// this could be cached as well
std::vector<Vec3d> ret; ret.reserve(spts.size());
std::vector<sla::SupportPoint> ret;
ret.reserve(spts.size());
for(auto& sp : spts) ret.emplace_back( trafo() * Vec3d(sp.cast<double>()));
for(sla::SupportPoint& sp : spts) {
Vec3d transformed_pos = trafo() * Vec3d(sp.pos(0), sp.pos(1), sp.pos(2));
ret.emplace_back(transformed_pos(0), transformed_pos(1), transformed_pos(2), sp.head_front_radius, sp.is_new_island);
}
return ret;
}
DynamicConfig SLAPrintStatistics::config() const
{
DynamicConfig config;
const std::string print_time = Slic3r::short_time(this->estimated_print_time);
config.set_key_value("print_time", new ConfigOptionString(print_time));
config.set_key_value("objects_used_material", new ConfigOptionFloat(this->objects_used_material));
config.set_key_value("support_used_material", new ConfigOptionFloat(this->support_used_material));
config.set_key_value("total_cost", new ConfigOptionFloat(this->total_cost));
config.set_key_value("total_weight", new ConfigOptionFloat(this->total_weight));
return config;
}
DynamicConfig SLAPrintStatistics::placeholders()
{
DynamicConfig config;
for (const std::string &key : {
"print_time", "total_cost", "total_weight",
"objects_used_material", "support_used_material" })
config.set_key_value(key, new ConfigOptionString(std::string("{") + key + "}"));
return config;
}
std::string SLAPrintStatistics::finalize_output_path(const std::string &path_in) const
{
std::string final_path;
try {
boost::filesystem::path path(path_in);
DynamicConfig cfg = this->config();
PlaceholderParser pp;
std::string new_stem = pp.process(path.stem().string(), 0, &cfg);
final_path = (path.parent_path() / (new_stem + path.extension().string())).string();
}
catch (const std::exception &ex) {
BOOST_LOG_TRIVIAL(error) << "Failed to apply the print statistics to the export file name: " << ex.what();
final_path = path_in;
}
return final_path;
}
} // namespace Slic3r

View file

@ -2,7 +2,6 @@
#define slic3r_SLAPrint_hpp_
#include <mutex>
#include "PrintBase.hpp"
#include "PrintExport.hpp"
#include "Point.hpp"
@ -70,7 +69,7 @@ public:
// This will return the transformed mesh which is cached
const TriangleMesh& transformed_mesh() const;
std::vector<Vec3d> transformed_support_points() const;
std::vector<sla::SupportPoint> transformed_support_points() const;
// Get the needed Z elevation for the model geometry if supports should be
// displayed. This Z offset should also be applied to the support
@ -91,7 +90,7 @@ public:
const std::vector<ExPolygons>& get_support_slices() const;
// This method returns the support points of this SLAPrintObject.
const Eigen::MatrixXd& get_support_points() const;
const std::vector<sla::SupportPoint>& get_support_points() const;
// An index record referencing the slices
// (get_model_slices(), get_support_slices()) where the keys are the height
@ -139,6 +138,10 @@ protected:
// Invalidate steps based on a set of parameters changed.
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
// Which steps have to be performed. Implicitly: all
// to be accessible from SLAPrint
std::vector<bool> m_stepmask;
private:
// Object specific configuration, pulled from the configuration layer.
SLAPrintObjectConfig m_config;
@ -146,9 +149,6 @@ private:
Transform3d m_trafo = Transform3d::Identity();
std::vector<Instance> m_instances;
// Which steps have to be performed. Implicitly: all
std::vector<bool> m_stepmask;
// Individual 2d slice polygons from lower z to higher z levels
std::vector<ExPolygons> m_model_slices;
@ -171,6 +171,35 @@ using PrintObjects = std::vector<SLAPrintObject*>;
class TriangleMesh;
struct SLAPrintStatistics
{
SLAPrintStatistics() { clear(); }
std::string estimated_print_time;
double objects_used_material;
double support_used_material;
size_t slow_layers_count;
size_t fast_layers_count;
double total_cost;
double total_weight;
// Config with the filled in print statistics.
DynamicConfig config() const;
// Config with the statistics keys populated with placeholder strings.
static DynamicConfig placeholders();
// Replace the print statistics placeholders in the path.
std::string finalize_output_path(const std::string &path_in) const;
void clear() {
estimated_print_time.clear();
objects_used_material = 0.;
support_used_material = 0.;
slow_layers_count = 0;
fast_layers_count = 0;
total_cost = 0.;
total_weight = 0.;
}
};
/**
* @brief This class is the high level FSM for the SLA printing process.
*
@ -194,7 +223,9 @@ public:
void clear() override;
bool empty() const override { return m_objects.empty(); }
ApplyStatus apply(const Model &model, const DynamicPrintConfig &config) override;
void set_task(const TaskParams &params) override;
void process() override;
void finalize() override;
// Returns true if an object step is done on all objects and there's at least one object.
bool is_step_done(SLAPrintObjectStep step) const;
// Returns true if the last step was finished with success.
@ -205,8 +236,9 @@ public:
}
const PrintObjects& objects() const { return m_objects; }
std::string output_filename() const override
{ return this->PrintBase::output_filename(m_print_config.output_filename_format.value, "zip"); }
std::string output_filename() const override;
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
private:
using SLAPrinter = FilePrinter<FilePrinterFormat::SLA_PNGZIP>;
@ -215,6 +247,8 @@ private:
// Invalidate steps based on a set of parameters changed.
bool invalidate_state_by_config_options(const std::vector<t_config_option_key> &opt_keys);
void fill_statistics();
SLAPrintConfig m_print_config;
SLAPrinterConfig m_printer_config;
SLAMaterialConfig m_material_config;
@ -246,6 +280,9 @@ private:
// The printer itself
SLAPrinterPtr m_printer;
// Estimated print time, material consumed.
SLAPrintStatistics m_print_statistics;
friend SLAPrintObject;
};

View file

@ -42,6 +42,12 @@ Surface::is_internal() const
|| this->surface_type == stInternalVoid;
}
bool
Surface::is_top() const
{
return this->surface_type == stTop;
}
bool
Surface::is_bottom() const
{

View file

@ -98,6 +98,7 @@ public:
bool is_solid() const;
bool is_external() const;
bool is_internal() const;
bool is_top() const;
bool is_bottom() const;
bool is_bridge() const;
};

View file

@ -23,21 +23,10 @@
// Scene's GUI made using imgui library
#define ENABLE_IMGUI (1 && ENABLE_1_42_0_ALPHA1)
#define DISABLE_MOVE_ROTATE_SCALE_GIZMOS_IMGUI (1 && ENABLE_IMGUI)
// Modified Sla support gizmo
#define ENABLE_SLA_SUPPORT_GIZMO_MOD (1 && ENABLE_1_42_0_ALPHA1)
// Use wxDataViewRender instead of wxDataViewCustomRenderer
#define ENABLE_NONCUSTOM_DATA_VIEW_RENDERING (0 && ENABLE_1_42_0_ALPHA1)
//====================
// 1.42.0.alpha2 techs
//====================
#define ENABLE_1_42_0_ALPHA2 1
// Adds print bed models to 3D scene
#define ENABLE_PRINT_BED_MODELS (1 && ENABLE_1_42_0_ALPHA2)
//====================
// 1.42.0.alpha4 techs
//====================
@ -49,10 +38,6 @@
#define ENABLE_MOVE_MIN_THRESHOLD (1 && ENABLE_1_42_0_ALPHA4)
// Modified initial default placement of generic subparts
#define ENABLE_GENERIC_SUBPARTS_PLACEMENT (1 && ENABLE_1_42_0_ALPHA4)
// Reworked management of bed shape changes
#define ENABLE_REWORKED_BED_SHAPE_CHANGE (1 && ENABLE_1_42_0_ALPHA4)
// Use anisotropic filtering on bed plate texture
#define ENABLE_ANISOTROPIC_FILTER_ON_BED_TEXTURES (1 && ENABLE_1_42_0_ALPHA4)
// Bunch of fixes related to volumes centering
#define ENABLE_VOLUMES_CENTERING_FIXES (1 && ENABLE_1_42_0_ALPHA4)
@ -65,4 +50,13 @@
// Toolbar items hidden/shown in dependence of the user mode
#define ENABLE_MODE_AWARE_TOOLBAR_ITEMS (1 && ENABLE_1_42_0_ALPHA5)
//====================
// 1.42.0.alpha7 techs
//====================
#define ENABLE_1_42_0_ALPHA7 1
// Printbed textures generated from svg files
#define ENABLE_TEXTURES_FROM_SVG (1 && ENABLE_1_42_0_ALPHA7)
#endif // _technologies_h_

View file

@ -20,7 +20,7 @@ public:
gluDeleteTess(m_tesselator);
}
Pointf3s tesselate(const ExPolygon &expoly, double z_, bool flipped_)
std::vector<Vec3d> tesselate3d(const ExPolygon &expoly, double z_, bool flipped_)
{
m_z = z_;
m_flipped = flipped_;
@ -56,7 +56,7 @@ public:
return std::move(m_output_triangles);
}
Pointf3s tesselate(const ExPolygons &expolygons, double z_, bool flipped_)
std::vector<Vec3d> tesselate3d(const ExPolygons &expolygons, double z_, bool flipped_)
{
m_z = z_;
m_flipped = flipped_;
@ -189,16 +189,60 @@ private:
bool m_flipped;
};
Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z, bool flip)
std::vector<Vec3d> triangulate_expolygon_3d(const ExPolygon &poly, coordf_t z, bool flip)
{
GluTessWrapper tess;
return tess.tesselate(poly, z, flip);
return tess.tesselate3d(poly, z, flip);
}
Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z, bool flip)
std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z, bool flip)
{
GluTessWrapper tess;
return tess.tesselate(polys, z, flip);
return tess.tesselate3d(polys, z, flip);
}
std::vector<Vec2d> triangulate_expolygon_2d(const ExPolygon &poly, bool flip)
{
GluTessWrapper tess;
std::vector<Vec3d> triangles = tess.tesselate3d(poly, 0, flip);
std::vector<Vec2d> out;
out.reserve(triangles.size());
for (const Vec3d &pt : triangles)
out.emplace_back(pt.x(), pt.y());
return out;
}
std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip)
{
GluTessWrapper tess;
std::vector<Vec3d> triangles = tess.tesselate3d(polys, 0, flip);
std::vector<Vec2d> out;
out.reserve(triangles.size());
for (const Vec3d &pt : triangles)
out.emplace_back(pt.x(), pt.y());
return out;
}
std::vector<Vec2f> triangulate_expolygon_2f(const ExPolygon &poly, bool flip)
{
GluTessWrapper tess;
std::vector<Vec3d> triangles = tess.tesselate3d(poly, 0, flip);
std::vector<Vec2f> out;
out.reserve(triangles.size());
for (const Vec3d &pt : triangles)
out.emplace_back(float(pt.x()), float(pt.y()));
return out;
}
std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip)
{
GluTessWrapper tess;
std::vector<Vec3d> triangles = tess.tesselate3d(polys, 0, flip);
std::vector<Vec2f> out;
out.reserve(triangles.size());
for (const Vec3d &pt : triangles)
out.emplace_back(float(pt.x()), float(pt.y()));
return out;
}
} // namespace Slic3r

View file

@ -10,8 +10,12 @@ namespace Slic3r {
class ExPolygon;
typedef std::vector<ExPolygon> ExPolygons;
extern Pointf3s triangulate_expolygons_3df(const ExPolygon &poly, coordf_t z = 0, bool flip = false);
extern Pointf3s triangulate_expolygons_3df(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
extern std::vector<Vec3d> triangulate_expolygon_3d (const ExPolygon &poly, coordf_t z = 0, bool flip = false);
extern std::vector<Vec3d> triangulate_expolygons_3d(const ExPolygons &polys, coordf_t z = 0, bool flip = false);
extern std::vector<Vec2d> triangulate_expolygon_2d (const ExPolygon &poly, bool flip = false);
extern std::vector<Vec2d> triangulate_expolygons_2d(const ExPolygons &polys, bool flip = false);
extern std::vector<Vec2f> triangulate_expolygon_2f (const ExPolygon &poly, bool flip = false);
extern std::vector<Vec2f> triangulate_expolygons_2f(const ExPolygons &polys, bool flip = false);
} // namespace Slic3r

View file

@ -1781,7 +1781,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating upper part";
ExPolygons section;
this->make_expolygons_simple(upper_lines, &section);
Pointf3s triangles = triangulate_expolygons_3df(section, z, true);
Pointf3s triangles = triangulate_expolygons_3d(section, z, true);
stl_facet facet;
facet.normal = stl_normal(0, 0, -1.f);
for (size_t i = 0; i < triangles.size(); ) {
@ -1795,7 +1795,7 @@ void TriangleMeshSlicer::cut(float z, TriangleMesh* upper, TriangleMesh* lower)
BOOST_LOG_TRIVIAL(trace) << "TriangleMeshSlicer::cut - triangulating lower part";
ExPolygons section;
this->make_expolygons_simple(lower_lines, &section);
Pointf3s triangles = triangulate_expolygons_3df(section, z, false);
Pointf3s triangles = triangulate_expolygons_3d(section, z, false);
stl_facet facet;
facet.normal = stl_normal(0, 0, -1.f);
for (size_t i = 0; i < triangles.size(); ) {

View file

@ -206,6 +206,69 @@ public:
void reset() { closure = Closure(); }
};
// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes
// and removing spaces.
static std::string short_time(const std::string &time)
{
// Parse the dhms time format.
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
if (time.find('d') != std::string::npos)
::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
else if (time.find('h') != std::string::npos)
::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
else if (time.find('m') != std::string::npos)
::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
else if (time.find('s') != std::string::npos)
::sscanf(time.c_str(), "%ds", &seconds);
// Round to full minutes.
if (days + hours + minutes > 0 && seconds >= 30) {
if (++minutes == 60) {
minutes = 0;
if (++hours == 24) {
hours = 0;
++days;
}
}
}
// Format the dhm time.
char buffer[64];
if (days > 0)
::sprintf(buffer, "%dd%dh%dm", days, hours, minutes);
else if (hours > 0)
::sprintf(buffer, "%dh%dm", hours, minutes);
else if (minutes > 0)
::sprintf(buffer, "%dm", minutes);
else
::sprintf(buffer, "%ds", seconds);
return buffer;
}
// Returns the given time is seconds in format DDd HHh MMm SSs
static std::string get_time_dhms(float time_in_secs)
{
int days = (int)(time_in_secs / 86400.0f);
time_in_secs -= (float)days * 86400.0f;
int hours = (int)(time_in_secs / 3600.0f);
time_in_secs -= (float)hours * 3600.0f;
int minutes = (int)(time_in_secs / 60.0f);
time_in_secs -= (float)minutes * 60.0f;
char buffer[64];
if (days > 0)
::sprintf(buffer, "%dd %dh %dm %ds", days, hours, minutes, (int)time_in_secs);
else if (hours > 0)
::sprintf(buffer, "%dh %dm %ds", hours, minutes, (int)time_in_secs);
else if (minutes > 0)
::sprintf(buffer, "%dm %ds", minutes, (int)time_in_secs);
else
::sprintf(buffer, "%ds", (int)time_in_secs);
return buffer;
}
} // namespace Slic3r
#if WIN32

View file

@ -42,22 +42,27 @@ namespace Slic3r {
static boost::log::trivial::severity_level logSeverity = boost::log::trivial::error;
void set_logging_level(unsigned int level)
static boost::log::trivial::severity_level level_to_boost(unsigned level)
{
switch (level) {
// Report fatal errors only.
case 0: logSeverity = boost::log::trivial::fatal; break;
case 0: return boost::log::trivial::fatal;
// Report fatal errors and errors.
case 1: logSeverity = boost::log::trivial::error; break;
case 1: return boost::log::trivial::error;
// Report fatal errors, errors and warnings.
case 2: logSeverity = boost::log::trivial::warning; break;
case 2: return boost::log::trivial::warning;
// Report all errors, warnings and infos.
case 3: logSeverity = boost::log::trivial::info; break;
case 3: return boost::log::trivial::info;
// Report all errors, warnings, infos and debugging.
case 4: logSeverity = boost::log::trivial::debug; break;
case 4: return boost::log::trivial::debug;
// Report everyting including fine level tracing information.
default: logSeverity = boost::log::trivial::trace; break;
default: return boost::log::trivial::trace;
}
}
void set_logging_level(unsigned int level)
{
logSeverity = level_to_boost(level);
boost::log::core::get()->set_filter
(
@ -73,6 +78,7 @@ unsigned get_logging_level()
case boost::log::trivial::warning : return 2;
case boost::log::trivial::info : return 3;
case boost::log::trivial::debug : return 4;
case boost::log::trivial::trace : return 5;
default: return 1;
}
}
@ -88,21 +94,7 @@ static struct RunOnInit {
void trace(unsigned int level, const char *message)
{
boost::log::trivial::severity_level severity = boost::log::trivial::trace;
switch (level) {
// Report fatal errors only.
case 0: severity = boost::log::trivial::fatal; break;
// Report fatal errors and errors.
case 1: severity = boost::log::trivial::error; break;
// Report fatal errors, errors and warnings.
case 2: severity = boost::log::trivial::warning; break;
// Report all errors, warnings and infos.
case 3: severity = boost::log::trivial::info; break;
// Report all errors, warnings, infos and debugging.
case 4: severity = boost::log::trivial::debug; break;
// Report everyting including fine level tracing information.
default: severity = boost::log::trivial::trace; break;
}
boost::log::trivial::severity_level severity = level_to_boost(level);
BOOST_LOG_STREAM_WITH_PARAMS(::boost::log::trivial::logger::get(),\
(::boost::log::keywords::severity = severity)) << message;