Merge remote-tracking branch 'remotes/origin/lh_multi_material_segmentation' into vb_print_regions

This commit is contained in:
Vojtech Bubnik 2021-05-26 15:23:35 +02:00
commit 980ca195f5
39 changed files with 2691 additions and 146 deletions

View file

@ -132,6 +132,8 @@ add_library(libslic3r STATIC
Model.hpp
ModelArrange.hpp
ModelArrange.cpp
MultiMaterialSegmentation.cpp
MultiMaterialSegmentation.hpp
CustomGCode.cpp
CustomGCode.hpp
Arrange.hpp

View file

@ -877,7 +877,7 @@ ClipperLib::Path mittered_offset_path_scaled(const Points &contour, const std::v
Vec2d nnext = perp(ptnext - pt).normalized();
double delta = deltas[i];
double sin_a = clamp(-1., 1., cross2(nprev, nnext));
double sin_a = std::clamp(cross2(nprev, nnext), -1., 1.);
double convex = sin_a * delta;
if (convex <= - sin_min_parallel) {
// Concave corner.

View file

@ -270,7 +270,7 @@ std::vector<float> contour_distance2(const EdgeGrid::Grid &grid, const size_t id
const Vec2d v = (segment.second - segment.first).cast<double>();
const Vec2d va = (this->point - segment.first).cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt
const double t = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2);
const double t = (l2 == 0.0) ? 0. : std::clamp(va.dot(v) / l2, 0., 1.);
// Closest point from this->point to the segment.
const Vec2d foot = segment.first.cast<double>() + t * v;
const Vec2d bisector = foot - this->point.cast<double>();

View file

@ -372,6 +372,27 @@ bool remove_sticks(ExPolygon &poly)
return remove_sticks(poly.contour) || remove_sticks(poly.holes);
}
bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area)
{
bool modified = false;
size_t free_idx = 0;
for (size_t expoly_idx = 0; expoly_idx < expolygons.size(); ++expoly_idx) {
if (std::abs(expolygons[expoly_idx].area()) >= min_area) {
// Expolygon is big enough, so also check all its holes
modified |= remove_small(expolygons[expoly_idx].holes, min_area);
if (free_idx < expoly_idx) {
std::swap(expolygons[expoly_idx].contour, expolygons[free_idx].contour);
std::swap(expolygons[expoly_idx].holes, expolygons[free_idx].holes);
}
++free_idx;
} else
modified = true;
}
if (free_idx < expolygons.size())
expolygons.erase(expolygons.begin() + free_idx, expolygons.end());
return modified;
}
void keep_largest_contour_only(ExPolygons &polygons)
{
if (polygons.size() > 1) {

View file

@ -360,15 +360,11 @@ extern std::vector<BoundingBox> get_extents_vector(const ExPolygons &polygons);
extern bool remove_sticks(ExPolygon &poly);
extern void keep_largest_contour_only(ExPolygons &polygons);
inline double area(const ExPolygon &poly) { return poly.area(); }
inline double area(const ExPolygon &poly) { return poly.area(); }
inline double area(const ExPolygons &polys) { double s = 0.; for (auto &p : polys) s += p.area(); return s; }
inline double area(const ExPolygons &polys)
{
double s = 0.;
for (auto &p : polys) s += p.area();
return s;
}
// Removes all expolygons smaller than min_area and also removes all holes smaller than min_area
extern bool remove_small_and_small_holes(ExPolygons &expolygons, double min_area);
} // namespace Slic3r

View file

@ -550,10 +550,10 @@ void gcode_paint_layer(
boost::geometry::expand(bboxLine, rect[2]);
boost::geometry::expand(bboxLine, rect[3]);
B2i bboxLinei(
V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))),
clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))),
V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))),
clamp(0, nr-1, int(ceil (bboxLine.max_corner().y())))));
V2i(std::clamp(int(floor(bboxLine.min_corner().x())), 0, nc-1),
std::clamp(int(floor(bboxLine.min_corner().y())), 0, nr-1)),
V2i(std::clamp(int(ceil(bboxLine.max_corner().x())), 0, nc-1),
std::clamp(int(ceil(bboxLine.max_corner().y())), 0, nr-1)));
// printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y());
#ifdef _DEBUG
float area = polyArea(rect, 4);
@ -597,10 +597,10 @@ void gcode_paint_bitmap(
boost::geometry::expand(bboxLine, rect[2]);
boost::geometry::expand(bboxLine, rect[3]);
B2i bboxLinei(
V2i(clamp(0, nc-1, int(floor(bboxLine.min_corner().x()))),
clamp(0, nr-1, int(floor(bboxLine.min_corner().y())))),
V2i(clamp(0, nc-1, int(ceil (bboxLine.max_corner().x()))),
clamp(0, nr-1, int(ceil (bboxLine.max_corner().y())))));
V2i(std::clamp(int(floor(bboxLine.min_corner().x())), 0, nc-1),
std::clamp(int(floor(bboxLine.min_corner().y())), 0, nr-1)),
V2i(std::clamp(int(ceil(bboxLine.max_corner().x())), 0, nc-1),
std::clamp(int(ceil(bboxLine.max_corner().y())), 0, nr-1)));
// printf("bboxLinei %d,%d %d,%d\n", bboxLinei.min_corner().x(), bboxLinei.min_corner().y(), bboxLinei.max_corner().x(), bboxLinei.max_corner().y());
for (int j = bboxLinei.min_corner().y(); j + 1 < bboxLinei.max_corner().y(); ++ j) {
for (int i = bboxLinei.min_corner().x(); i + 1 < bboxLinei.max_corner().x(); ++i) {
@ -664,10 +664,10 @@ void gcode_spread_points(
const float height_target = it->height;
B2f bbox(center - V2f(radius, radius), center + V2f(radius, radius));
B2i bboxi(
V2i(clamp(0, nc-1, int(floor(bbox.min_corner().x()))),
clamp(0, nr-1, int(floor(bbox.min_corner().y())))),
V2i(clamp(0, nc-1, int(ceil (bbox.max_corner().x()))),
clamp(0, nr-1, int(ceil (bbox.max_corner().y())))));
V2i(std::clamp(int(floor(bbox.min_corner().x())), 0, nc-1),
std::clamp(int(floor(bbox.min_corner().y())), 0, nr-1)),
V2i(std::clamp(int(ceil(bbox.max_corner().x())), 0, nc-1),
std::clamp(int(ceil(bbox.max_corner().y())), 0, nr-1)));
/*
// Fill in the spans, at which the circle intersects the rows.
int row_first = bboxi.min_corner().y();
@ -758,7 +758,7 @@ void gcode_spread_points(
area_circle_total += area;
if (cell.area < area)
cell.area = area;
cell.fraction_covered = clamp(0.f, 1.f, (cell.area > 0) ? (area / cell.area) : 0);
cell.fraction_covered = std::clamp((cell.area > 0) ? (area / cell.area) : 0, 0.f, 1.f);
if (cell.fraction_covered == 0) {
-- n_cells;
continue;
@ -1018,7 +1018,7 @@ void ExtrusionSimulator::evaluate_accumulator(ExtrusionSimulationType simulation
float p = mask[r][c];
#endif
int idx = int(floor(p * float(pimpl->color_gradient.size()) + 0.5f));
V3uc clr = pimpl->color_gradient[clamp(0, int(pimpl->color_gradient.size()-1), idx)];
V3uc clr = pimpl->color_gradient[std::clamp(idx, 0, int(pimpl->color_gradient.size()-1))];
*ptr ++ = clr.get<0>();
*ptr ++ = clr.get<1>();
*ptr ++ = clr.get<2>();

View file

@ -55,8 +55,8 @@ static std::vector<coordf_t> perpendPoints(const coordf_t offset, const size_t b
static inline void trim(Pointfs &pts, coordf_t minX, coordf_t minY, coordf_t maxX, coordf_t maxY)
{
for (Vec2d &pt : pts) {
pt(0) = clamp(minX, maxX, pt(0));
pt(1) = clamp(minY, maxY, pt(1));
pt.x() = std::clamp(pt.x(), minX, maxX);
pt.y() = std::clamp(pt.y(), minY, maxY);
}
}

View file

@ -53,7 +53,7 @@ static inline Polyline make_wave(
polyline.points.reserve(points.size());
for (auto& point : points) {
point(1) += offset;
point(1) = clamp(0., height, double(point(1)));
point(1) = std::clamp(double(point.y()), 0., height);
if (vertical)
std::swap(point(0), point(1));
polyline.points.emplace_back((point * scaleFactor).cast<coord_t>());

View file

@ -96,6 +96,7 @@ static constexpr const char* PRINTABLE_ATTR = "printable";
static constexpr const char* INSTANCESCOUNT_ATTR = "instances_count";
static constexpr const char* CUSTOM_SUPPORTS_ATTR = "slic3rpe:custom_supports";
static constexpr const char* CUSTOM_SEAM_ATTR = "slic3rpe:custom_seam";
static constexpr const char* MMU_SEGMENTATION_ATTR = "slic3rpe:mmu_segmentation";
static constexpr const char* KEY_ATTR = "key";
static constexpr const char* VALUE_ATTR = "value";
@ -290,6 +291,7 @@ namespace Slic3r {
std::vector<unsigned int> triangles;
std::vector<std::string> custom_supports;
std::vector<std::string> custom_seam;
std::vector<std::string> mmu_segmentation;
bool empty() { return vertices.empty() || triangles.empty(); }
@ -298,6 +300,7 @@ namespace Slic3r {
triangles.clear();
custom_supports.clear();
custom_seam.clear();
mmu_segmentation.clear();
}
};
@ -1565,6 +1568,12 @@ namespace Slic3r {
m_curr_object.geometry.custom_supports.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SUPPORTS_ATTR));
m_curr_object.geometry.custom_seam.push_back(get_attribute_value_string(attributes, num_attributes, CUSTOM_SEAM_ATTR));
// m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
// FIXME Lukas H.: This is only for backward compatibility with older 3MF test files. Removes this when it is not necessary.
if(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR) != "")
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, MMU_SEGMENTATION_ATTR));
else
m_curr_object.geometry.mmu_segmentation.push_back(get_attribute_value_string(attributes, num_attributes, "slic3rpe:mmu_painting"));
return true;
}
@ -1889,15 +1898,18 @@ namespace Slic3r {
volume->source.transform = Slic3r::Geometry::Transformation(volume_matrix_to_object);
volume->calculate_convex_hull();
// recreate custom supports and seam from previously loaded attribute
// recreate custom supports, seam and mmu segmentation from previously loaded attribute
for (unsigned i=0; i<triangles_count; ++i) {
size_t index = src_start_id/3 + i;
assert(index < geometry.custom_supports.size());
assert(index < geometry.custom_seam.size());
assert(index < geometry.mmu_segmentation.size());
if (! geometry.custom_supports[index].empty())
volume->supported_facets.set_triangle_from_string(i, geometry.custom_supports[index]);
if (! geometry.custom_seam[index].empty())
volume->seam_facets.set_triangle_from_string(i, geometry.custom_seam[index]);
if (! geometry.mmu_segmentation[index].empty())
volume->mmu_segmentation_facets.set_triangle_from_string(i, geometry.mmu_segmentation[index]);
}
@ -2532,6 +2544,15 @@ namespace Slic3r {
output_buffer += "\"";
}
std::string mmu_painting_data_string = volume->mmu_segmentation_facets.get_triangle_as_string(i);
if (! mmu_painting_data_string.empty()) {
output_buffer += " ";
output_buffer += MMU_SEGMENTATION_ATTR;
output_buffer += "=\"";
output_buffer += mmu_painting_data_string;
output_buffer += "\"";
}
output_buffer += "/>\n";
if (! flush())

View file

@ -620,7 +620,7 @@ static std::vector<float> contour_distance(const EdgeGrid::Grid &grid,
const Vec2d v = (segment.second - segment.first).cast<double>();
const Vec2d va = (this->point - segment.first).cast<double>();
const double l2 = v.squaredNorm(); // avoid a sqrt
const double t = (l2 == 0.0) ? 0. : clamp(0., 1., va.dot(v) / l2);
const double t = (l2 == 0.0) ? 0. : std::clamp(va.dot(v) / l2, 0., 1.);
// Closest point from this->point to the segment.
const Vec2d foot = segment.first.cast<double>() + t * v;
const Vec2d bisector = foot - this->point.cast<double>();

View file

@ -725,8 +725,8 @@ std::string CoolingBuffer::apply_layer_cooldown(
if (int(layer_id) >= disable_fan_first_layers && int(layer_id) + 1 < full_fan_speed_layer) {
// Ramp up the fan speed from disable_fan_first_layers to full_fan_speed_layer.
float factor = float(int(layer_id + 1) - disable_fan_first_layers) / float(full_fan_speed_layer - disable_fan_first_layers);
fan_speed_new = clamp(0, 255, int(float(fan_speed_new ) * factor + 0.5f));
bridge_fan_speed = clamp(0, 255, int(float(bridge_fan_speed) * factor + 0.5f));
fan_speed_new = std::clamp(int(float(fan_speed_new) * factor + 0.5f), 0, 255);
bridge_fan_speed = std::clamp(int(float(bridge_fan_speed) * factor + 0.5f), 0, 255);
}
#undef EXTRUDER_CONFIG
bridge_fan_control = bridge_fan_speed > fan_speed_new;

View file

@ -1064,6 +1064,7 @@ void ModelObject::convert_units(ModelObjectPtrs& new_objects, ConversionType con
vol->supported_facets.assign(volume->supported_facets);
vol->seam_facets.assign(volume->seam_facets);
vol->mmu_segmentation_facets.assign(volume->mmu_segmentation_facets);
// Perform conversion only if the target "imperial" state is different from the current one.
// This check supports conversion of "mixed" set of volumes, each with different "imperial" state.
@ -1165,6 +1166,7 @@ ModelObjectPtrs ModelObject::cut(size_t instance, coordf_t z, bool keep_upper, b
volume->supported_facets.clear();
volume->seam_facets.clear();
volume->mmu_segmentation_facets.clear();
if (! volume->is_model_part()) {
// Modifiers are not cut, but we still need to add the instance transformation
@ -1758,6 +1760,7 @@ void ModelVolume::assign_new_unique_ids_recursive()
config.set_new_unique_id();
supported_facets.set_new_unique_id();
seam_facets.set_new_unique_id();
mmu_segmentation_facets.set_new_unique_id();
}
void ModelVolume::rotate(double angle, Axis axis)
@ -2074,8 +2077,10 @@ bool model_volume_list_changed(const ModelObject &model_object_old, const ModelO
bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
assert(mo.volumes.size() == mo_new.volumes.size());
for (size_t i=0; i<mo.volumes.size(); ++i) {
// FIXME Lukas H.: Because of adding another mesh modifiers when slicing, then assert triggered and possible crash. It requires changing the integration of MMU segmentation.
// assert(mo.volumes.size() == mo_new.volumes.size());
// for (size_t i=0; i<mo.volumes.size(); ++i) {
for (size_t i=0; i<std::min(mo.volumes.size(), mo_new.volumes.size()); ++i) {
if (! mo_new.volumes[i]->supported_facets.timestamp_matches(mo.volumes[i]->supported_facets))
return true;
}
@ -2084,14 +2089,28 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
assert(mo.volumes.size() == mo_new.volumes.size());
for (size_t i=0; i<mo.volumes.size(); ++i) {
// FIXME Lukas H.: Because of adding another mesh modifiers when slicing, then assert triggered and possible crash. It requires changing the integration of MMU segmentation.
// assert(mo.volumes.size() == mo_new.volumes.size());
// for (size_t i=0; i<mo.volumes.size(); ++i) {
for (size_t i=0; i<std::min(mo.volumes.size(), mo_new.volumes.size()); ++i) {
if (! mo_new.volumes[i]->seam_facets.timestamp_matches(mo.volumes[i]->seam_facets))
return true;
}
return false;
}
bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new) {
assert(! model_volume_list_changed(mo, mo_new, ModelVolumeType::MODEL_PART));
// FIXME Lukas H.: Because of adding another mesh modifiers when slicing, then assert triggered and possible crash. It requires changing the integration of MMU segmentation.
// assert(mo.volumes.size() == mo_new.volumes.size());
// for (size_t i=0; i<mo.volumes.size(); ++i) {
for (size_t i=0; i<std::min(mo.volumes.size(), mo_new.volumes.size()); ++i) {
if (! mo_new.volumes[i]->mmu_segmentation_facets.timestamp_matches(mo.volumes[i]->mmu_segmentation_facets))
return true;
}
return false;
}
extern bool model_has_multi_part_objects(const Model &model)
{
for (const ModelObject *model_object : model.objects)

View file

@ -591,6 +591,9 @@ public:
// List of seam enforcers/blockers.
FacetsAnnotation seam_facets;
// List of mesh facets painted for MMU segmentation.
FacetsAnnotation mmu_segmentation_facets;
// A parent object owning this modifier volume.
ModelObject* get_object() const { return this->object; }
ModelVolumeType type() const { return m_type; }
@ -679,6 +682,7 @@ public:
this->config.set_new_unique_id();
this->supported_facets.set_new_unique_id();
this->seam_facets.set_new_unique_id();
this->mmu_segmentation_facets.set_new_unique_id();
}
protected:
@ -718,22 +722,26 @@ private:
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
if (mesh.stl.stats.number_of_facets > 1)
calculate_convex_hull();
}
ModelVolume(ModelObject *object, TriangleMesh &&mesh, TriangleMesh &&convex_hull) :
m_mesh(new TriangleMesh(std::move(mesh))), m_convex_hull(new TriangleMesh(std::move(convex_hull))), m_type(ModelVolumeType::MODEL_PART), object(object) {
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
}
// Copying an existing volume, therefore this volume will get a copy of the ID assigned.
@ -741,19 +749,22 @@ private:
ObjectBase(other),
name(other.name), source(other.source), m_mesh(other.m_mesh), m_convex_hull(other.m_convex_hull),
config(other.config), m_type(other.m_type), object(object), m_transformation(other.m_transformation),
supported_facets(other.supported_facets), seam_facets(other.seam_facets)
supported_facets(other.supported_facets), seam_facets(other.seam_facets), mmu_segmentation_facets(other.mmu_segmentation_facets)
{
assert(this->id().valid());
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
assert(this->id() == other.id());
assert(this->config.id() == other.config.id());
assert(this->supported_facets.id() == other.supported_facets.id());
assert(this->seam_facets.id() == other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() == other.mmu_segmentation_facets.id());
this->set_material_id(other.material_id());
}
// Providing a new mesh, therefore this volume will get a new unique ID assigned.
@ -764,9 +775,11 @@ private:
assert(this->config.id().valid());
assert(this->supported_facets.id().valid());
assert(this->seam_facets.id().valid());
assert(this->mmu_segmentation_facets.id().valid());
assert(this->id() != this->config.id());
assert(this->id() != this->supported_facets.id());
assert(this->id() != this->seam_facets.id());
assert(this->id() != this->mmu_segmentation_facets.id());
assert(this->id() != other.id());
assert(this->config.id() == other.config.id());
this->set_material_id(other.material_id());
@ -777,9 +790,11 @@ private:
assert(this->config.id() != other.config.id());
assert(this->supported_facets.id() != other.supported_facets.id());
assert(this->seam_facets.id() != other.seam_facets.id());
assert(this->mmu_segmentation_facets.id() != other.mmu_segmentation_facets.id());
assert(this->id() != this->config.id());
assert(this->supported_facets.empty());
assert(this->seam_facets.empty());
assert(this->mmu_segmentation_facets.empty());
}
ModelVolume& operator=(ModelVolume &rhs) = delete;
@ -787,17 +802,19 @@ private:
friend class cereal::access;
friend class UndoRedo::StackImpl;
// Used for deserialization, therefore no IDs are allocated.
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), object(nullptr) {
ModelVolume() : ObjectBase(-1), config(-1), supported_facets(-1), seam_facets(-1), mmu_segmentation_facets(-1), object(nullptr) {
assert(this->id().invalid());
assert(this->config.id().invalid());
assert(this->supported_facets.id().invalid());
assert(this->seam_facets.id().invalid());
assert(this->mmu_segmentation_facets.id().invalid());
}
template<class Archive> void load(Archive &ar) {
bool has_convex_hull;
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::load_by_value(ar, supported_facets);
cereal::load_by_value(ar, seam_facets);
cereal::load_by_value(ar, mmu_segmentation_facets);
cereal::load_by_value(ar, config);
assert(m_mesh);
if (has_convex_hull) {
@ -813,6 +830,7 @@ private:
ar(name, source, m_mesh, m_type, m_material_id, m_transformation, m_is_splittable, has_convex_hull);
cereal::save_by_value(ar, supported_facets);
cereal::save_by_value(ar, seam_facets);
cereal::save_by_value(ar, mmu_segmentation_facets);
cereal::save_by_value(ar, config);
if (has_convex_hull)
cereal::save_optional(ar, m_convex_hull);
@ -1094,6 +1112,10 @@ bool model_custom_supports_data_changed(const ModelObject& mo, const ModelObject
// The function assumes that volumes list is synchronized.
bool model_custom_seam_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// Test whether the now ModelObject has newer MMU segmentation data than the old one.
// The function assumes that volumes list is synchronized.
extern bool model_mmu_segmentation_data_changed(const ModelObject& mo, const ModelObject& mo_new);
// If the model has multi-part objects, then it is currently not supported by the SLA mode.
// Either the model cannot be loaded, or a SLA printer has to be activated.
bool model_has_multi_part_objects(const Model &model);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,18 @@
#ifndef slic3r_MultiMaterialSegmentation_hpp_
#define slic3r_MultiMaterialSegmentation_hpp_
#include <utility>
#include <vector>
namespace Slic3r {
class PrintObject;
class ExPolygon;
// Returns MMU segmentation based on painting in MMU segmentation gizmo
std::vector<std::vector<std::pair<ExPolygon, size_t>>> multi_material_segmentation_by_painting(const PrintObject &print_object);
} // namespace Slic3r
#endif // slic3r_MultiMaterialSegmentation_hpp_

View file

@ -445,7 +445,7 @@ const std::vector<std::string>& Preset::print_options()
"perimeter_extrusion_width", "external_perimeter_extrusion_width", "infill_extrusion_width", "solid_infill_extrusion_width",
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio", "clip_multipart_objects",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming",
"wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "compatible_printers", "compatible_printers_condition", "inherits"
};
return s_opts;

View file

@ -216,6 +216,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
osteps.emplace_back(posSupportMaterial);
steps.emplace_back(psSkirt);
steps.emplace_back(psBrim);
} else if (opt_key == "mmu_segmented_region_max_width") {
invalidated |= this->invalidate_all_steps();
} else {
// for legacy, if we can't handle this option let's invalidate all steps
//FIXME invalidate all steps of all objects as well?

View file

@ -546,7 +546,6 @@ public:
[object_id](const PrintObject *obj) { return obj->id() == object_id; });
return (it == m_objects.end()) ? nullptr : *it;
}
// ConstPrintRegionPtrsAdaptor regions() const { return ConstPrintRegionPtrsAdaptor(&m_regions); }
// How many of PrintObject::copies() over all print objects are there?
// If zero, then the print is empty and the print shall not be executed.
unsigned int num_object_instances() const;

View file

@ -1433,6 +1433,14 @@ void PrintConfigDef::init_fff_params()
def->mode = comExpert;
def->set_default_value(new ConfigOptionBool(false));
def = this->add("mmu_segmented_region_max_width", coFloat);
def->label = L("Maximum width of a segmented region");
def->tooltip = L("Maximum width of a segmented region. Zero disables this feature.");
def->sidetext = L("mm (zero to disable)");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionFloat(0.f));
def = this->add("ironing", coBool);
def->label = L("Enable ironing");
def->tooltip = L("Enable ironing of the top layers with the hot print head for smooth surface");

View file

@ -699,6 +699,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloat, max_print_height))
((ConfigOptionFloats, min_print_speed))
((ConfigOptionFloat, min_skirt_length))
((ConfigOptionFloat, mmu_segmented_region_max_width))
((ConfigOptionString, notes))
((ConfigOptionFloats, nozzle_diameter))
((ConfigOptionBool, only_retract_when_crossing_perimeters))

View file

@ -6,6 +6,7 @@
#include "Geometry.hpp"
#include "I18N.hpp"
#include "Layer.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "SupportMaterial.hpp"
#include "Surface.hpp"
#include "Slicing.hpp"

View file

@ -298,7 +298,7 @@ std::vector<double> layer_height_profile_adaptive(const SlicingParameters& slici
if (z_gap > 0.0)
{
layer_height_profile.push_back(slicing_params.object_print_z_height());
layer_height_profile.push_back(clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, z_gap));
layer_height_profile.push_back(std::clamp(z_gap, slicing_params.min_layer_height, slicing_params.max_layer_height));
}
return layer_height_profile;
@ -376,7 +376,7 @@ std::vector<double> smooth_height_profile(const std::vector<double>& profile, co
}
}
height = clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, (weight_total != 0.0) ? height /= weight_total : hi);
height = std::clamp((weight_total != 0.0) ? height /= weight_total : hi, slicing_params.min_layer_height, slicing_params.max_layer_height);
if (smoothing_params.keep_min)
height = std::min(height, hi);
}
@ -502,7 +502,7 @@ void adjust_layer_height_profile(
assert(false);
break;
}
height = clamp(slicing_params.min_layer_height, slicing_params.max_layer_height, height);
height = std::clamp(height, slicing_params.min_layer_height, slicing_params.max_layer_height);
if (zz == z_span_variable.second) {
// This is the last point of the profile.
if (profile_new[profile_new.size() - 2] + EPSILON > zz) {
@ -670,11 +670,11 @@ int generate_layer_height_texture(
assert(mid <= slicing_params.object_print_z_height());
coordf_t h = hi - lo;
hi = std::min(hi, slicing_params.object_print_z_height());
int cell_first = clamp(0, ncells-1, int(ceil(lo * z_to_cell)));
int cell_last = clamp(0, ncells-1, int(floor(hi * z_to_cell)));
int cell_first = std::clamp(int(ceil(lo * z_to_cell)), 0, ncells-1);
int cell_last = std::clamp(int(floor(hi * z_to_cell)), 0, ncells-1);
for (int cell = cell_first; cell <= cell_last; ++ cell) {
coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale;
int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf)));
int idx1 = std::clamp(int(floor(idxf)), 0, int(palette_raw.size() - 1));
int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1);
coordf_t t = idxf - coordf_t(idx1);
const Vec3crd &color1 = palette_raw[idx1];
@ -693,9 +693,9 @@ int generate_layer_height_texture(
assert(row >= 0 && row < rows);
assert(col >= 0 && col < cols);
unsigned char *ptr = (unsigned char*)data + (row * cols + col) * 4;
ptr[0] = (unsigned char)clamp<int>(0, 255, int(floor(color(0) + 0.5)));
ptr[1] = (unsigned char)clamp<int>(0, 255, int(floor(color(1) + 0.5)));
ptr[2] = (unsigned char)clamp<int>(0, 255, int(floor(color(2) + 0.5)));
ptr[0] = (unsigned char)std::clamp(int(floor(color(0) + 0.5)), 0, 255);
ptr[1] = (unsigned char)std::clamp(int(floor(color(1) + 0.5)), 0, 255);
ptr[2] = (unsigned char)std::clamp(int(floor(color(2) + 0.5)), 0, 255);
ptr[3] = 255;
if (col == 0 && row > 0) {
// Duplicate the first value in a row as a last value of the preceding row.
@ -706,11 +706,11 @@ int generate_layer_height_texture(
}
}
if (level_of_detail_2nd_level) {
cell_first = clamp(0, ncells1-1, int(ceil(lo * z_to_cell1)));
cell_last = clamp(0, ncells1-1, int(floor(hi * z_to_cell1)));
cell_first = std::clamp(int(ceil(lo * z_to_cell1)), 0, ncells1-1);
cell_last = std::clamp(int(floor(hi * z_to_cell1)), 0, ncells1-1);
for (int cell = cell_first; cell <= cell_last; ++ cell) {
coordf_t idxf = (0.5 * hscale + (h - slicing_params.layer_height)) * coordf_t(palette_raw.size()-1) / hscale;
int idx1 = clamp(0, int(palette_raw.size() - 1), int(floor(idxf)));
int idx1 = std::clamp(int(floor(idxf)), 0, int(palette_raw.size() - 1));
int idx2 = std::min(int(palette_raw.size() - 1), idx1 + 1);
coordf_t t = idxf - coordf_t(idx1);
const Vec3crd &color1 = palette_raw[idx1];
@ -725,9 +725,9 @@ int generate_layer_height_texture(
assert(row >= 0 && row < rows/2);
assert(col >= 0 && col < cols/2);
unsigned char *ptr = data1 + (row * cols1 + col) * 4;
ptr[0] = (unsigned char)clamp<int>(0, 255, int(floor(color(0) + 0.5)));
ptr[1] = (unsigned char)clamp<int>(0, 255, int(floor(color(1) + 0.5)));
ptr[2] = (unsigned char)clamp<int>(0, 255, int(floor(color(2) + 0.5)));
ptr[0] = (unsigned char)std::clamp(int(floor(color(0) + 0.5)), 0, 255);
ptr[1] = (unsigned char)std::clamp(int(floor(color(1) + 0.5)), 0, 255);
ptr[2] = (unsigned char)std::clamp(int(floor(color(2) + 0.5)), 0, 255);
ptr[3] = 255;
if (col == 0 && row > 0) {
// Duplicate the first value in a row as a last value of the preceding row.

View file

@ -36,7 +36,7 @@ void TriangleSelector::Triangle::set_division(int sides_to_split, int special_si
void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
const Vec3f& source, float radius,
CursorType cursor_type, EnforcerBlockerType new_state,
const Transform3d& trafo)
const Transform3d& trafo, bool triangle_splitting)
{
assert(facet_start < m_orig_size_indices);
@ -59,7 +59,7 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
while (facet_idx < int(facets_to_check.size())) {
int facet = facets_to_check[facet_idx];
if (! visited[facet]) {
if (select_triangle(facet, new_state)) {
if (select_triangle(facet, new_state, false, triangle_splitting)) {
// add neighboring facets to list to be proccessed later
for (int n=0; n<3; ++n) {
int neighbor_idx = m_mesh->stl.neighbors_start[facet].neighbor[n];
@ -73,13 +73,56 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
}
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f& hit, int facet_start, float seed_fill_angle)
{
this->seed_fill_unselect_all_triangles();
std::vector<bool> visited(m_triangles.size(), false);
std::queue<size_t> facet_queue;
facet_queue.push(facet_start);
// Check if neighbour_facet_idx is satisfies angle in seed_fill_angle and append it to facet_queue if it do.
auto check_angle_and_append = [this, &facet_queue](const size_t facet_idx, const size_t neighbour_facet_idx, const float seed_fill_angle) -> void {
double dot_product = m_triangles[neighbour_facet_idx].normal.dot(m_triangles[facet_idx].normal);
dot_product = std::clamp(dot_product, 0., 1.);
double facet_angle_limit = cos(Geometry::deg2rad(seed_fill_angle));
if ((dot_product + EPSILON) >= facet_angle_limit)
facet_queue.push(neighbour_facet_idx);
};
while(!facet_queue.empty()) {
size_t current_facet = facet_queue.front();
facet_queue.pop();
if (!visited[current_facet]) {
if (!m_triangles[current_facet].is_split())
m_triangles[current_facet].select_by_seed_fill();
if (m_triangles[current_facet].is_split())
for (int split_triangle_idx = 0; split_triangle_idx <= m_triangles[current_facet].number_of_split_sides(); ++split_triangle_idx) {
assert(split_triangle_idx < int(m_triangles[current_facet].children.size()));
assert(m_triangles[current_facet].children[split_triangle_idx] < int(m_triangles.size()));
if (!visited[m_triangles[current_facet].children[split_triangle_idx]])
check_angle_and_append(current_facet, m_triangles[current_facet].children[split_triangle_idx], seed_fill_angle);
}
if (int(current_facet) < m_orig_size_indices)
for (int neighbor_idx : m_mesh->stl.neighbors_start[current_facet].neighbor) {
assert(neighbor_idx >= 0);
if (neighbor_idx >= 0 && !visited[neighbor_idx])
check_angle_and_append(current_facet, neighbor_idx, seed_fill_angle);
}
}
visited[current_facet] = true;
}
}
// Selects either the whole triangle (discarding any children it had), or divides
// the triangle recursively, selecting just subtriangles truly inside the circle.
// This is done by an actual recursive call. Returns false if the triangle is
// outside the cursor.
bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call)
bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call, bool triangle_splitting)
{
assert(facet_idx < int(m_triangles.size()));
@ -108,7 +151,10 @@ bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type,
return true;
}
split_triangle(facet_idx);
if(triangle_splitting)
split_triangle(facet_idx);
else if(!m_triangles[facet_idx].is_split())
m_triangles[facet_idx].set_state(type);
tr = &m_triangles[facet_idx]; // might have been invalidated
@ -118,7 +164,7 @@ bool TriangleSelector::select_triangle(int facet_idx, EnforcerBlockerType type,
assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size()));
select_triangle(tr->children[i], type, true);
select_triangle(tr->children[i], type, true, triangle_splitting);
tr = &m_triangles[facet_idx]; // might have been invalidated
}
}
@ -417,7 +463,7 @@ TriangleSelector::TriangleSelector(const TriangleMesh& mesh)
}
void TriangleSelector::reset()
void TriangleSelector::reset(const EnforcerBlockerType reset_state)
{
if (m_orig_size_indices != 0) // unless this is run from constructor
garbage_collect();
@ -428,7 +474,7 @@ void TriangleSelector::reset()
for (size_t i=0; i<m_mesh->its.indices.size(); ++i) {
const stl_triangle_vertex_indices& ind = m_mesh->its.indices[i];
const Vec3f& normal = m_mesh->stl.facet_start[i].normal;
push_triangle(ind[0], ind[1], ind[2], normal);
push_triangle(ind[0], ind[1], ind[2], normal, reset_state);
}
m_orig_size_vertices = m_vertices.size();
m_orig_size_indices = m_triangles.size();
@ -454,13 +500,13 @@ void TriangleSelector::set_edge_limit(float edge_limit)
void TriangleSelector::push_triangle(int a, int b, int c, const Vec3f& normal)
void TriangleSelector::push_triangle(int a, int b, int c, const Vec3f& normal, const EnforcerBlockerType state)
{
for (int i : {a, b, c}) {
assert(i >= 0 && i < int(m_vertices.size()));
++m_vertices[i].ref_cnt;
}
m_triangles.emplace_back(a, b, c, normal);
m_triangles.emplace_back(a, b, c, normal, state);
}
@ -550,8 +596,9 @@ indexed_triangle_set TriangleSelector::get_facets(EnforcerBlockerType state) con
std::map<int, std::vector<bool>> TriangleSelector::serialize() const
{
// Each original triangle of the mesh is assigned a number encoding its state
// or how it is split. Each triangle is encoded by 4 bits (xxyy):
// leaf triangle: xx = EnforcerBlockerType, yy = 0
// or how it is split. Each triangle is encoded by 4 bits (xxyy) or 8 bits (zzzzxxyy):
// leaf triangle: xx = EnforcerBlockerType (Only values 0, 1, and 2. Value 3 is used as an indicator for additional 4 bits.), yy = 0
// leaf triangle: xx = 0b11, yy = 0b00, zzzz = EnforcerBlockerType (subtracted by 3)
// non-leaf: xx = special side, yy = number of split sides
// These are bitwise appended and formed into one 64-bit integer.
@ -594,9 +641,17 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const
serialize_recursive(tr.children[child_idx]);
} else {
// In case this is leaf, we better save information about its state.
assert(int(tr.get_state()) <= 3);
data.push_back(int(tr.get_state()) & 0b01);
data.push_back(int(tr.get_state()) & 0b10);
assert(int(tr.get_state()) <= 15);
if (3 <= int(tr.get_state()) && int(tr.get_state()) <= 15) {
data.insert(data.end(), {true, true});
for (size_t bit_idx = 0; bit_idx < 4; ++bit_idx) {
size_t bit_mask = uint64_t(0b0001) << bit_idx;
data.push_back((int(tr.get_state()) - 3) & bit_mask);
}
} else {
data.push_back(int(tr.get_state()) & 0b01);
data.push_back(int(tr.get_state()) & 0b10);
}
++stored_triangles;
}
};
@ -608,13 +663,13 @@ std::map<int, std::vector<bool>> TriangleSelector::serialize() const
return out;
}
void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data, const EnforcerBlockerType init_state)
{
reset(); // dump any current state
reset(init_state); // dump any current state
for (const auto& [triangle_id, code] : data) {
assert(triangle_id < int(m_triangles.size()));
assert(! code.empty());
int processed_triangles = 0;
int processed_nibbles = 0;
struct ProcessingInfo {
int facet_id = 0;
int processed_children = 0;
@ -626,18 +681,26 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
while (true) {
// Read next triangle info.
int next_code = 0;
for (int i=3; i>=0; --i) {
next_code = next_code << 1;
next_code |= int(code[4 * processed_triangles + i]);
}
++processed_triangles;
std::array<int, 2> next_code{};
for(size_t nibble_idx = 0; nibble_idx < 2; ++nibble_idx) {
assert(nibble_idx < 2);
if(nibble_idx >= 1 && (next_code[0] >> 2) != 0b11)
break;
int num_of_split_sides = (next_code & 0b11);
for (int i = 3; i >= 0; --i) {
next_code[nibble_idx] = next_code[nibble_idx] << 1;
next_code[nibble_idx] |= int(code[4 * processed_nibbles + i]);
}
++processed_nibbles;
}
int num_of_split_sides = (next_code[0] & 0b11);
int num_of_children = num_of_split_sides != 0 ? num_of_split_sides + 1 : 0;
bool is_split = num_of_children != 0;
EnforcerBlockerType state = EnforcerBlockerType(next_code >> 2);
int special_side = (next_code >> 2);
// Value of the second nibble was subtracted by 3, so it is added back.
auto state = EnforcerBlockerType(next_code[0] >> 2 == 0b11 ? next_code[1] + 3 : next_code[0] >> 2);
int special_side = (next_code[0] >> 2);
// Take care of the first iteration separately, so handling of the others is simpler.
if (parents.empty()) {
@ -693,6 +756,18 @@ void TriangleSelector::deserialize(const std::map<int, std::vector<bool>> data)
}
}
void TriangleSelector::seed_fill_unselect_all_triangles() {
for (Triangle &triangle : m_triangles)
if (!triangle.is_split())
triangle.unselect_by_seed_fill();
}
void TriangleSelector::seed_fill_apply_on_triangles(EnforcerBlockerType new_state)
{
for (Triangle &triangle : m_triangles)
if (!triangle.is_split() && triangle.is_selected_by_seed_fill())
triangle.set_state(new_state);
}
TriangleSelector::Cursor::Cursor(
const Vec3f& center_, const Vec3f& source_, float radius_world,

View file

@ -29,13 +29,18 @@ public:
explicit TriangleSelector(const TriangleMesh& mesh);
// Select all triangles fully inside the circle, subdivide where needed.
void select_patch(const Vec3f& hit, // point where to start
int facet_start, // facet that point belongs to
const Vec3f& source, // camera position (mesh coords)
float radius, // radius of the cursor
CursorType type, // current type of cursor
void select_patch(const Vec3f &hit, // point where to start
int facet_start, // facet that point belongs to
const Vec3f &source, // camera position (mesh coords)
float radius, // radius of the cursor
CursorType type, // current type of cursor
EnforcerBlockerType new_state, // enforcer or blocker?
const Transform3d& trafo); // matrix to get from mesh to world
const Transform3d &trafo, // matrix to get from mesh to world
bool triangle_splitting); // If triangles will be split base on the cursor or not
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet that point belongs to
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
// Get facets currently in the given state.
indexed_triangle_set get_facets(EnforcerBlockerType state) const;
@ -44,7 +49,7 @@ public:
void set_facet(int facet_idx, EnforcerBlockerType state);
// Clear everything and make the tree empty.
void reset();
void reset(const EnforcerBlockerType reset_state = EnforcerBlockerType{0});
// Remove all unnecessary data.
void garbage_collect();
@ -54,8 +59,13 @@ public:
std::map<int, std::vector<bool>> serialize() const;
// Load serialized data. Assumes that correct mesh is loaded.
void deserialize(const std::map<int, std::vector<bool>> data);
void deserialize(const std::map<int, std::vector<bool>> data, const EnforcerBlockerType init_state = EnforcerBlockerType{0});
// For all triangles, remove the flag indicating that the triangle was selected by seed fill.
void seed_fill_unselect_all_triangles();
// For all triangles selected by seed fill, set new EnforcerBlockerType and remove flag indicating that triangle was selected by seed fill.
void seed_fill_apply_on_triangles(EnforcerBlockerType new_state);
protected:
// Triangle and info about how it's split.
@ -63,10 +73,10 @@ protected:
public:
// Use TriangleSelector::push_triangle to create a new triangle.
// It increments/decrements reference counter on vertices.
Triangle(int a, int b, int c, const Vec3f& normal_)
Triangle(int a, int b, int c, const Vec3f& normal_, const EnforcerBlockerType init_state)
: verts_idxs{a, b, c},
normal{normal_},
state{EnforcerBlockerType(0)},
state{init_state},
number_of_splits{0},
special_side_idx{0},
old_number_of_splits{0}
@ -90,6 +100,12 @@ protected:
void set_state(EnforcerBlockerType type) { assert(! is_split()); state = type; }
EnforcerBlockerType get_state() const { assert(! is_split()); return state; }
// Set if the triangle has been selected or unselected by seed fill.
void select_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = true; }
void unselect_by_seed_fill() { assert(! is_split()); m_selected_by_seed_fill = false; }
// Get if the triangle has been selected or not by seed fill.
bool is_selected_by_seed_fill() const { assert(! is_split()); return m_selected_by_seed_fill; }
// Get info on how it's split.
bool is_split() const { return number_of_split_sides() != 0; }
int number_of_split_sides() const { return number_of_splits; }
@ -101,6 +117,7 @@ protected:
int number_of_splits;
int special_side_idx;
EnforcerBlockerType state;
bool m_selected_by_seed_fill = false;
// How many children were spawned during last split?
// Is not reset on remerging the triangle.
@ -153,8 +170,7 @@ protected:
float m_old_cursor_radius_sqr;
// Private functions:
bool select_triangle(int facet_idx, EnforcerBlockerType type,
bool recursive_call = false);
bool select_triangle(int facet_idx, EnforcerBlockerType type, bool recursive_call = false, bool triangle_splitting = true);
int vertices_inside(int facet_idx) const;
bool faces_camera(int facet) const;
void undivide_triangle(int facet_idx);
@ -162,7 +178,7 @@ protected:
void remove_useless_children(int facet_idx); // No hidden meaning. Triangles are meant.
bool is_pointer_in_triangle(int facet_idx) const;
bool is_edge_inside_cursor(int facet_idx) const;
void push_triangle(int a, int b, int c, const Vec3f& normal);
void push_triangle(int a, int b, int c, const Vec3f &normal, const EnforcerBlockerType state = EnforcerBlockerType{0});
void perform_split(int facet_idx, EnforcerBlockerType old_state);
};

View file

@ -41,7 +41,7 @@ namespace detail {
// Degenerate to a single closest point.
t = - b / (2. * a);
assert(t >= - EPSILON && t <= 1. + EPSILON);
return Slic3r::clamp(0., 1., t);
return std::clamp(t, 0., 1.);
} else {
u = sqrt(u);
out.first = 2;
@ -1142,7 +1142,7 @@ std::vector<Vec2d> edge_offset_contour_intersections(
#endif // NDEBUG
if (! bisector || (dmin != dmax && offset_distance >= dmin)) {
double t = (offset_distance - dmin) / (dmax - dmin);
t = clamp(0., 1., t);
t = std::clamp(t, 0., 1.);
if (d1 < d0) {
out[edge_idx2] = Slic3r::lerp(vertex_point(v1), vertex_point(v0), t);
// mark visited

View file

@ -239,26 +239,20 @@ template<typename T> inline bool one_of(const T& v, const std::initializer_list<
{ return contains(il, v); }
template<typename T>
static inline T sqr(T x)
constexpr inline T sqr(T x)
{
return x * x;
}
template <typename T>
static inline T clamp(const T low, const T high, const T value)
{
return std::max(low, std::min(high, value));
}
template <typename T, typename Number>
static inline T lerp(const T& a, const T& b, Number t)
constexpr inline T lerp(const T& a, const T& b, Number t)
{
assert((t >= Number(-EPSILON)) && (t <= Number(1) + Number(EPSILON)));
return (Number(1) - t) * a + t * b;
}
template <typename Number>
static inline bool is_approx(Number value, Number test_value)
constexpr inline bool is_approx(Number value, Number test_value)
{
return std::fabs(double(value) - double(test_value)) < double(EPSILON);
}