mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-23 22:54:08 -06:00
Merge remote-tracking branch 'remotes/origin/lh_multi_material_segmentation' into vb_print_regions
This commit is contained in:
commit
980ca195f5
39 changed files with 2691 additions and 146 deletions
|
@ -132,6 +132,8 @@ add_library(libslic3r STATIC
|
|||
Model.hpp
|
||||
ModelArrange.hpp
|
||||
ModelArrange.cpp
|
||||
MultiMaterialSegmentation.cpp
|
||||
MultiMaterialSegmentation.hpp
|
||||
CustomGCode.cpp
|
||||
CustomGCode.hpp
|
||||
Arrange.hpp
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>());
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
|
1523
src/libslic3r/MultiMaterialSegmentation.cpp
Normal file
1523
src/libslic3r/MultiMaterialSegmentation.cpp
Normal file
File diff suppressed because it is too large
Load diff
18
src/libslic3r/MultiMaterialSegmentation.hpp
Normal file
18
src/libslic3r/MultiMaterialSegmentation.hpp
Normal 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_
|
|
@ -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;
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue