mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-25 17:51:10 -06:00
Merge branch 'master' into tm_colldetection_upgr
This commit is contained in:
commit
03079381e1
119 changed files with 11942 additions and 8619 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 ¶ms) {}
|
||||
// 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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ¢roid, 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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
|
|
|
|||
137
src/libslic3r/SLA/SLACommon.hpp
Normal file
137
src/libslic3r/SLA/SLACommon.hpp
Normal 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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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>&);
|
||||
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 ¶ms)
|
||||
{
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -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 ¶ms) 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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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_
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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, §ion);
|
||||
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, §ion);
|
||||
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(); ) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue