Merge branch 'vb_mmu_top_bottom'

This commit is contained in:
Vojtech Bubnik 2021-07-13 11:08:52 +02:00
commit 19e3998bd0
21 changed files with 1442 additions and 477 deletions

View File

@ -602,12 +602,12 @@ FaceNeighborIndex its_create_neighbors_index_8(const indexed_triangle_set &its)
std::vector<Vec3crd> its_create_neighbors_index_9(const indexed_triangle_set &its)
{
return create_neighbors_index(ex_seq, its);
return create_face_neighbors_index(ex_seq, its);
}
std::vector<Vec3i> its_create_neighbors_index_10(const indexed_triangle_set &its)
{
return create_neighbors_index(ex_tbb, its);
return create_face_neighbors_index(ex_tbb, its);
}
} // namespace Slic3r

View File

@ -64,10 +64,12 @@ public:
void set(const std::string &section, const std::string &key, const std::string &value)
{
#ifndef NDEBUG
std::string key_trimmed = key;
boost::trim_all(key_trimmed);
assert(key_trimmed == key);
assert(! key_trimmed.empty());
{
std::string key_trimmed = key;
boost::trim_all(key_trimmed);
assert(key_trimmed == key);
assert(! key_trimmed.empty());
}
#endif // NDEBUG
std::string &old = m_storage[section][key];
if (old != value) {

View File

@ -201,21 +201,24 @@ void SeamPlacer::init(const Print& print)
std::vector<ExPolygons> temp_enf;
std::vector<ExPolygons> temp_blk;
std::vector<Polygons> temp_polygons;
for (const PrintObject* po : print.objects()) {
temp_enf.clear();
temp_blk.clear();
po->project_and_append_custom_facets(true, EnforcerBlockerType::ENFORCER, temp_enf);
po->project_and_append_custom_facets(true, EnforcerBlockerType::BLOCKER, temp_blk);
// Offset the triangles out slightly.
for (auto* custom_per_object : {&temp_enf, &temp_blk}) {
auto merge_and_offset = [po, &temp_polygons, max_nozzle_dmr](EnforcerBlockerType type, std::vector<ExPolygons>& out) {
// Offset the triangles out slightly.
temp_polygons.clear();
po->project_and_append_custom_facets(true, type, temp_polygons);
out.clear();
out.reserve(temp_polygons.size());
float offset = max_nozzle_dmr + po->config().elefant_foot_compensation;
for (ExPolygons& explgs : *custom_per_object) {
explgs = Slic3r::offset_ex(explgs, scale_(offset));
for (const Polygons &src : temp_polygons) {
out.emplace_back(Slic3r::offset_ex(src, scale_(offset)));
offset = max_nozzle_dmr;
}
}
};
merge_and_offset(EnforcerBlockerType::BLOCKER, temp_blk);
merge_and_offset(EnforcerBlockerType::ENFORCER, temp_enf);
// FIXME: Offsetting should be done somehow cheaper, but following does not work
// for (auto* custom_per_object : {&temp_enf, &temp_blk}) {

View File

@ -201,6 +201,16 @@ protected:
virtual ~SupportLayer() = default;
};
template<typename LayerContainer>
inline std::vector<float> zs_from_layers(const LayerContainer &layers)
{
std::vector<float> zs;
zs.reserve(layers.size());
for (const Layer *l : layers)
zs.emplace_back((float)l->slice_z);
return zs;
}
}
#endif

View File

@ -8,7 +8,7 @@
namespace Slic3r {
template<class ExPolicy>
std::vector<Vec3i> create_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its);
std::vector<Vec3i> create_face_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its);
namespace meshsplit_detail {
@ -24,7 +24,7 @@ template<> struct ItsWithNeighborsIndex_<indexed_triangle_set> {
static const indexed_triangle_set &get_its(const indexed_triangle_set &its) noexcept { return its; }
static Index get_index(const indexed_triangle_set &its) noexcept
{
return create_neighbors_index(ex_tbb, its);
return create_face_neighbors_index(ex_tbb, its);
}
};
@ -162,28 +162,14 @@ template<class Its> bool its_is_splittable(const Its &m)
return !faces.empty();
}
inline int get_vertex_index(size_t vertex_index, const stl_triangle_vertex_indices &triangle_indices) {
if (int(vertex_index) == triangle_indices[0]) return 0;
if (int(vertex_index) == triangle_indices[1]) return 1;
if (int(vertex_index) == triangle_indices[2]) return 2;
return -1;
}
inline Vec2crd get_edge_indices(int edge_index, const stl_triangle_vertex_indices &triangle_indices)
{
int next_edge_index = (edge_index == 2) ? 0 : edge_index + 1;
int vi0 = triangle_indices[edge_index];
int vi1 = triangle_indices[next_edge_index];
return Vec2crd(vi0, vi1);
}
template<class ExPolicy>
std::vector<Vec3i> create_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its)
std::vector<Vec3i> create_face_neighbors_index(ExPolicy &&ex, const indexed_triangle_set &its)
{
const std::vector<stl_triangle_vertex_indices> &indices = its.indices;
size_t vertices_size = its.vertices.size();
if (indices.empty() || vertices_size == 0) return {};
if (indices.empty()) return {};
assert(! its.vertices.empty());
auto vertex_triangles = VertexFaceIndex{its};
static constexpr int no_value = -1;
@ -192,27 +178,28 @@ std::vector<Vec3i> create_neighbors_index(ExPolicy &&ex, const indexed_triangle_
//for (const stl_triangle_vertex_indices& triangle_indices : indices) {
execution::for_each(ex, size_t(0), indices.size(),
[&neighbors, &indices, &vertex_triangles] (size_t index)
[&neighbors, &indices, &vertex_triangles] (size_t face_idx)
{
Vec3i& neighbor = neighbors[index];
const stl_triangle_vertex_indices & triangle_indices = indices[index];
Vec3i& neighbor = neighbors[face_idx];
const stl_triangle_vertex_indices & triangle_indices = indices[face_idx];
for (int edge_index = 0; edge_index < 3; ++edge_index) {
// check if done
int& neighbor_edge = neighbor[edge_index];
if (neighbor_edge != no_value) continue;
Vec2crd edge_indices = get_edge_indices(edge_index, triangle_indices);
if (neighbor_edge != no_value)
// This edge already has a neighbor assigned.
continue;
Vec2i edge_indices = its_triangle_edge(triangle_indices, edge_index);
// IMPROVE: use same vector for 2 sides of triangle
const auto &faces_range = vertex_triangles[edge_indices[0]];
for (const size_t &face : faces_range) {
if (face <= index) continue;
const stl_triangle_vertex_indices &face_indices = indices[face];
int vertex_index = get_vertex_index(edge_indices[1], face_indices);
for (const size_t other_face : vertex_triangles[edge_indices[0]]) {
if (other_face <= face_idx) continue;
const stl_triangle_vertex_indices &face_indices = indices[other_face];
int vertex_index = its_triangle_vertex_index(face_indices, edge_indices[1]);
// NOT Contain second vertex?
if (vertex_index < 0) continue;
// Has NOT oposit direction?
// Has NOT oposite direction?
if (edge_indices[0] != face_indices[(vertex_index + 1) % 3]) continue;
neighbor_edge = face;
neighbors[face][vertex_index] = index;
neighbor_edge = other_face;
neighbors[other_face][vertex_index] = face_idx;
break;
}
}

View File

@ -1959,8 +1959,19 @@ indexed_triangle_set FacetsAnnotation::get_facets(const ModelVolume& mv, Enforce
{
TriangleSelector selector(mv.mesh());
selector.deserialize(m_data);
indexed_triangle_set out = selector.get_facets(type);
return out;
return selector.get_facets(type);
}
indexed_triangle_set FacetsAnnotation::get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const
{
TriangleSelector selector(mv.mesh());
selector.deserialize(m_data);
return selector.get_facets_strict(type);
}
bool FacetsAnnotation::has_facets(const ModelVolume& mv, EnforcerBlockerType type) const
{
return TriangleSelector::has_facets(m_data, type);
}
bool FacetsAnnotation::set(const TriangleSelector& selector)

View File

@ -500,10 +500,26 @@ private:
};
enum class EnforcerBlockerType : int8_t {
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits!
// Maximum is 3. The value is serialized in TriangleSelector into 2 bits.
NONE = 0,
ENFORCER = 1,
BLOCKER = 2
BLOCKER = 2,
// Maximum is 15. The value is serialized in TriangleSelector into 6 bits using a 2 bit prefix code.
Extruder1 = ENFORCER,
Extruder2 = BLOCKER,
Extruder3,
Extruder4,
Extruder5,
Extruder6,
Extruder7,
Extruder8,
Extruder9,
Extruder10,
Extruder11,
Extruder12,
Extruder13,
Extruder14,
Extruder15,
};
enum class ConversionType : int {
@ -521,6 +537,8 @@ public:
const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>>& get_data() const throw() { return m_data; }
bool set(const TriangleSelector& selector);
indexed_triangle_set get_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
indexed_triangle_set get_facets_strict(const ModelVolume& mv, EnforcerBlockerType type) const;
bool has_facets(const ModelVolume& mv, EnforcerBlockerType type) const;
bool empty() const { return m_data.first.empty(); }
void clear();

View File

@ -1145,67 +1145,16 @@ static void cut_segmented_layers(const std::vector<ExPolygons>
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - cutting segmented layers in parallel - end";
}
// #define MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
// Returns MMU segmentation of top and bottom layers based on painting in MMU segmentation gizmo
static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bottom_layers(const PrintObject &print_object,
const std::vector<ExPolygons> &input_expolygons,
const std::function<void()> &throw_on_cancel_callback)
{
const size_t num_extruders = print_object.print()->config().nozzle_diameter.size() + 1;
const size_t num_layers = input_expolygons.size();
const ConstLayerPtrsAdaptor layers = print_object.layers();
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders);
triangles_by_color.assign(num_extruders, std::vector<ExPolygons>(layers.size()));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
for (const ModelVolume *mv : print_object.model_object()->volumes) {
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
throw_on_cancel_callback();
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
if (!mv->is_model_part() || custom_facets.indices.empty())
continue;
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest();
std::array<Vec3f, 3> facet;
Points projected_facet(3);
for (int p_idx = 0; p_idx < 3; ++p_idx) {
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
max_z = std::max(max_z, facet[p_idx].z());
min_z = std::min(min_z, facet[p_idx].z());
}
// Sort the vertices by z-axis for simplification of projected_facet on slices
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
for (int p_idx = 0; p_idx < 3; ++p_idx) {
projected_facet[p_idx] = Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y()));
projected_facet[p_idx] = projected_facet[p_idx] - print_object.center_offset();
}
ExPolygon triangle = ExPolygon(projected_facet);
// Find lowest slice not below the triangle.
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
if (last_layer == layers.end())
--last_layer;
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
--first_layer;
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it) {
size_t layer_idx = layer_it - layers.begin();
triangles_by_color[extruder_idx][layer_idx].emplace_back(triangle);
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float {
auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
@ -1235,9 +1184,10 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
return (*top_bottom_layer_it)->region().config().bottom_solid_layers;
};
std::vector<ExPolygons> top_layers(input_expolygons.size());
#if 0
std::vector<ExPolygons> top_layers(num_layers);
top_layers.back() = input_expolygons.back();
tbb::parallel_for(tbb::blocked_range<size_t>(1, input_expolygons.size()), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
@ -1245,9 +1195,9 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
}
}); // end of parallel_for
std::vector<ExPolygons> bottom_layers(input_expolygons.size());
std::vector<ExPolygons> bottom_layers(num_layers);
bottom_layers.front() = input_expolygons.front();
tbb::parallel_for(tbb::blocked_range<size_t>(0, input_expolygons.size() - 1), [&](const tbb::blocked_range<size_t> &range) {
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers - 1), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
@ -1255,28 +1205,84 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
}
}); // end of parallel_for
tbb::parallel_for(tbb::blocked_range<size_t>(0, input_expolygons.size()), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
for (std::vector<ExPolygons> &triangles : triangles_by_color) {
if (!triangles[layer_idx].empty() && (!top_layers[layer_idx].empty() || !bottom_layers[layer_idx].empty())) {
ExPolygons connected = union_ex(offset_ex(triangles[layer_idx], float(10 * SCALED_EPSILON)));
triangles[layer_idx] = union_ex(offset_ex(offset_ex(connected, -extrusion_width / 1), extrusion_width / 1));
} else {
triangles[layer_idx].clear();
std::vector<std::vector<ClipperLib::Paths>> triangles_by_color_raw(num_extruders, std::vector<ClipperLib::Paths>(layers.size()));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
{
auto delta = float(10 * SCALED_EPSILON);
std::vector<float> deltas { delta, delta, delta };
Points projected_facet;
for (const ModelVolume *mv : print_object.model_object()->volumes)
if (mv->is_model_part()) {
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
if (custom_facets.indices.empty())
continue;
throw_on_cancel_callback();
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest();
std::array<Vec3f, 3> facet;
for (int p_idx = 0; p_idx < 3; ++p_idx) {
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
max_z = std::max(max_z, facet[p_idx].z());
min_z = std::min(min_z, facet[p_idx].z());
}
// Sort the vertices by z-axis for simplification of projected_facet on slices
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
projected_facet.clear();
projected_facet.reserve(3);
for (int p_idx = 0; p_idx < 3; ++p_idx)
projected_facet.emplace_back(Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())) - print_object.center_offset());
if (cross2((projected_facet[1] - projected_facet[0]).cast<int64_t>(), (projected_facet[2] - projected_facet[1]).cast<int64_t>()) < 0)
// Make CCW.
std::swap(projected_facet[1], projected_facet[2]);
ClipperLib::Path offsetted = mittered_offset_path_scaled(projected_facet, deltas, 3.);
// Find lowest slice not below the triangle.
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
if (last_layer == layers.end())
--last_layer;
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
--first_layer;
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it)
if (size_t layer_idx = layer_it - layers.begin(); ! top_layers[layer_idx].empty() || ! bottom_layers[layer_idx].empty())
triangles_by_color_raw[extruder_idx][layer_idx].emplace_back(offsetted);
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders, std::vector<ExPolygons>(layers.size()));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float offset_factor = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
if (ClipperLib::Paths &src_paths = triangles_by_color_raw[extruder_id][layer_idx]; !src_paths.empty())
triangles_by_color[extruder_id][layer_idx] = offset_ex(offset_ex(ClipperPaths_to_Slic3rExPolygons(src_paths), -offset_factor), offset_factor);
}
}); // end of parallel_for
triangles_by_color_raw.clear();
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(input_expolygons.size()));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(input_expolygons.size()));
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - begin";
for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) {
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int top_solid_layers = get_top_solid_layers(layer_idx);
ExPolygons top_expolygon = top_layers[layer_idx];
@ -1312,7 +1318,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - end";
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - begin";
for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) {
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int bottom_solid_layers = get_bottom_solid_layers(layer_idx);
const ExPolygons &bottom_expolygon = bottom_layers[layer_idx];
@ -1328,7 +1334,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
if (!intersection_poly.empty()) {
triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(),
intersection_poly.end());
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, input_expolygons.size()); ++last_idx) {
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) {
float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width;
if (offset_ex(bottom_expolygon, offset_value).empty())
continue;
@ -1347,8 +1353,8 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - end";
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(input_expolygons.size()));
for (size_t layer_idx = 0; layer_idx < input_expolygons.size(); ++layer_idx) {
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
throw_on_cancel_callback();
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
@ -1363,6 +1369,169 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
}
#else
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
int max_top_layers = 0;
int max_bottom_layers = 0;
int granularity = 1;
for (size_t i = 0; i < print_object.num_printing_regions(); ++ i) {
const PrintRegionConfig &config = print_object.printing_region(i).config();
max_top_layers = std::max(max_top_layers, config.top_solid_layers.value);
max_bottom_layers = std::max(max_bottom_layers, config.bottom_solid_layers.value);
granularity = std::max(granularity, std::max(config.top_solid_layers.value, config.bottom_solid_layers.value) - 1);
}
// Project upwards pointing painted triangles over top surfaces,
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
std::vector<float> zs = zs_from_layers(print_object.layers());
Transform3d object_trafo = print_object.trafo();
object_trafo.pretranslate(Vec3d(- unscale<double>(print_object.center_offset().x()), - unscale<double>(print_object.center_offset().y()), 0));
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
static int iRun = 0;
#endif // NDEBUG
if (max_top_layers > 0 || max_bottom_layers > 0) {
for (const ModelVolume *mv : print_object.model_object()->volumes)
if (mv->is_model_part()) {
const Transform3d volume_trafo = object_trafo * mv->get_matrix();
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
const indexed_triangle_set painted = mv->mmu_segmentation_facets.get_facets_strict(*mv, EnforcerBlockerType(extruder_idx));
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
{
static int iRun = 0;
its_write_obj(painted, debug_out_path("mm-painted-patch-%d-%d.obj", iRun ++, extruder_idx).c_str());
}
#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
if (! painted.indices.empty()) {
std::vector<Polygons> top, bottom;
slice_mesh_slabs(painted, zs, volume_trafo, max_top_layers > 0 ? &top : nullptr, max_bottom_layers > 0 ? &bottom : nullptr, throw_on_cancel_callback);
auto merge = [](std::vector<Polygons> &&src, std::vector<Polygons> &dst) {
auto it_src = find_if(src.begin(), src.end(), [](const Polygons &p){ return ! p.empty(); });
if (it_src != src.end()) {
if (dst.empty()) {
dst = std::move(src);
} else {
assert(src.size() == dst.size());
auto it_dst = dst.begin() + (it_src - src.begin());
for (; it_src != src.end(); ++ it_src, ++ it_dst)
if (! it_src->empty()) {
if (it_dst->empty())
*it_dst = std::move(*it_src);
else
append(*it_dst, std::move(*it_src));
}
}
}
};
merge(std::move(top), top_raw[extruder_idx]);
merge(std::move(bottom), bottom_raw[extruder_idx]);
}
}
}
}
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
{
const char* colors[] = { "aqua", "black", "blue", "fuchsia", "gray", "green", "lime", "maroon", "navy", "olive", "purple", "red", "silver", "teal", "yellow" };
static int iRun = 0;
for (size_t layer_id = 0; layer_id < zs.size(); ++layer_id) {
std::vector<std::pair<Slic3r::ExPolygons, SVG::ExPolygonAttributes>> svg;
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++ extruder_idx) {
if (! top_raw[extruder_idx].empty() && ! top_raw[extruder_idx][layer_id].empty())
if (ExPolygons expoly = union_ex(top_raw[extruder_idx][layer_id]); ! expoly.empty()) {
const char *color = colors[extruder_idx];
svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("top%d", extruder_idx), color, color, color });
}
if (! bottom_raw[extruder_idx].empty() && ! bottom_raw[extruder_idx][layer_id].empty())
if (ExPolygons expoly = union_ex(bottom_raw[extruder_idx][layer_id]); ! expoly.empty()) {
const char *color = colors[extruder_idx + 8];
svg.emplace_back(expoly, SVG::ExPolygonAttributes{ format("bottom%d", extruder_idx), color, color, color });
}
}
SVG::export_expolygons(debug_out_path("mm-segmentation-top-bottom-%d-%d-%lf.svg", iRun, layer_id, zs[layer_id]), svg);
}
++ iRun;
}
#endif // MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers * 2));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers, granularity), [&](const tbb::blocked_range<size_t> &range) {
size_t group_idx = range.begin() / granularity;
size_t layer_idx_offset = (group_idx & 1) * num_layers;
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int top_solid_layers = get_top_solid_layers(layer_idx);
int bottom_solid_layers = get_bottom_solid_layers(layer_idx);
float narrow_island_width = 0.1f * float(extrusion_width);
for (size_t color_idx = 0; color_idx < num_extruders; ++ color_idx) {
throw_on_cancel_callback();
if (std::vector<Polygons> &top = top_raw[color_idx]; ! top.empty() && ! top[layer_idx].empty())
if (ExPolygons top_ex = union_ex(top[layer_idx]); ! top_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
top_ex = offset2_ex(top_ex, - narrow_island_width, + narrow_island_width);
if (! top_ex.empty()) {
append(triangles_by_color_top[color_idx][layer_idx + layer_idx_offset], top_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) {
offset -= extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = intersection_ex(top_ex, offset_ex(layer_slices_trimmed, offset));
if (last.empty())
break;
append(triangles_by_color_top[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
}
if (std::vector<Polygons> &bottom = bottom_raw[color_idx]; ! bottom.empty() && ! bottom[layer_idx].empty())
if (ExPolygons bottom_ex = union_ex(bottom[layer_idx]); ! bottom_ex.empty()) {
// Clean up thin projections. They are not printable anyways.
bottom_ex = offset2_ex(bottom_ex, - narrow_island_width, + narrow_island_width);
if (! bottom_ex.empty()) {
append(triangles_by_color_bottom[color_idx][layer_idx + layer_idx_offset], bottom_ex);
float offset = 0.f;
ExPolygons layer_slices_trimmed = input_expolygons[layer_idx];
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) {
offset -= extrusion_width;
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx]);
ExPolygons last = intersection_ex(bottom_ex, offset_ex(layer_slices_trimmed, offset));
if (last.empty())
break;
append(triangles_by_color_bottom[color_idx][last_idx + layer_idx_offset], std::move(last));
}
}
}
}
}
});
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) {
throw_on_cancel_callback();
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx + num_layers]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx + num_layers]));
self = union_ex(self);
}
// Trim one region by the other if some of the regions overlap.
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++ color_idx)
triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx],
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
});
#endif
return triangles_by_color_merged;
}

View File

@ -655,10 +655,8 @@ std::string Print::validate(std::string* warning) const
// Notify the user in that case.
if (! object->has_support() && warning) {
for (const ModelVolume* mv : object->model_object()->volumes) {
bool has_enforcers = mv->is_support_enforcer()
|| (mv->is_model_part()
&& ! mv->supported_facets.empty()
&& ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty());
bool has_enforcers = mv->is_support_enforcer() ||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
if (has_enforcers) {
*warning = "_SUPPORTS_OFF";
break;

View File

@ -326,12 +326,12 @@ public:
void slice();
// Helpers to slice support enforcer / blocker meshes by the support generator.
std::vector<ExPolygons> slice_support_volumes(const ModelVolumeType model_volume_type) const;
std::vector<ExPolygons> slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
std::vector<ExPolygons> slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
std::vector<Polygons> slice_support_volumes(const ModelVolumeType model_volume_type) const;
std::vector<Polygons> slice_support_blockers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_BLOCKER); }
std::vector<Polygons> slice_support_enforcers() const { return this->slice_support_volumes(ModelVolumeType::SUPPORT_ENFORCER); }
// Helpers to project custom facets on slices
void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const;
void project_and_append_custom_facets(bool seam, EnforcerBlockerType type, std::vector<Polygons>& expolys) const;
private:
// to be called from Print only.

View File

@ -402,10 +402,8 @@ void PrintObject::generate_support_material()
// Notify the user in that case.
if (! this->has_support()) {
for (const ModelVolume* mv : this->model_object()->volumes) {
bool has_enforcers = mv->is_support_enforcer()
|| (mv->is_model_part()
&& ! mv->supported_facets.empty()
&& ! mv->supported_facets.get_facets(*mv, EnforcerBlockerType::ENFORCER).indices.empty());
bool has_enforcers = mv->is_support_enforcer() ||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
if (has_enforcers) {
this->active_step_add_warning(PrintStateBase::WarningLevel::CRITICAL,
L("An object has custom support enforcers which will not be used "
@ -2101,206 +2099,206 @@ void PrintObject::_generate_support_material()
support_material.generate(*this);
}
void PrintObject::project_and_append_custom_facets(
bool seam, EnforcerBlockerType type, std::vector<ExPolygons>& expolys) const
static void project_triangles_to_slabs(ConstLayerPtrsAdaptor layers, const indexed_triangle_set &custom_facets, const Transform3f &tr, bool seam, std::vector<Polygons> &out)
{
for (const ModelVolume* mv : this->model_object()->volumes) {
const indexed_triangle_set custom_facets = seam
? mv->seam_facets.get_facets(*mv, type)
: mv->supported_facets.get_facets(*mv, type);
if (! mv->is_model_part() || custom_facets.indices.empty())
if (custom_facets.indices.empty())
return;
const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f);
// The projection will be at most a pentagon. Let's minimize heap
// reallocations by saving in in the following struct.
// Points are used so that scaling can be done in parallel
// and they can be moved from to create an ExPolygon later.
struct LightPolygon {
LightPolygon() { pts.reserve(5); }
LightPolygon(const std::array<Vec2f, 3>& tri) {
pts.reserve(3);
pts.emplace_back(scaled<coord_t>(tri.front()));
pts.emplace_back(scaled<coord_t>(tri[1]));
pts.emplace_back(scaled<coord_t>(tri.back()));
}
Points pts;
void add(const Vec2f& pt) {
pts.emplace_back(scaled<coord_t>(pt));
assert(pts.size() <= 5);
}
};
// Structure to collect projected polygons. One element for each triangle.
// Saves vector of polygons and layer_id of the first one.
struct TriangleProjections {
size_t first_layer_id;
std::vector<LightPolygon> polygons;
};
// Vector to collect resulting projections from each triangle.
std::vector<TriangleProjections> projections_of_triangles(custom_facets.indices.size());
// Iterate over all triangles.
tbb::parallel_for(
tbb::blocked_range<size_t>(0, custom_facets.indices.size()),
[&custom_facets, &tr, tr_det_sign, seam, layers, &projections_of_triangles](const tbb::blocked_range<size_t>& range) {
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
std::array<Vec3f, 3> facet;
// Transform the triangle into worlds coords.
for (int i=0; i<3; ++i)
facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)];
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
if (! seam && tr_det_sign * z_comp > 0.)
continue;
const Transform3f& tr1 = mv->get_matrix().cast<float>();
const Transform3f& tr2 = this->trafo().cast<float>();
const Transform3f tr = tr2 * tr1;
const float tr_det_sign = (tr.matrix().determinant() > 0. ? 1.f : -1.f);
const Vec2f center = unscaled<float>(this->center_offset());
ConstLayerPtrsAdaptor layers = this->layers();
// The algorithm does not process vertical triangles, but it should for seam.
// In that case, tilt the triangle a bit so the projection does not degenerate.
if (seam && z_comp == 0.f)
facet[0].x() += float(EPSILON);
// The projection will be at most a pentagon. Let's minimize heap
// reallocations by saving in in the following struct.
// Points are used so that scaling can be done in parallel
// and they can be moved from to create an ExPolygon later.
struct LightPolygon {
LightPolygon() { pts.reserve(5); }
LightPolygon(const std::array<Vec2f, 3>& tri) {
pts.reserve(3);
pts.emplace_back(scaled<coord_t>(tri.front()));
pts.emplace_back(scaled<coord_t>(tri[1]));
pts.emplace_back(scaled<coord_t>(tri.back()));
}
// Sort the three vertices according to z-coordinate.
std::sort(facet.begin(), facet.end(),
[](const Vec3f& pt1, const Vec3f&pt2) {
return pt1.z() < pt2.z();
});
Points pts;
std::array<Vec2f, 3> trianglef;
for (int i=0; i<3; ++i)
trianglef[i] = to_2d(facet[i]);
void add(const Vec2f& pt) {
pts.emplace_back(scaled<coord_t>(pt));
assert(pts.size() <= 5);
}
};
// Structure to collect projected polygons. One element for each triangle.
// Saves vector of polygons and layer_id of the first one.
struct TriangleProjections {
size_t first_layer_id;
std::vector<LightPolygon> polygons;
};
// Vector to collect resulting projections from each triangle.
std::vector<TriangleProjections> projections_of_triangles(custom_facets.indices.size());
// Iterate over all triangles.
tbb::parallel_for(
tbb::blocked_range<size_t>(0, custom_facets.indices.size()),
[center, &custom_facets, &tr, tr_det_sign, seam, layers, &projections_of_triangles](const tbb::blocked_range<size_t>& range) {
for (size_t idx = range.begin(); idx < range.end(); ++ idx) {
std::array<Vec3f, 3> facet;
// Transform the triangle into worlds coords.
for (int i=0; i<3; ++i)
facet[i] = tr * custom_facets.vertices[custom_facets.indices[idx](i)];
// Ignore triangles with upward-pointing normal. Don't forget about mirroring.
float z_comp = (facet[1]-facet[0]).cross(facet[2]-facet[0]).z();
if (! seam && tr_det_sign * z_comp > 0.)
continue;
// The algorithm does not process vertical triangles, but it should for seam.
// In that case, tilt the triangle a bit so the projection does not degenerate.
if (seam && z_comp == 0.f)
facet[0].x() += float(EPSILON);
// Sort the three vertices according to z-coordinate.
std::sort(facet.begin(), facet.end(),
[](const Vec3f& pt1, const Vec3f&pt2) {
return pt1.z() < pt2.z();
// Find lowest slice not below the triangle.
auto it = std::lower_bound(layers.begin(), layers.end(), facet[0].z()+EPSILON,
[](const Layer* l1, float z) {
return l1->slice_z < z;
});
std::array<Vec2f, 3> trianglef;
for (int i=0; i<3; ++i)
trianglef[i] = to_2d(facet[i]) - center;
// Count how many projections will be generated for this triangle
// and allocate respective amount in projections_of_triangles.
size_t first_layer_id = projections_of_triangles[idx].first_layer_id = it - layers.begin();
size_t last_layer_id = first_layer_id;
// The cast in the condition below is important. The comparison must
// be an exact opposite of the one lower in the code where
// the polygons are appended. And that one is on floats.
while (last_layer_id + 1 < layers.size()
&& float(layers[last_layer_id]->slice_z) <= facet[2].z())
++last_layer_id;
// Find lowest slice not below the triangle.
auto it = std::lower_bound(layers.begin(), layers.end(), facet[0].z()+EPSILON,
[](const Layer* l1, float z) {
return l1->slice_z < z;
});
// Count how many projections will be generated for this triangle
// and allocate respective amount in projections_of_triangles.
size_t first_layer_id = projections_of_triangles[idx].first_layer_id = it - layers.begin();
size_t last_layer_id = first_layer_id;
// The cast in the condition below is important. The comparison must
// be an exact opposite of the one lower in the code where
// the polygons are appended. And that one is on floats.
while (last_layer_id + 1 < layers.size()
&& float(layers[last_layer_id]->slice_z) <= facet[2].z())
++last_layer_id;
if (first_layer_id == last_layer_id) {
// The triangle fits just a single slab, just project it. This also avoids division by zero for horizontal triangles.
float dz = facet[2].z() - facet[0].z();
assert(dz >= 0);
// The face is nearly horizontal and it crosses the slicing plane at first_layer_id - 1.
// Rather add this face to both the planes.
bool add_below = dz < float(2. * EPSILON) && first_layer_id > 0 && layers[first_layer_id - 1]->slice_z > facet[0].z() - EPSILON;
projections_of_triangles[idx].polygons.reserve(add_below ? 2 : 1);
if (first_layer_id == last_layer_id) {
// The triangle fits just a single slab, just project it. This also avoids division by zero for horizontal triangles.
float dz = facet[2].z() - facet[0].z();
assert(dz >= 0);
// The face is nearly horizontal and it crosses the slicing plane at first_layer_id - 1.
// Rather add this face to both the planes.
bool add_below = dz < float(2. * EPSILON) && first_layer_id > 0 && layers[first_layer_id - 1]->slice_z > facet[0].z() - EPSILON;
projections_of_triangles[idx].polygons.reserve(add_below ? 2 : 1);
projections_of_triangles[idx].polygons.emplace_back(trianglef);
if (add_below) {
-- projections_of_triangles[idx].first_layer_id;
projections_of_triangles[idx].polygons.emplace_back(trianglef);
if (add_below) {
-- projections_of_triangles[idx].first_layer_id;
projections_of_triangles[idx].polygons.emplace_back(trianglef);
}
continue;
}
projections_of_triangles[idx].polygons.resize(last_layer_id - first_layer_id + 1);
// Calculate how to move points on triangle sides per unit z increment.
Vec2f ta(trianglef[1] - trianglef[0]);
Vec2f tb(trianglef[2] - trianglef[0]);
ta *= 1.f/(facet[1].z() - facet[0].z());
tb *= 1.f/(facet[2].z() - facet[0].z());
// Projection on current slice will be build directly in place.
LightPolygon* proj = &projections_of_triangles[idx].polygons[0];
proj->add(trianglef[0]);
bool passed_first = false;
bool stop = false;
// Project a sub-polygon on all slices intersecting the triangle.
while (it != layers.end()) {
const float z = float((*it)->slice_z);
// Projections of triangle sides intersections with slices.
// a moves along one side, b tracks the other.
Vec2f a;
Vec2f b;
// If the middle vertex was already passed, append the vertex
// and use ta for tracking the remaining side.
if (z > facet[1].z() && ! passed_first) {
proj->add(trianglef[1]);
ta = trianglef[2]-trianglef[1];
ta *= 1.f/(facet[2].z() - facet[1].z());
passed_first = true;
}
// This slice is above the triangle already.
if (z > facet[2].z() || it+1 == layers.end()) {
proj->add(trianglef[2]);
stop = true;
}
else {
// Move a, b along the side it currently tracks to get
// projected intersection with current slice.
a = passed_first ? (trianglef[1]+ta*(z-facet[1].z()))
: (trianglef[0]+ta*(z-facet[0].z()));
b = trianglef[0]+tb*(z-facet[0].z());
proj->add(a);
proj->add(b);
}
if (stop)
break;
// Advance to the next layer.
++it;
++proj;
assert(proj <= &projections_of_triangles[idx].polygons.back() );
// a, b are first two points of the polygon for the next layer.
proj->add(b);
proj->add(a);
}
continue;
}
}); // end of parallel_for
// Make sure that the output vector can be used.
expolys.resize(layers.size());
projections_of_triangles[idx].polygons.resize(last_layer_id - first_layer_id + 1);
// Now append the collected polygons to respective layers.
for (auto& trg : projections_of_triangles) {
int layer_id = int(trg.first_layer_id);
for (LightPolygon &poly : trg.polygons) {
if (layer_id >= int(expolys.size()))
break; // part of triangle could be projected above top layer
assert(! poly.pts.empty());
// The resulting triangles are fed to the Clipper library, which seem to handle flipped triangles well.
// Calculate how to move points on triangle sides per unit z increment.
Vec2f ta(trianglef[1] - trianglef[0]);
Vec2f tb(trianglef[2] - trianglef[0]);
ta *= 1.f/(facet[1].z() - facet[0].z());
tb *= 1.f/(facet[2].z() - facet[0].z());
// Projection on current slice will be built directly in place.
LightPolygon* proj = &projections_of_triangles[idx].polygons[0];
proj->add(trianglef[0]);
bool passed_first = false;
bool stop = false;
// Project a sub-polygon on all slices intersecting the triangle.
while (it != layers.end()) {
const float z = float((*it)->slice_z);
// Projections of triangle sides intersections with slices.
// a moves along one side, b tracks the other.
Vec2f a;
Vec2f b;
// If the middle vertex was already passed, append the vertex
// and use ta for tracking the remaining side.
if (z > facet[1].z() && ! passed_first) {
proj->add(trianglef[1]);
ta = trianglef[2]-trianglef[1];
ta *= 1.f/(facet[2].z() - facet[1].z());
passed_first = true;
}
// This slice is above the triangle already.
if (z > facet[2].z() || it+1 == layers.end()) {
proj->add(trianglef[2]);
stop = true;
}
else {
// Move a, b along the side it currently tracks to get
// projected intersection with current slice.
a = passed_first ? (trianglef[1]+ta*(z-facet[1].z()))
: (trianglef[0]+ta*(z-facet[0].z()));
b = trianglef[0]+tb*(z-facet[0].z());
proj->add(a);
proj->add(b);
}
if (stop)
break;
// Advance to the next layer.
++it;
++proj;
assert(proj <= &projections_of_triangles[idx].polygons.back() );
// a, b are first two points of the polygon for the next layer.
proj->add(b);
proj->add(a);
}
}
}); // end of parallel_for
// Make sure that the output vector can be used.
out.resize(layers.size());
// Now append the collected polygons to respective layers.
for (auto& trg : projections_of_triangles) {
int layer_id = int(trg.first_layer_id);
for (LightPolygon &poly : trg.polygons) {
if (layer_id >= int(out.size()))
break; // part of triangle could be projected above top layer
assert(! poly.pts.empty());
// The resulting triangles are fed to the Clipper library, which seem to handle flipped triangles well.
// if (cross2(Vec2d((poly.pts[1] - poly.pts[0]).cast<double>()), Vec2d((poly.pts[2] - poly.pts[1]).cast<double>())) < 0)
// std::swap(poly.pts.front(), poly.pts.back());
expolys[layer_id].emplace_back(std::move(poly.pts));
++layer_id;
}
out[layer_id].emplace_back(std::move(poly.pts));
++layer_id;
}
} // loop over ModelVolumes
}
}
void PrintObject::project_and_append_custom_facets(
bool seam, EnforcerBlockerType type, std::vector<Polygons>& out) const
{
for (const ModelVolume* mv : this->model_object()->volumes)
if (mv->is_model_part()) {
const indexed_triangle_set custom_facets = seam
? mv->seam_facets.get_facets_strict(*mv, type)
: mv->supported_facets.get_facets_strict(*mv, type);
if (! custom_facets.indices.empty())
project_triangles_to_slabs(this->layers(), custom_facets,
(Eigen::Translation3d(to_3d(unscaled<double>(this->center_offset()), 0.)) * this->trafo() * mv->get_matrix()).cast<float>(),
seam, out);
}
}
const Layer* PrintObject::get_layer_at_printz(coordf_t print_z) const {
auto it = Slic3r::lower_bound_by_predicate(m_layers.begin(), m_layers.end(), [print_z](const Layer *layer) { return layer->print_z < print_z; });

View File

@ -39,16 +39,6 @@ LayerPtrs new_layers(
return out;
}
template<typename LayerContainer>
static inline std::vector<float> zs_from_layers(const LayerContainer &layers)
{
std::vector<float> zs;
zs.reserve(layers.size());
for (const Layer *l : layers)
zs.emplace_back((float)l->slice_z);
return zs;
}
//FIXME The admesh repair function may break the face connectivity, rather refresh it here as the slicing code relies on it.
// This function will go away once we get rid of admesh from ModelVolume.
static indexed_triangle_set get_mesh_its_fix_mesh_connectivity(TriangleMesh mesh)
@ -604,14 +594,18 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
if (! layer_split)
continue;
// Split LayerRegions by by_extruder regions.
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
auto it_painted_region = layer_range.painted_regions.begin();
for (int region_id = 0; region_id < int(layer->region_count()); ++ region_id)
if (LayerRegion &layerm = *layer->get_region(region_id); ! layerm.slices.surfaces.empty()) {
assert(layerm.region().print_object_region_id() == region_id);
const BoundingBox bbox = get_extents(layerm.slices.surfaces);
assert(it_painted_region < layer_range.painted_regions.end());
// Find the first it_painted_region which overrides this region.
for (; layer_range.volume_regions[it_painted_region->parent].region->print_object_region_id() < region_id; ++ it_painted_region)
assert(it_painted_region < layer_range.painted_regions.end());
assert(&layerm.region() == it_painted_region->region && layerm.region().print_object_region_id() == region_id);
assert(it_painted_region != layer_range.painted_regions.end());
assert(it_painted_region != layer_range.painted_regions.end());
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region());
// 1-based extruder ID
bool self_trimmed = false;
int self_extruder_id = -1;
@ -619,7 +613,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
if (ByExtruder &segmented = by_extruder[extruder_id - 1]; segmented.bbox.defined && bbox.overlap(segmented.bbox)) {
// Find the target region.
for (; int(it_painted_region->extruder_id) < extruder_id; ++ it_painted_region)
assert(it_painted_region < layer_range.painted_regions.end());
assert(it_painted_region != layer_range.painted_regions.end());
assert(layer_range.volume_regions[it_painted_region->parent].region == &layerm.region() && int(it_painted_region->extruder_id) == extruder_id);
//FIXME Don't trim by self, it is not reliable.
if (&layerm.region() == it_painted_region->region) {
@ -669,7 +663,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, ThrowOnCance
ByRegion &src = by_region[region_id];
if (src.needs_merge)
// Multiple regions were merged into one.
src.expolygons = offset2_ex(src.expolygons, float(scale_(EPSILON)), - float(scale_(EPSILON)));
src.expolygons = offset2_ex(src.expolygons, float(scale_(10 * EPSILON)), - float(scale_(10 * EPSILON)));
layer->get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
}
}
@ -701,7 +695,7 @@ void PrintObject::slice_volumes()
std::vector<float> slice_zs = zs_from_layers(m_layers);
Transform3d trafo = this->trafo();
trafo.pretranslate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
trafo.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0));
std::vector<std::vector<ExPolygons>> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs,
slice_volumes_inner(
print->config(), this->config(), trafo,
@ -812,12 +806,12 @@ void PrintObject::slice_volumes()
BOOST_LOG_TRIVIAL(debug) << "Slicing volumes - make_slices in parallel - end";
}
std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const
std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType model_volume_type) const
{
auto it_volume = this->model_object()->volumes.begin();
auto it_volume_end = this->model_object()->volumes.end();
for (; it_volume != it_volume_end && (*it_volume)->type() != model_volume_type; ++ it_volume) ;
std::vector<ExPolygons> slices;
std::vector<Polygons> slices;
if (it_volume != it_volume_end) {
// Found at least a single support volume of model_volume_type.
std::vector<float> zs = zs_from_layers(this->layers());
@ -831,16 +825,18 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
for (; it_volume != it_volume_end; ++ it_volume)
if ((*it_volume)->type() == model_volume_type) {
std::vector<ExPolygons> slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback);
if (slices.empty())
slices = std::move(slices2);
else if (! slices2.empty()) {
if (slices.empty()) {
slices.reserve(slices2.size());
for (ExPolygons &src : slices2)
slices.emplace_back(to_polygons(std::move(src)));
} else if (!slices2.empty()) {
if (merge_layers.empty())
merge_layers.assign(zs.size(), false);
for (size_t i = 0; i < zs.size(); ++ i) {
if (slices[i].empty())
slices[i] = std::move(slices2[i]);
slices[i] = to_polygons(std::move(slices2[i]));
else if (! slices2[i].empty()) {
append(slices[i], std::move(slices2[i]));
append(slices[i], to_polygons(std::move(slices2[i])));
merge_layers[i] = true;
merge = true;
}
@ -848,7 +844,7 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
}
}
if (merge) {
std::vector<ExPolygons*> to_merge;
std::vector<Polygons*> to_merge;
to_merge.reserve(zs.size());
for (size_t i = 0; i < zs.size(); ++ i)
if (merge_layers[i])
@ -857,7 +853,7 @@ std::vector<ExPolygons> PrintObject::slice_support_volumes(const ModelVolumeType
tbb::blocked_range<size_t>(0, to_merge.size()),
[&to_merge](const tbb::blocked_range<size_t> &range) {
for (size_t i = range.begin(); i < range.end(); ++ i)
*to_merge[i] = union_ex(*to_merge[i]);
*to_merge[i] = union_(*to_merge[i]);
});
}
}

View File

@ -1353,9 +1353,9 @@ struct SupportAnnotations
object.project_and_append_custom_facets(false, EnforcerBlockerType::BLOCKER, blockers_layers);
}
std::vector<ExPolygons> enforcers_layers;
std::vector<ExPolygons> blockers_layers;
const std::vector<Polygons>& buildplate_covered;
std::vector<Polygons> enforcers_layers;
std::vector<Polygons> blockers_layers;
const std::vector<Polygons>& buildplate_covered;
};
struct SlicesMarginCache

View File

@ -728,7 +728,7 @@ static std::vector<EdgeToFace> create_edge_map(
// Map from a face edge to a unique edge identifier or -1 if no neighbor exists.
// Two neighbor faces share a unique edge identifier even if they are flipped.
template<typename ThrowOnCancelCallback>
static inline std::vector<Vec3i> create_face_neighbors_index_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel)
static inline std::vector<Vec3i> its_face_edge_ids_impl(const indexed_triangle_set &its, ThrowOnCancelCallback throw_on_cancel)
{
std::vector<Vec3i> out(its.indices.size(), Vec3i(-1, -1, -1));
@ -778,14 +778,56 @@ static inline std::vector<Vec3i> create_face_neighbors_index_impl(const indexed_
return out;
}
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its)
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its)
{
return create_face_neighbors_index_impl(its, [](){});
return its_face_edge_ids_impl(its, [](){});
}
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback)
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback)
{
return create_face_neighbors_index_impl(its, throw_on_cancel_callback);
return its_face_edge_ids_impl(its, throw_on_cancel_callback);
}
// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices.
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors, bool assign_unbound_edges, int *num_edges)
{
// out elements are not initialized!
std::vector<Vec3i> out(face_neighbors.size());
int last_edge_id = 0;
for (int i = 0; i < int(face_neighbors.size()); ++ i) {
const stl_triangle_vertex_indices &triangle = its.indices[i];
const Vec3i &neighbors = face_neighbors[i];
for (int j = 0; j < 3; ++ j) {
int n = neighbors[j];
if (n > i) {
const stl_triangle_vertex_indices &triangle2 = its.indices[n];
int edge_id = last_edge_id ++;
Vec2i edge = its_triangle_edge(triangle, j);
// First find an edge with opposite orientation.
std::swap(edge(0), edge(1));
int k = its_triangle_edge_index(triangle2, edge);
//FIXME is the following realistic? Could face_neighbors contain such faces?
// And if it does, do we want to produce the same edge ID for those mutually incorrectly oriented edges?
if (k == -1) {
// Second find an edge with the same orientation (the neighbor triangle may be flipped).
std::swap(edge(0), edge(1));
k = its_triangle_edge_index(triangle2, edge);
}
assert(k >= 0);
out[i](j) = edge_id;
out[n](k) = edge_id;
} else if (n == -1) {
out[i](j) = assign_unbound_edges ? last_edge_id ++ : -1;
} else {
// Triangle shall never be neighbor of itself.
assert(n < i);
// Don't do anything, the neighbor will assign us an edge ID in later iterations.
}
}
}
if (num_edges)
*num_edges = last_edge_id;
return out;
}
// Merge duplicate vertices, return number of vertices removed.
@ -1219,14 +1261,14 @@ void VertexFaceIndex::create(const indexed_triangle_set &its)
m_vertex_to_face_start.front() = 0;
}
std::vector<Vec3i> its_create_neighbors_index(const indexed_triangle_set &its)
std::vector<Vec3i> its_face_neighbors(const indexed_triangle_set &its)
{
return create_neighbors_index(ex_seq, its);
return create_face_neighbors_index(ex_seq, its);
}
std::vector<Vec3i> its_create_neighbors_index_par(const indexed_triangle_set &its)
std::vector<Vec3i> its_face_neighbors_par(const indexed_triangle_set &its)
{
return create_neighbors_index(ex_tbb, its);
return create_face_neighbors_index(ex_tbb, its);
}
} // namespace Slic3r

View File

@ -116,13 +116,14 @@ private:
// Map from a face edge to a unique edge identifier or -1 if no neighbor exists.
// Two neighbor faces share a unique edge identifier even if they are flipped.
// Used for chaining slice lines into polygons.
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its);
std::vector<Vec3i> create_face_neighbors_index(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback);
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its);
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::function<void()> throw_on_cancel_callback);
// Having the face neighbors available, assign unique edge IDs to face edges for chaining of polygons over slices.
std::vector<Vec3i> its_face_edge_ids(const indexed_triangle_set &its, std::vector<Vec3i> &face_neighbors, bool assign_unbound_edges = false, int *num_edges = nullptr);
// Create index that gives neighbor faces for each face. Ignores face orientations.
// TODO: naming...
std::vector<Vec3i> its_create_neighbors_index(const indexed_triangle_set &its);
std::vector<Vec3i> its_create_neighbors_index_par(const indexed_triangle_set &its);
std::vector<Vec3i> its_face_neighbors(const indexed_triangle_set &its);
std::vector<Vec3i> its_face_neighbors_par(const indexed_triangle_set &its);
// After applying a transformation with negative determinant, flip the faces to keep the transformed mesh volume positive.
void its_flip_triangles(indexed_triangle_set &its);
@ -153,6 +154,28 @@ void its_collect_mesh_projection_points_above(const indexed_triangle_set &its, c
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Matrix3f &m, const float z);
Polygon its_convex_hull_2d_above(const indexed_triangle_set &its, const Transform3f &t, const float z);
// Index of a vertex inside triangle_indices.
inline int its_triangle_vertex_index(const stl_triangle_vertex_indices &triangle_indices, int vertex_idx)
{
return vertex_idx == triangle_indices[0] ? 0 :
vertex_idx == triangle_indices[1] ? 1 :
vertex_idx == triangle_indices[2] ? 2 : -1;
}
inline Vec2i its_triangle_edge(const stl_triangle_vertex_indices &triangle_indices, int edge_idx)
{
int next_edge_idx = (edge_idx == 2) ? 0 : edge_idx + 1;
return { triangle_indices[edge_idx], triangle_indices[next_edge_idx] };
}
// Index of an edge inside triangle.
inline int its_triangle_edge_index(const stl_triangle_vertex_indices &triangle_indices, const Vec2i &triangle_edge)
{
return triangle_edge(0) == triangle_indices[0] && triangle_edge(1) == triangle_indices[1] ? 0 :
triangle_edge(0) == triangle_indices[1] && triangle_edge(1) == triangle_indices[2] ? 1 :
triangle_edge(0) == triangle_indices[2] && triangle_edge(1) == triangle_indices[0] ? 2 : -1;
}
using its_triangle = std::array<stl_vertex, 3>;
inline its_triangle its_triangle_vertices(const indexed_triangle_set &its,

View File

@ -3,6 +3,7 @@
#include "Tesselate.hpp"
#include "TriangleMesh.hpp"
#include "TriangleMeshSlicer.hpp"
#include "Utils.hpp"
#include <algorithm>
#include <cmath>
@ -14,6 +15,10 @@
#include <tbb/parallel_for.h>
#ifndef NDEBUG
// #define EXPENSIVE_DEBUG_CHECKS
#endif // NDEBUG
#if 0
#define DEBUG
#define _DEBUG
@ -26,6 +31,8 @@
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>
// #define SLIC3R_DEBUG_SLICE_PROCESSING
#if defined(SLIC3R_DEBUG) || defined(SLIC3R_DEBUG_SLICE_PROCESSING)
#include "SVG.hpp"
#endif
@ -64,6 +71,8 @@ public:
bool is_seed_candidate() const { return (this->flags & NO_SEED) == 0 && ! this->skip(); }
void set_no_seed(bool set) { if (set) this->flags |= NO_SEED; else this->flags &= ~NO_SEED; }
void reverse() { std::swap(a, b); std::swap(a_id, b_id); std::swap(edge_a_id, edge_b_id); }
// Inherits Point a, b
// For each line end point, either {a,b}_id or {a,b}edge_a_id is set, the other is left to -1.
@ -81,8 +90,14 @@ public:
Top,
// Two vertices are aligned with the cutting plane, the third vertex is above the cutting plane.
Bottom,
// Two vertices are aligned with the cutting plane, the edge is shared by two triangles, where one
// triangle is below or at the cutting plane and the other is above or at the cutting plane (only one
// vertex may lie on the plane).
TopBottom,
// All three vertices of a face are aligned with the cutting plane.
Horizontal
Horizontal,
// Edge
Slab,
};
// feGeneral, feTop, feBottom, feHorizontal
@ -102,6 +117,15 @@ public:
SKIP = 0x200,
};
uint32_t flags { 0 };
#ifndef NDEBUG
enum class Source {
BottomPlane,
TopPlane,
Slab,
};
Source source { Source::BottomPlane };
#endif // NDEBUG
};
using IntersectionLines = std::vector<IntersectionLine>;
@ -119,7 +143,7 @@ static FacetSliceType slice_facet(
// 3 vertices of the triangle, XY scaled. Z scaled or unscaled (same as slice_z).
const stl_vertex *vertices,
const stl_triangle_vertex_indices &indices,
const Vec3i &edge_neighbor,
const Vec3i &edge_ids,
const int idx_vertex_lowest,
const bool horizontal,
IntersectionLine &line_out)
@ -138,7 +162,7 @@ static FacetSliceType slice_facet(
{
int k = (idx_vertex_lowest + j) % 3;
int l = (k + 1) % 3;
edge_id = edge_neighbor(k);
edge_id = edge_ids(k);
a_id = indices[k];
a = vertices + k;
b_id = indices[l];
@ -284,11 +308,11 @@ void slice_facet_at_zs(
const std::vector<Vec3f> &mesh_vertices,
const TransformVertex &transform_vertex_fn,
const stl_triangle_vertex_indices &indices,
const Vec3i &facet_neighbors,
const Vec3i &edge_ids,
// Scaled or unscaled zs. If vertices have their zs scaled or transform_vertex_fn scales them, then zs have to be scaled as well.
const std::vector<float> &zs,
std::vector<IntersectionLines> &lines,
boost::mutex &lines_mutex)
std::array<std::mutex, 64> &lines_mutex)
{
stl_vertex vertices[3] { transform_vertex_fn(mesh_vertices[indices(0)]), transform_vertex_fn(mesh_vertices[indices(1)]), transform_vertex_fn(mesh_vertices[indices(2)]) };
@ -299,43 +323,439 @@ void slice_facet_at_zs(
// find layer extents
auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z
auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z
int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0);
for (auto it = min_layer; it != max_layer; ++ it) {
IntersectionLine il;
int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0);
if (slice_facet(*it, vertices, indices, facet_neighbors, idx_vertex_lowest, min_z == max_z, il) == FacetSliceType::Slicing &&
il.edge_type != IntersectionLine::FacetEdgeType::Horizontal) {
// Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume.
boost::lock_guard<boost::mutex> l(lines_mutex);
lines[it - zs.begin()].emplace_back(il);
// Ignore horizontal triangles. Any valid horizontal triangle must have a vertical triangle connected, otherwise the part has zero volume.
if (min_z != max_z && slice_facet(*it, vertices, indices, edge_ids, idx_vertex_lowest, false, il) == FacetSliceType::Slicing) {
assert(il.edge_type != IntersectionLine::FacetEdgeType::Horizontal);
size_t slice_id = it - zs.begin();
boost::lock_guard<std::mutex> l(lines_mutex[slice_id >> 6]);
lines[slice_id].emplace_back(il);
}
}
}
template<typename TransformVertex, typename ThrowOnCancel>
inline std::vector<IntersectionLines> slice_make_lines(
static inline std::vector<IntersectionLines> slice_make_lines(
const std::vector<stl_vertex> &vertices,
const TransformVertex &transform_vertex_fn,
const std::vector<stl_triangle_vertex_indices> &indices,
const std::vector<Vec3i> &face_neighbors,
const std::vector<Vec3i> &face_edge_ids,
const std::vector<float> &zs,
const ThrowOnCancel throw_on_cancel_fn)
{
std::vector<IntersectionLines> lines(zs.size(), IntersectionLines());
boost::mutex lines_mutex;
std::array<std::mutex, 64> lines_mutex;
tbb::parallel_for(
tbb::blocked_range<int>(0, int(indices.size())),
[&vertices, &transform_vertex_fn, &indices, &face_neighbors, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range<int> &range) {
[&vertices, &transform_vertex_fn, &indices, &face_edge_ids, &zs, &lines, &lines_mutex, throw_on_cancel_fn](const tbb::blocked_range<int> &range) {
for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) {
if ((face_idx & 0x0ffff) == 0)
throw_on_cancel_fn();
slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_neighbors[face_idx], zs, lines, lines_mutex);
slice_facet_at_zs(vertices, transform_vertex_fn, indices[face_idx], face_edge_ids[face_idx], zs, lines, lines_mutex);
}
}
);
return lines;
}
// For projecting triangle sets onto slice slabs.
struct SlabLines {
// Intersection lines of a slice with a triangle set, CCW oriented.
std::vector<IntersectionLines> at_slice;
// Projections of triangle set boundary lines into layer below (for projection from the top)
// or into layer above (for projection from the bottom).
// In both cases the intersection liens are CCW oriented.
std::vector<IntersectionLines> between_slices;
};
// Orientation of the face normal in regard to a XY plane pointing upwards.
enum class FaceOrientation : char {
// Z component of the normal is positive.
Up,
// Z component of the normal is negative.
Down,
// Z component of the normal is zero.
Vertical,
// Triangle is degenerate, thus its normal is undefined. We may want to slice the degenerate triangles
// because of the connectivity information they carry.
Degenerate
};
template<bool ProjectionFromTop>
void slice_facet_with_slabs(
// Scaled or unscaled vertices. transform_vertex_fn may scale zs.
const std::vector<Vec3f> &mesh_vertices,
const std::vector<stl_triangle_vertex_indices> &mesh_triangles,
const size_t facet_idx,
const Vec3i &facet_neighbors,
const Vec3i &facet_edge_ids,
// Increase edge_ids at the top plane of the slab edges by num_edges to allow chaining
// from bottom plane of the slab to the top plane of the slab and vice versa.
const int num_edges,
const std::vector<float> &zs,
SlabLines &lines,
std::array<std::mutex, 64> &lines_mutex)
{
const stl_triangle_vertex_indices &indices = mesh_triangles[facet_idx];
stl_vertex vertices[3] { mesh_vertices[indices(0)], mesh_vertices[indices(1)], mesh_vertices[indices(2)] };
// find facet extents
const float min_z = fminf(vertices[0].z(), fminf(vertices[1].z(), vertices[2].z()));
const float max_z = fmaxf(vertices[0].z(), fmaxf(vertices[1].z(), vertices[2].z()));
const bool horizontal = min_z == max_z;
// find layer extents
auto min_layer = std::lower_bound(zs.begin(), zs.end(), min_z); // first layer whose slice_z is >= min_z
auto max_layer = std::upper_bound(min_layer, zs.end(), max_z); // first layer whose slice_z is > max_z
assert(min_layer == zs.end() ? max_layer == zs.end() : *min_layer >= min_z);
assert(max_layer == zs.end() || *max_layer > max_z);
auto emit_slab_edge = [&lines, &lines_mutex](IntersectionLine il, size_t slab_id, bool reverse) {
if (reverse)
il.reverse();
boost::lock_guard<std::mutex> l(lines_mutex[(slab_id + 32) >> 6]);
lines.between_slices[slab_id].emplace_back(il);
};
if (min_layer == max_layer || horizontal) {
// Horizontal face or a nearly horizontal face that fits between two layers or below the bottom most or above the top most layer.
assert(horizontal || zs.empty() || max_z < zs.front() || min_z > zs.back() ||
(min_layer == max_layer && min_layer != zs.end() && min_layer != zs.begin() && *(min_layer - 1) < min_z && *min_layer > max_z));
if (horizontal && min_layer != zs.end() && *min_layer == min_z) {
// Slicing a horizontal triangle with a slicing plane. The triangle has to be upwards facing for ProjectionFromTop
// and downwards facing for ! ProjectionFromTop.
assert(min_layer != max_layer);
size_t line_id = min_layer - zs.begin();
for (int iedge = 0; iedge < 3; ++ iedge)
if (facet_neighbors(iedge) == -1) {
int i = iedge;
int j = next_idx_modulo(i, 3);
assert(vertices[i].z() == zs[line_id]);
assert(vertices[j].z() == zs[line_id]);
IntersectionLine il {
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
indices(i), indices(j), -1, -1,
ProjectionFromTop ? IntersectionLine::FacetEdgeType::Bottom : IntersectionLine::FacetEdgeType::Top
};
// Don't flip the FacetEdgeType::Top edge, it will be flipped when chaining.
// if (! ProjectionFromTop) il.reverse();
boost::lock_guard<std::mutex> l(lines_mutex[line_id >> 6]);
lines.at_slice[line_id].emplace_back(il);
}
} else {
// Triangle is completely between two slicing planes, the triangle may or may not be horizontal, which
// does not matter for the processing of such a triangle.
size_t slab_id;
if (ProjectionFromTop) {
if (max_layer == zs.begin()) {
// Not slicing the triangle and it is below the lowest layer.
return;
} else {
// Not slicing the triangle and it could be projected into a slab.
slab_id = max_layer - zs.begin();
}
} else {
// projection from bottom
if (min_layer == zs.end()) {
// Not slicing the triangle and it is above the highest layer.
return;
} else {
// Not slicing the triangle and it could be projected into a slab.
slab_id = min_layer - zs.begin();
}
}
if (ProjectionFromTop)
-- slab_id;
for (int iedge = 0; iedge < 3; ++ iedge)
if (facet_neighbors(iedge) == -1) {
int i = iedge;
int j = next_idx_modulo(i, 3);
assert(ProjectionFromTop ? vertices[i].z() >= zs[slab_id] : vertices[i].z() <= zs[slab_id]);
assert(ProjectionFromTop ? vertices[j].z() >= zs[slab_id] : vertices[j].z() <= zs[slab_id]);
emit_slab_edge(
IntersectionLine {
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab
},
slab_id, ! ProjectionFromTop);
}
}
} else {
// The triangle is not horizontal and at least a single slicing plane intersects the triangle.
int idx_vertex_lowest = (vertices[1].z() == min_z) ? 1 : ((vertices[2].z() == min_z) ? 2 : 0);
IntersectionLine il_prev;
for (auto it = min_layer; it != max_layer; ++ it) {
IntersectionLine il;
auto type = slice_facet(*it, vertices, indices, facet_edge_ids, idx_vertex_lowest, false, il);
if (type == FacetSliceType::NoSlice) {
// One and exactly one vertex is touching the slicing plane.
} else {
if (il.edge_type == IntersectionLine::FacetEdgeType::Top || il.edge_type == IntersectionLine::FacetEdgeType::Bottom) {
// The non-horizontal triangle is being sliced at one of its edges.
// If the edge is open (it does not have a neighbor), add it.
// If the edge has a neighbor, then add it as TopBottom, and do it just once.
assert(il.a_id != -1 && il.b_id != -1);
assert(il.edge_a_id == -1 && il.edge_b_id == -1);
// Identify edge ID from the edge vertices.
int edge_id;
if (type == FacetSliceType::Cutting) {
// The edge is oriented CCW along the face perimeter.
assert(il.edge_type == IntersectionLine::FacetEdgeType::Bottom);
edge_id = il.a_id == indices(0) ? 0 : il.a_id == indices(1) ? 1 : 2;
assert(il.a_id == indices(edge_id));
assert(il.b_id == indices(next_idx_modulo(edge_id, 3)));
} else {
// The edge is oriented CW along the face perimeter.
assert(il.edge_type == IntersectionLine::FacetEdgeType::Top);
edge_id = il.b_id == indices(0) ? 0 : il.b_id == indices(1) ? 1 : 2;
assert(il.b_id == indices(edge_id));
assert(il.a_id == indices(next_idx_modulo(edge_id, 3)));
}
int neighbor_idx = facet_neighbors(edge_id);
if (neighbor_idx == -1) {
// Save the open edge for sure.
type = FacetSliceType::Slicing;
} else {
const stl_triangle_vertex_indices &neighbor = mesh_triangles[neighbor_idx];
float z = *it;
#ifndef NDEBUG
int num_on_plane = (mesh_vertices[neighbor(0)].z() == z) + (mesh_vertices[neighbor(1)].z() == z) + (mesh_vertices[neighbor(2)].z() == z);
assert(num_on_plane == 2 || num_on_plane == 3);
#endif // NDEBUG
if (mesh_vertices[neighbor(0)].z() == z && mesh_vertices[neighbor(1)].z() == z && mesh_vertices[neighbor(2)].z() == z) {
// The neighbor triangle is horizontal.
// Is the corner convex or concave?
if (il.edge_type == (ProjectionFromTop ? IntersectionLine::FacetEdgeType::Top : IntersectionLine::FacetEdgeType::Bottom)) {
// Convex corner. Add this edge to both slabs, the edge is a boundary edge of both the projection patch below and
// above this slicing plane.
type = FacetSliceType::Slicing;
il.edge_type = IntersectionLine::FacetEdgeType::TopBottom;
} else {
// Concave corner. Ignore this edge, it is internal to the projection patch.
type = FacetSliceType::Cutting;
}
} else if (il.edge_type == IntersectionLine::FacetEdgeType::Top) {
// Indicate that the edge belongs to both the slab below and above the plane.
assert(type == FacetSliceType::Slicing);
il.edge_type = IntersectionLine::FacetEdgeType::TopBottom;
} else {
// Don't add this edge, as the neighbor triangle will add the same edge as FacetEdgeType::TopBottom.
assert(type == FacetSliceType::Cutting);
assert(il.edge_type == IntersectionLine::FacetEdgeType::Bottom);
}
}
}
if (type == FacetSliceType::Slicing) {
if (! ProjectionFromTop)
il.reverse();
size_t line_id = it - zs.begin();
boost::lock_guard<std::mutex> l(lines_mutex[line_id >> 6]);
lines.at_slice[line_id].emplace_back(il);
}
}
if (! ProjectionFromTop || it != zs.begin()) {
size_t slab_id = it - zs.begin();
if (ProjectionFromTop)
-- slab_id;
// Try to project unbound edges.
for (int iedge = 0; iedge < 3; ++ iedge)
if (facet_neighbors(iedge) == -1) {
// Unbound edge.
int edge_id = facet_edge_ids(iedge);
bool intersects_this = il.edge_a_id == edge_id || il.edge_b_id == edge_id;
bool intersects_prev = il_prev.edge_a_id == edge_id || il_prev.edge_b_id == edge_id;
int i = iedge;
int j = next_idx_modulo(i, 3);
assert((! intersects_this && ! intersects_prev) || vertices[j].z() != vertices[i].z());
bool edge_up = vertices[j].z() > vertices[i].z();
if (intersects_this && intersects_prev) {
// Intersects both, emit the segment between these intersections.
Line l(il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b,
il.edge_a_id == edge_id ? il.a : il.b);
emit_slab_edge(
IntersectionLine { l, -1, -1, edge_id, edge_id + num_edges, IntersectionLine::FacetEdgeType::Slab },
slab_id, ProjectionFromTop != edge_up);
} else if (intersects_this) {
// Intersects just the top plane, may touch the bottom plane.
assert((vertices[i].z() > *it && vertices[j].z() < *it) || (vertices[i].z() < *it && vertices[j].z() > *it));
assert(il.edge_a_id == edge_id || il.edge_b_id == edge_id);
emit_slab_edge(
IntersectionLine { {
to_2d(edge_up ? vertices[i] : vertices[j]).cast<coord_t>(),
il.edge_a_id == edge_id ? il.a : il.b
},
edge_up ? indices(i) : indices(j), -1, -1, edge_id + num_edges, IntersectionLine::FacetEdgeType::Slab
},
slab_id, ProjectionFromTop != edge_up);
} else if (intersects_prev) {
// Intersects just the bottom plane, may touch the top vertex.
assert(*it <= max_z);
#ifndef NDEBUG
{
auto it_prev = it;
-- it_prev;
assert((vertices[i].z() > *it_prev && vertices[j].z() < *it_prev) || (vertices[i].z() < *it_prev && vertices[j].z() > *it_prev));
}
#endif // NDEBUG
emit_slab_edge(
IntersectionLine { {
il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b,
to_2d(edge_up ? vertices[j] : vertices[i]).cast<coord_t>()
},
-1, edge_up ? indices(j) : indices(i), edge_id, -1, IntersectionLine::FacetEdgeType::Slab
},
slab_id, ProjectionFromTop != edge_up);
} else if (float zi = vertices[i].z(), zj = vertices[j].z(); zi < *it || zj < *it) {
// The edge does not intersect the current plane and it does not intersect the previous plane either.
// Both points have to be inside the slab.
assert(zi <= *it && zj <= *it);
#ifndef NDEBUG
if (type == FacetSliceType::Slicing || type == FacetSliceType::Cutting) {
// Such edge should already be processed in the code above, it shall be skipped here.
assert(indices(i) != il.b_id || indices(j) != il.a_id);
assert(indices(i) != il.a_id || indices(j) != il.b_id);
}
#endif // NDEBUG
// Is it inside the slab?
bool inside_slab = true;
if (it != min_layer) {
auto it_prev = it;
-- it_prev;
assert(*it_prev >= *min_layer && *it_prev < *it);
// One point may touch the plane below, the other must not.
inside_slab = zi > *it_prev || zj > *it_prev;
// Both points have to be inside the slab.
assert(! inside_slab || (zi >= *it_prev && zj >= *it_prev));
}
if (inside_slab) {
assert(ProjectionFromTop ? vertices[i].z() >= zs[slab_id] : vertices[i].z() <= zs[slab_id]);
assert(ProjectionFromTop ? vertices[j].z() >= zs[slab_id] : vertices[j].z() <= zs[slab_id]);
emit_slab_edge(
IntersectionLine {
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab
},
slab_id, ! ProjectionFromTop);
}
}
}
}
il_prev = il;
}
if (ProjectionFromTop || max_layer != zs.end()) {
// Try to project unbound edges above the last slicing plane to the last slab.
// Last layer slicing this triangle.
auto it = max_layer - 1;
size_t slab_id = max_layer - zs.begin();
if (ProjectionFromTop)
-- slab_id;
for (int iedge = 0; iedge < 3; ++ iedge)
if (facet_neighbors(iedge) == -1) {
// Unbound edge.
int edge_id = facet_edge_ids(iedge);
int i = iedge;
int j = next_idx_modulo(i, 3);
if (il_prev.edge_a_id == edge_id || il_prev.edge_b_id == edge_id) {
// Intersects just the bottom plane, may touch the top vertex.
assert((vertices[i].z() > *it && vertices[j].z() < *it) || (vertices[i].z() < *it && vertices[j].z() > *it));
bool edge_up = vertices[j].z() > vertices[i].z();
emit_slab_edge(
IntersectionLine{ {
il_prev.edge_a_id == edge_id ? il_prev.a : il_prev.b,
to_2d(edge_up ? vertices[j] : vertices[i]).cast<coord_t>()
},
-1, edge_up ? indices(j) : indices(i), edge_id, -1, IntersectionLine::FacetEdgeType::Slab
},
slab_id, ProjectionFromTop != edge_up);
} else if (float zi = vertices[i].z(), zj = vertices[j].z(); zi > *it || zj > *it) {
// The edge does not intersect the current plane and it does not intersect the previous plane either.
// Both points have to be inside the slab.
assert(zi >= *it && zj >= *it);
assert(max_layer == zs.end() || (zi < *max_layer && zj < *max_layer));
emit_slab_edge(
IntersectionLine{
{ to_2d(vertices[i]).cast<coord_t>(), to_2d(vertices[j]).cast<coord_t>() },
indices(i), indices(j), -1, -1, IntersectionLine::FacetEdgeType::Slab
},
slab_id, ! ProjectionFromTop);
}
}
}
}
}
// used by slice_mesh_slabs() to produce on-slice lines and between-slices lines.
// Returning top / bottom SlabLines.
template<typename ThrowOnCancel>
inline std::pair<SlabLines, SlabLines> slice_slabs_make_lines(
const std::vector<stl_vertex> &vertices,
const std::vector<stl_triangle_vertex_indices> &indices,
const std::vector<Vec3i> &face_neighbors,
const std::vector<Vec3i> &face_edge_ids,
// Total number of edges. All face_edge_ids are lower than num_edges.
// num_edges will be used to distinguish between intersections with the top and bottom plane.
const int num_edges,
const std::vector<FaceOrientation> &face_orientation,
const std::vector<float> &zs,
bool top,
bool bottom,
const ThrowOnCancel throw_on_cancel_fn)
{
std::pair<SlabLines, SlabLines> out;
SlabLines &lines_top = out.first;
SlabLines &lines_bottom = out.second;
std::array<std::mutex, 64> lines_mutex_top;
std::array<std::mutex, 64> lines_mutex_bottom;
if (top) {
lines_top.at_slice.assign(zs.size(), IntersectionLines());
lines_top.between_slices.assign(zs.size(), IntersectionLines());
}
if (bottom) {
lines_bottom.at_slice.assign(zs.size(), IntersectionLines());
lines_bottom.between_slices.assign(zs.size(), IntersectionLines());
}
tbb::parallel_for(
tbb::blocked_range<int>(0, int(indices.size())),
[&vertices, &indices, &face_neighbors, &face_edge_ids, num_edges, &face_orientation, &zs, top, bottom, &lines_top, &lines_bottom, &lines_mutex_top, &lines_mutex_bottom, throw_on_cancel_fn]
(const tbb::blocked_range<int> &range) {
for (int face_idx = range.begin(); face_idx < range.end(); ++ face_idx) {
if ((face_idx & 0x0ffff) == 0)
throw_on_cancel_fn();
FaceOrientation fo = face_orientation[face_idx];
Vec3i edge_ids = face_edge_ids[face_idx];
if (top && (fo == FaceOrientation::Up || fo == FaceOrientation::Degenerate)) {
Vec3i neighbors = face_neighbors[face_idx];
// Reset neighborship of this triangle in case the other triangle is oriented backwards from this one.
for (int i = 0; i < 3; ++ i)
if (neighbors(i) != -1) {
FaceOrientation fo2 = face_orientation[neighbors(i)];
if (fo2 != FaceOrientation::Up && fo2 != FaceOrientation::Degenerate)
neighbors(i) = -1;
}
slice_facet_with_slabs<true>(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_top, lines_mutex_top);
}
if (bottom && (fo == FaceOrientation::Down || fo == FaceOrientation::Degenerate)) {
Vec3i neighbors = face_neighbors[face_idx];
// Reset neighborship of this triangle in case the other triangle is oriented backwards from this one.
for (int i = 0; i < 3; ++ i)
if (neighbors(i) != -1) {
FaceOrientation fo2 = face_orientation[neighbors(i)];
if (fo2 != FaceOrientation::Down && fo2 != FaceOrientation::Degenerate)
neighbors(i) = -1;
}
slice_facet_with_slabs<false>(vertices, indices, face_idx, neighbors, edge_ids, num_edges, zs, lines_bottom, lines_mutex_bottom);
}
}
}
);
return out;
}
#if 0
//FIXME Should this go away? For valid meshes the function slice_facet() returns Slicing
// and sets edges of vertical triangles to produce only a single edge per pair of neighbor faces.
@ -495,6 +915,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg
if ((first_line->edge_a_id != -1 && first_line->edge_a_id == last_line->edge_b_id) ||
(first_line->a_id != -1 && first_line->a_id == last_line->b_id)) {
// The current loop is complete. Add it to the output.
assert(first_line->a == last_line->b);
loops.emplace_back(std::move(loop_pts));
#ifdef SLIC3R_TRIANGLEMESH_DEBUG
printf(" Discovered %s polygon of %d points\n", (p.is_counter_clockwise() ? "ccw" : "cw"), (int)p.points.size());
@ -513,6 +934,7 @@ static void chain_lines_by_triangle_connectivity(IntersectionLines &lines, Polyg
next_line->edge_a_id, next_line->edge_b_id, next_line->a_id, next_line->b_id,
next_line->a.x, next_line->a.y, next_line->b.x, next_line->b.y);
*/
assert(last_line->b == next_line->a);
loop_pts.emplace_back(next_line->a);
last_line = next_line;
next_line->set_skip();
@ -778,7 +1200,7 @@ static Polygons make_loops(
{
static int iRun = 0;
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-%d.svg", iRun ++).c_str(), bbox_svg);
svg.draw(union_ex(*loops));
svg.draw(union_ex(loops));
for (const OpenPolyline &pl : open_polylines)
svg.draw(Polyline(pl.points), "red");
svg.Close();
@ -795,7 +1217,7 @@ static Polygons make_loops(
{
static int iRun = 0;
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines2-%d.svg", iRun++).c_str(), bbox_svg);
svg.draw(union_ex(*loops));
svg.draw(union_ex(loops));
for (const OpenPolyline &pl : open_polylines) {
if (pl.points.empty())
continue;
@ -825,7 +1247,7 @@ static Polygons make_loops(
{
static int iRun = 0;
SVG svg(debug_out_path("TriangleMeshSlicer_make_loops-polylines-final-%d.svg", iRun++).c_str(), bbox_svg);
svg.draw(union_ex(*loops));
svg.draw(union_ex(loops));
for (const OpenPolyline &pl : open_polylines) {
if (pl.points.empty())
continue;
@ -892,6 +1314,132 @@ static std::vector<Polygons> make_loops(
return layers;
}
// used by slice_mesh_slabs() to produce loops from on-slice lines and between-slices lines.
template<bool ProjectionFromTop, typename ThrowOnCancel>
static std::vector<Polygons> make_slab_loops(
// Lines will have their flags modified.
SlabLines &lines,
// To differentiate edge IDs of the top plane from the edge IDs of the bottom plane for chaining.
int num_edges,
ThrowOnCancel throw_on_cancel)
{
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
static int iRun = 0;
++ iRun;
#endif // SLIC3R_DEBUG_SLICE_PROCESSING
assert(! lines.at_slice.empty() && lines.at_slice.size() == lines.between_slices.size());
std::vector<Polygons> layers;
layers.resize(lines.at_slice.size());
tbb::parallel_for(
tbb::blocked_range<int>(0, int(lines.at_slice.size())),
[&lines, num_edges, &layers, throw_on_cancel](const tbb::blocked_range<int> &range) {
for (int line_idx = range.begin(); line_idx < range.end(); ++ line_idx) {
if ((line_idx & 0x0ffff) == 0)
throw_on_cancel();
IntersectionLines in;
size_t nlines = lines.between_slices[line_idx].size();
int slice_below = ProjectionFromTop ? line_idx : line_idx - 1;
int slice_above = ProjectionFromTop ? line_idx + 1 : line_idx;
bool has_slice_below = ProjectionFromTop || line_idx > 0;
bool has_slice_above = ! ProjectionFromTop || line_idx + 1 < int(lines.at_slice.size());
if (has_slice_below)
nlines += lines.at_slice[slice_below].size();
if (has_slice_above)
nlines += lines.at_slice[slice_above].size();
if (nlines) {
in.reserve(nlines);
if (has_slice_below) {
for (const IntersectionLine &l : lines.at_slice[slice_below])
if (l.edge_type != IntersectionLine::FacetEdgeType::Top) {
in.emplace_back(l);
#ifndef NDEBUG
in.back().source = IntersectionLine::Source::BottomPlane;
#endif // NDEBUG
}
}
{
// Edges in between slice_below and slice_above.
#ifndef NDEBUG
size_t old_size = in.size();
#endif // NDEBUG
// Edge IDs of end points on in-between lines that touch the layer above are already increased with num_edges.
append(in, lines.between_slices[line_idx]);
#ifndef NDEBUG
for (auto it = in.begin() + old_size; it != in.end(); ++ it) {
assert(it->edge_type == IntersectionLine::FacetEdgeType::Slab);
it->source = IntersectionLine::Source::Slab;
}
#endif // NDEBUG
}
if (has_slice_above) {
for (const IntersectionLine &lsrc : lines.at_slice[slice_above])
if (lsrc.edge_type != IntersectionLine::FacetEdgeType::Bottom) {
in.emplace_back(lsrc);
auto &l = in.back();
l.reverse();
// Differentiate edge IDs of the top plane from the edge IDs of the bottom plane for chaining.
if (l.edge_a_id >= 0)
l.edge_a_id += num_edges;
if (l.edge_b_id >= 0)
l.edge_b_id += num_edges;
#ifndef NDEBUG
l.source = IntersectionLine::Source::TopPlane;
#endif // NDEBUG
}
}
if (! in.empty()) {
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
BoundingBox bbox_svg;
coordf_t stroke_width = scale_(0.02);
{
for (const IntersectionLine &line : in) {
bbox_svg.merge(line.a);
bbox_svg.merge(line.b);
}
SVG svg(debug_out_path("make_slab_loops-in-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
svg.arrows = true;
for (const IntersectionLine& line : in) {
const char* color = line.source == IntersectionLine::Source::BottomPlane ? "red" : line.source == IntersectionLine::Source::TopPlane ? "blue" : "green";
svg.draw(line, color, stroke_width);
}
svg.Close();
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
Polygons &loops = layers[line_idx];
std::vector<OpenPolyline> open_polylines;
chain_lines_by_triangle_connectivity(in, loops, open_polylines);
#ifdef SLIC3R_DEBUG_SLICE_PROCESSING
{
SVG svg(debug_out_path("make_slab_loops-out-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
svg.arrows = true;
for (const IntersectionLine& line : in) {
const char* color = line.source == IntersectionLine::Source::BottomPlane ? "red" : line.source == IntersectionLine::Source::TopPlane ? "blue" : "green";
svg.draw(line, color, stroke_width);
}
svg.draw(loops, "black");
svg.Close();
}
{
SVG svg(debug_out_path("make_slab_loops-open-polylines-%d-%d-%s.svg", iRun, line_idx, ProjectionFromTop ? "top" : "bottom").c_str(), bbox_svg);
svg.draw(loops, "black");
svg.arrows = true;
for (const OpenPolyline &open_polyline : open_polylines)
svg.draw(Polyline(open_polyline.points), "black", stroke_width);
svg.Close();
}
#endif /* SLIC3R_DEBUG_SLICE_PROCESSING */
assert(! loops.empty());
assert(open_polylines.empty());
}
}
}
}
);
return layers;
}
// Used to cut the mesh into two halves.
static ExPolygons make_expolygons_simple(std::vector<IntersectionLine> &lines)
{
@ -1055,6 +1603,44 @@ static void make_expolygons(const Polygons &loops, const float closing_radius, c
union_ex(loops, fill_type));
}
// Make a trafo for transforming the vertices. Scale up in XY, not in Z.
static inline Transform3f make_trafo_for_slicing(const Transform3d &trafo)
{
auto t = trafo;
static constexpr const double s = 1. / SCALING_FACTOR;
t.prescale(Vec3d(s, s, 1.));
return t.cast<float>();
}
static inline bool is_identity(const Transform3d &trafo)
{
return trafo.matrix() == Transform3d::Identity().matrix();
}
static std::vector<stl_vertex> transform_mesh_vertices_for_slicing(const indexed_triangle_set &mesh, const Transform3d &trafo)
{
// Copy and scale vertices in XY, don't scale in Z.
// Possibly apply the transformation.
static constexpr const double s = 1. / SCALING_FACTOR;
std::vector<stl_vertex> out(mesh.vertices);
if (is_identity(trafo)) {
// Identity.
for (stl_vertex &v : out) {
// Scale just XY, leave Z unscaled.
v.x() *= float(s);
v.y() *= float(s);
}
} else {
// Transform the vertices, scale up in XY, not in Y.
auto t = trafo;
t.prescale(Vec3d(s, s, 1.));
auto tf = t.cast<float>();
for (stl_vertex &v : out)
v = tf * v;
}
return out;
}
std::vector<Polygons> slice_mesh(
const indexed_triangle_set &mesh,
// Unscaled Zs
@ -1071,41 +1657,23 @@ std::vector<Polygons> slice_mesh(
// Instead of edge identifiers, one shall use a sorted pair of edge vertex indices.
// However facets_edges assigns a single edge ID to two triangles only, thus when factoring facets_edges out, one will have
// to make sure that no code relies on it.
std::vector<Vec3i> facets_edges = create_face_neighbors_index(mesh);
const bool identity = params.trafo.matrix() == Transform3d::Identity().matrix();
static constexpr const double s = 1. / SCALING_FACTOR;
std::vector<Vec3i> face_edge_ids = its_face_edge_ids(mesh);
if (zs.size() <= 1) {
// It likely is not worthwile to copy the vertices. Apply the transformation in place.
if (identity)
if (is_identity(params.trafo)) {
lines = slice_make_lines(
mesh.vertices, [](const Vec3f &p) { return Vec3f(scaled<float>(p.x()), scaled<float>(p.y()), p.z()); },
mesh.indices, facets_edges, zs, throw_on_cancel);
else {
// Transform the vertices, scale up in XY, not in Y.
auto t = params.trafo;
t.prescale(Vec3d(s, s, 1.));
auto tf = t.cast<float>();
lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, facets_edges, zs, throw_on_cancel);
mesh.indices, face_edge_ids, zs, throw_on_cancel);
} else {
// Transform the vertices, scale up in XY, not in Z.
Transform3f tf = make_trafo_for_slicing(params.trafo);
lines = slice_make_lines(mesh.vertices, [tf](const Vec3f &p) { return tf * p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel);
}
} else {
// Copy and scale vertices in XY, don't scale in Z.
// Possibly apply the transformation.
std::vector<stl_vertex> vertices(mesh.vertices);
if (identity) {
for (stl_vertex &v : vertices) {
// Scale just XY, leave Z unscaled.
v.x() *= float(s);
v.y() *= float(s);
}
} else {
// Transform the vertices, scale up in XY, not in Y.
auto t = params.trafo;
t.prescale(Vec3d(s, s, 1.));
auto tf = t.cast<float>();
for (stl_vertex &v : vertices)
v = tf * v;
}
lines = slice_make_lines(vertices, [](const Vec3f &p) { return p; }, mesh.indices, facets_edges, zs, throw_on_cancel);
// Copy and scale vertices in XY, don't scale in Z. Possibly apply the transformation.
lines = slice_make_lines(
transform_mesh_vertices_for_slicing(mesh, params.trafo),
[](const Vec3f &p) { return p; }, mesh.indices, face_edge_ids, zs, throw_on_cancel);
}
}
@ -1194,6 +1762,107 @@ std::vector<ExPolygons> slice_mesh_ex(
return layers;
}
// Slice a triangle set with a set of Z slabs (thick layers).
// The effect is similar to producing the usual top / bottom layers from a sliced mesh by
// subtracting layer[i] from layer[i - 1] for the top surfaces resp.
// subtracting layer[i] from layer[i + 1] for the bottom surfaces,
// with the exception that the triangle set this function processes may not cover the whole top resp. bottom surface.
// top resp. bottom surfaces are calculated only if out_top resp. out_bottom is not null.
void slice_mesh_slabs(
const indexed_triangle_set &mesh,
// Unscaled Zs
const std::vector<float> &zs,
const Transform3d &trafo,
std::vector<Polygons> *out_top,
std::vector<Polygons> *out_bottom,
std::function<void()> throw_on_cancel)
{
BOOST_LOG_TRIVIAL(debug) << "slice_mesh_slabs to polygons";
#ifdef EXPENSIVE_DEBUG_CHECKS
{
// Verify that the vertices are unique.
auto v = mesh.vertices;
std::sort(v.begin(), v.end(), [](auto &l, auto &r) {
return l.x() < r.x() || (l.x() == r.x() && (l.y() < r.y() || (l.y() == r.y() && l.z() < r.z())));
});
size_t num_duplicates = v.end() - std::unique(v.begin(), v.end());
assert(num_duplicates == 0);
}
if (0)
{
// Verify that there are no T-joints.
// The T-joints could likely be already part of the source mesh.
for (const auto &tri : mesh.indices)
for (int i = 0; i < 3; ++ i) {
int j = next_idx_modulo(i, 3);
int k = next_idx_modulo(j, 3);
auto &v1 = mesh.vertices[tri(i)];
auto &v2 = mesh.vertices[tri(j)];
auto &v3 = mesh.vertices[tri(k)];
for (auto &pt : mesh.vertices)
if (&pt != &v1 && &pt != &v2) {
assert(pt != v1 && pt != v2);
assert((pt - v1).norm() > EPSILON);
assert((pt - v2).norm() > EPSILON);
auto l2 = (v2 - v1).squaredNorm();
assert(l2 > 0);
auto t = (pt - v1).dot(v2 - v1);
if (t > 0 && t < l2) {
auto d2 = (pt - v1).squaredNorm() - sqr(t) / l2;
auto d = sqrt(std::max(d2, 0.f));
if (&pt == &v3) {
if (d < EPSILON)
printf("Degenerate triangle!\n");
} else {
assert(d > EPSILON);
}
}
}
}
}
#endif // EXPENSIVE_DEBUG_CHECKS
std::vector<stl_vertex> vertices_transformed = transform_mesh_vertices_for_slicing(mesh, trafo);
std::vector<FaceOrientation> face_orientation(mesh.indices.size(), FaceOrientation::Up);
for (const stl_triangle_vertex_indices &tri : mesh.indices) {
const Vec3f fa = vertices_transformed[tri(0)];
const Vec3f fb = vertices_transformed[tri(1)];
const Vec3f fc = vertices_transformed[tri(2)];
assert(fa != fb && fa != fc && fb != fc);
const Point a = to_2d(fa).cast<coord_t>();
const Point b = to_2d(fb).cast<coord_t>();
const Point c = to_2d(fc).cast<coord_t>();
const int64_t d = cross2((b - a).cast<int64_t>(), (c - b).cast<int64_t>());
FaceOrientation fo = FaceOrientation::Vertical;
if (d > 0)
fo = FaceOrientation::Up;
else if (d < 0)
fo = FaceOrientation::Down;
else {
// Is the triangle vertical or degenerate?
assert(d == 0);
fo = fa == fb || fa == fc || fb == fc ? FaceOrientation::Degenerate : FaceOrientation::Vertical;
}
face_orientation[&tri - mesh.indices.data()] = fo;
}
std::vector<Vec3i> face_neighbors = its_face_neighbors_par(mesh);
int num_edges;
std::vector<Vec3i> face_edge_ids = its_face_edge_ids(mesh, face_neighbors, true, &num_edges);
std::pair<SlabLines, SlabLines> lines = slice_slabs_make_lines(
vertices_transformed, mesh.indices, face_neighbors, face_edge_ids, num_edges, face_orientation, zs,
out_top != nullptr, out_bottom != nullptr, throw_on_cancel);
throw_on_cancel();
if (out_top)
*out_top = make_slab_loops<true>(lines.first, num_edges, throw_on_cancel);
if (out_bottom)
*out_bottom = make_slab_loops<false>(lines.second, num_edges, throw_on_cancel);
}
// Remove duplicates of slice_vertices, optionally triangulate the cut.
static void triangulate_slice(
indexed_triangle_set &its,
@ -1308,7 +1977,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
// To triangulate the caps after slicing.
IntersectionLines upper_lines, lower_lines;
std::vector<int> upper_slice_vertices, lower_slice_vertices;
std::vector<Vec3i> facets_edges = create_face_neighbors_index(mesh);
std::vector<Vec3i> facets_edge_ids = its_face_edge_ids(mesh);
for (int facet_idx = 0; facet_idx < int(mesh.indices.size()); ++ facet_idx) {
const stl_triangle_vertex_indices &facet = mesh.indices[facet_idx];
@ -1329,7 +1998,7 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
dst.y() = scale_(src.y());
dst.z() = src.z();
}
slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edges[facet_idx], idx_vertex_lowest, min_z == max_z, line);
slice_type = slice_facet(z, vertices_scaled, mesh.indices[facet_idx], facets_edge_ids[facet_idx], idx_vertex_lowest, min_z == max_z, line);
}
if (slice_type != FacetSliceType::NoSlice) {
@ -1371,8 +2040,8 @@ void cut_mesh(const indexed_triangle_set &mesh, float z, indexed_triangle_set *u
// get vertices starting from the isolated one
int iv = isolated_vertex;
stl_vertex v0v1, v2v0;
assert(facets_edges[facet_idx](iv) == line.edge_a_id ||facets_edges[facet_idx](iv) == line.edge_b_id);
if (facets_edges[facet_idx](iv) == line.edge_a_id) {
assert(facets_edge_ids[facet_idx](iv) == line.edge_a_id ||facets_edge_ids[facet_idx](iv) == line.edge_b_id);
if (facets_edge_ids[facet_idx](iv) == line.edge_a_id) {
v0v1 = to_3d(unscaled<float>(line.a), z);
v2v0 = to_3d(unscaled<float>(line.b), z);
} else {

View File

@ -77,6 +77,21 @@ inline std::vector<ExPolygons> slice_mesh_ex(
return slice_mesh_ex(mesh, zs, params, throw_on_cancel);
}
// Slice a triangle set with a set of Z slabs (thick layers).
// The effect is similar to producing the usual top / bottom layers from a sliced mesh by
// subtracting layer[i] from layer[i - 1] for the top surfaces resp.
// subtracting layer[i] from layer[i + 1] for the bottom surfaces,
// with the exception that the triangle set this function processes may not cover the whole top resp. bottom surface.
// top resp. bottom surfaces are calculated only if out_top resp. out_bottom is not null.
void slice_mesh_slabs(
const indexed_triangle_set &mesh,
// Unscaled Zs
const std::vector<float> &zs,
const Transform3d &trafo,
std::vector<Polygons> *out_top,
std::vector<Polygons> *out_bottom,
std::function<void()> throw_on_cancel);
void cut_mesh(
const indexed_triangle_set &mesh,
float z,

View File

@ -810,7 +810,11 @@ int TriangleSelector::push_triangle(int a, int b, int c, int source_triangle, co
void TriangleSelector::perform_split(int facet_idx, const Vec3i &neighbors, EnforcerBlockerType old_state)
{
// Reserve space for the new triangles upfront, so that the reference to this triangle will not change.
m_triangles.reserve(m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1);
{
size_t num_triangles_new = m_triangles.size() + m_triangles[facet_idx].number_of_split_sides() + 1;
if (m_triangles.capacity() < num_triangles_new)
m_triangles.reserve(next_highest_power_of_2(num_triangles_new));
}
Triangle &tr = m_triangles[facet_idx];
assert(tr.is_split());
@ -918,7 +922,7 @@ indexed_triangle_set TriangleSelector::get_facets_strict(EnforcerBlockerType sta
++ num_vertices;
out.vertices.reserve(num_vertices);
std::vector<int> vertex_map(m_vertices.size(), -1);
for (int i = 0; i < m_vertices.size(); ++ i)
for (size_t i = 0; i < m_vertices.size(); ++ i)
if (const Vertex &v = m_vertices[i]; v.ref_cnt > 0) {
vertex_map[i] = int(out.vertices.size());
out.vertices.emplace_back(v.v);
@ -958,10 +962,13 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
this->triangle_midpoint(neighbors(1), vertices(2), vertices(1)),
this->triangle_midpoint(neighbors(2), vertices(0), vertices(2)));
int splits = (midpoints(0) != -1) + (midpoints(1) != -1) + (midpoints(2) != -1);
if (splits == 0) {
switch (splits) {
case 0:
// Just emit this triangle.
out_triangles.emplace_back(vertices(0), midpoints(0), midpoints(2));
} else if (splits == 1) {
out_triangles.emplace_back(vertices(0), vertices(1), vertices(2));
break;
case 1:
{
// Split to two triangles
int i = midpoints(0) != -1 ? 2 : midpoints(1) != -1 ? 0 : 1;
int j = next_idx_modulo(i, 3);
@ -969,16 +976,19 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
this->get_facets_split_by_tjoints(
{ vertices(i), vertices(j), midpoints(j) },
{ neighbors(i),
this->neighbor_child(neighbors(j), vertices(j), vertices(k), Partition::Second),
this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::Second),
-1 },
out_triangles);
this->get_facets_split_by_tjoints(
{ midpoints(j), vertices(j), vertices(k) },
{ this->neighbor_child(neighbors(j), vertices(j), vertices(k), Partition::First),
{ midpoints(j), vertices(k), vertices(i) },
{ this->neighbor_child(neighbors(j), vertices(k), vertices(j), Partition::First),
neighbors(k),
-1 },
out_triangles);
} else if (splits == 2) {
break;
}
case 2:
{
// Split to three triangles.
int i = midpoints(0) == -1 ? 2 : midpoints(1) == -1 ? 0 : 1;
int j = next_idx_modulo(i, 3);
@ -1000,7 +1010,10 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
this->neighbor_child(neighbors(k), vertices(i), vertices(k), Partition::Second),
-1 },
out_triangles);
} else if (splits == 4) {
break;
}
default:
assert(splits == 3);
// Split to 4 triangles.
this->get_facets_split_by_tjoints(
{ vertices(0), midpoints(0), midpoints(2) },
@ -1021,6 +1034,7 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i vertices, const V
-1 },
out_triangles);
out_triangles.emplace_back(midpoints);
break;
}
}
@ -1106,6 +1120,13 @@ void TriangleSelector::deserialize(const std::pair<std::vector<std::pair<int, in
{
reset(); // dump any current state
// Reserve number of triangles as if each triangle was saved with 4 bits.
// With MMU painting this estimate may be somehow low, but better than nothing.
m_triangles.reserve(std::max(m_mesh->its.indices.size(), data.second.size() / 4));
// Number of triangles is twice the number of vertices on a large manifold mesh of genus zero.
// Here the triangles count account for both the nodes and leaves, thus the following line may overestimate.
m_vertices.reserve(std::max(m_mesh->its.vertices.size(), m_triangles.size() / 2));
// Vector to store all parents that have offsprings.
struct ProcessingInfo {
int facet_id = 0;
@ -1203,7 +1224,8 @@ bool TriangleSelector::has_facets(const std::pair<std::vector<std::pair<int, int
std::vector<int> parents_children;
parents_children.reserve(64);
for (auto [triangle_id, ibit] : data.first) {
for (const std::pair<int, int> &triangle_id_and_ibit : data.first) {
int ibit = triangle_id_and_ibit.second;
assert(ibit < data.second.size());
auto next_nibble = [&data, &ibit = ibit]() {
int n = 0;

View File

@ -39,26 +39,22 @@ enum ModelInstanceEPrintVolumeState : unsigned char;
// possibly indexed by triangles and / or quads.
class GLIndexedVertexArray {
public:
GLIndexedVertexArray() :
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{}
// Only Eigen types of Nx16 size are vectorized. This bounding box will not be vectorized.
static_assert(sizeof(Eigen::AlignedBox<float, 3>) == 24, "Eigen::AlignedBox<float, 3> is not being vectorized, thus it does not need to be aligned");
using BoundingBox = Eigen::AlignedBox<float, 3>;
GLIndexedVertexArray() { m_bounding_box.setEmpty(); }
GLIndexedVertexArray(const GLIndexedVertexArray &rhs) :
vertices_and_normals_interleaved(rhs.vertices_and_normals_interleaved),
triangle_indices(rhs.triangle_indices),
quad_indices(rhs.quad_indices),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
{ assert(! rhs.has_VBOs()); }
m_bounding_box(rhs.m_bounding_box)
{ assert(! rhs.has_VBOs()); m_bounding_box.setEmpty(); }
GLIndexedVertexArray(GLIndexedVertexArray &&rhs) :
vertices_and_normals_interleaved(std::move(rhs.vertices_and_normals_interleaved)),
triangle_indices(std::move(rhs.triangle_indices)),
quad_indices(std::move(rhs.quad_indices)),
vertices_and_normals_interleaved_VBO_id(0),
triangle_indices_VBO_id(0),
quad_indices_VBO_id(0)
m_bounding_box(rhs.m_bounding_box)
{ assert(! rhs.has_VBOs()); }
~GLIndexedVertexArray() { release_geometry(); }
@ -92,7 +88,7 @@ public:
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
this->triangle_indices = std::move(rhs.triangle_indices);
this->quad_indices = std::move(rhs.quad_indices);
this->m_bounding_box = std::move(rhs.m_bounding_box);
this->m_bounding_box = rhs.m_bounding_box;
this->vertices_and_normals_interleaved_size = rhs.vertices_and_normals_interleaved_size;
this->triangle_indices_size = rhs.triangle_indices_size;
this->quad_indices_size = rhs.quad_indices_size;
@ -147,7 +143,7 @@ public:
this->vertices_and_normals_interleaved.emplace_back(z);
this->vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
m_bounding_box.merge(Vec3f(x, y, z).cast<double>());
m_bounding_box.extend(Vec3f(x, y, z));
};
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
@ -203,10 +199,10 @@ public:
this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear();
this->quad_indices.clear();
this->m_bounding_box.reset();
vertices_and_normals_interleaved_size = 0;
triangle_indices_size = 0;
quad_indices_size = 0;
m_bounding_box.setEmpty();
}
// Shrink the internal storage to tighly fit the data stored.
@ -216,7 +212,7 @@ public:
this->quad_indices.shrink_to_fit();
}
const BoundingBoxf3& bounding_box() const { return m_bounding_box; }
const BoundingBox& bounding_box() const { return m_bounding_box; }
// Return an estimate of the memory consumed by this class.
size_t cpu_memory_used() const { return sizeof(*this) + vertices_and_normals_interleaved.capacity() * sizeof(float) + triangle_indices.capacity() * sizeof(int) + quad_indices.capacity() * sizeof(int); }
@ -235,7 +231,7 @@ public:
size_t total_memory_used() const { return this->cpu_memory_used() + this->gpu_memory_used(); }
private:
BoundingBoxf3 m_bounding_box;
BoundingBox m_bounding_box;
};
class GLVolume {
@ -355,7 +351,15 @@ public:
std::vector<size_t> offsets;
// Bounding box of this volume, in unscaled coordinates.
const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); }
BoundingBoxf3 bounding_box() const {
BoundingBoxf3 out;
if (! this->indexed_vertex_array.bounding_box().isEmpty()) {
out.min = this->indexed_vertex_array.bounding_box().min().cast<double>();
out.max = this->indexed_vertex_array.bounding_box().max().cast<double>();
out.defined = true;
};
return out;
}
void set_render_color(float r, float g, float b, float a);
void set_render_color(const float* rgba, unsigned int size);

View File

@ -470,52 +470,50 @@ void TriangleSelectorMmuGui::render(ImGuiWrapper *imgui)
{
static constexpr std::array<float, 4> seed_fill_color{0.f, 1.f, 0.44f, 1.f};
std::vector<int> color_cnt(m_iva_colors.size());
int seed_fill_cnt = 0;
for (auto &iva_color : m_iva_colors)
iva_color.release_geometry();
m_iva_seed_fill.release_geometry();
auto append_triangle = [this](GLIndexedVertexArray &iva, int &cnt, const Triangle &tr) -> void {
for (int i = 0; i < 3; ++i)
iva.push_geometry(m_vertices[tr.verts_idxs[i]].v, m_mesh->stl.facet_start[tr.source_triangle].normal);
iva.push_triangle(cnt, cnt + 1, cnt + 2);
cnt += 3;
};
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx) {
for (const Triangle &tr : m_triangles) {
if (!tr.valid() || tr.is_split() || tr.is_selected_by_seed_fill() || tr.get_state() != EnforcerBlockerType(color_idx))
continue;
append_triangle(m_iva_colors[color_idx], color_cnt[color_idx], tr);
for (const Triangle &tr : m_triangles)
if (tr.valid() && ! tr.is_split()) {
GLIndexedVertexArray *iva = nullptr;
if (tr.is_selected_by_seed_fill())
iva = &m_iva_seed_fill;
else if (int color = int(tr.get_state()); color < int(m_iva_colors.size()))
iva = &m_iva_colors[color];
if (iva) {
if (iva->vertices_and_normals_interleaved.size() + 18 > iva->vertices_and_normals_interleaved.capacity())
iva->vertices_and_normals_interleaved.reserve(next_highest_power_of_2(iva->vertices_and_normals_interleaved.size() + 18));
const Vec3f &n = m_mesh->stl.facet_start[tr.source_triangle].normal;
for (int i = 0; i < 3; ++ i) {
const Vec3f &v = m_vertices[tr.verts_idxs[i]].v;
iva->vertices_and_normals_interleaved.emplace_back(n.x());
iva->vertices_and_normals_interleaved.emplace_back(n.y());
iva->vertices_and_normals_interleaved.emplace_back(n.z());
iva->vertices_and_normals_interleaved.emplace_back(v.x());
iva->vertices_and_normals_interleaved.emplace_back(v.y());
iva->vertices_and_normals_interleaved.emplace_back(v.z());
}
}
}
}
for (const Triangle &tr : m_triangles) {
if (!tr.valid() || tr.is_split() || !tr.is_selected_by_seed_fill())
continue;
append_triangle(m_iva_seed_fill, seed_fill_cnt, tr);
}
for (auto &iva_color : m_iva_colors)
iva_color.finalize_geometry(true);
m_iva_seed_fill.finalize_geometry(true);
auto *shader = wxGetApp().get_current_shader();
auto* shader = wxGetApp().get_current_shader();
if (!shader)
return;
assert(shader->get_name() == "gouraud");
auto render = [&shader](const GLIndexedVertexArray &iva, const std::array<float, 4> &color) -> void {
if (iva.has_VBOs()) {
shader->set_uniform("uniform_color", color);
for (size_t i = 0; i <= m_iva_colors.size(); ++i)
if (GLIndexedVertexArray &iva = i == m_iva_colors.size() ? m_iva_seed_fill : m_iva_colors[i];
! iva.vertices_and_normals_interleaved.empty()) {
iva.vertices_and_normals_interleaved_size = iva.vertices_and_normals_interleaved.size();
iva.triangle_indices.assign(iva.vertices_and_normals_interleaved_size / 6, 0);
std::iota(iva.triangle_indices.begin(), iva.triangle_indices.end(), 0);
iva.triangle_indices_size = iva.triangle_indices.size();
iva.finalize_geometry(true);
shader->set_uniform("uniform_color",
(i == 0) ? m_default_volume_color : i == m_iva_colors.size() ? seed_fill_color : m_colors[i - 1]);
iva.render();
}
};
for (size_t color_idx = 0; color_idx < m_iva_colors.size(); ++color_idx)
render(m_iva_colors[color_idx], (color_idx == 0) ? m_default_volume_color : m_colors[color_idx - 1]);
render(m_iva_seed_fill, seed_fill_color);
}
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const

View File

@ -685,7 +685,7 @@ void TriangleSelectorGUI::render_debug(ImGuiWrapper* imgui)
va = &m_varrays[ORIGINAL];
cnt = &cnts[ORIGINAL];
}
else if (tr.valid) {
else if (tr.valid()) {
va = &m_varrays[SPLIT];
cnt = &cnts[SPLIT];
}