SPE-2714 fixed

bad merging of island parts(UniformSupportIsland.cpp:1853-56) + multi insertation of same change for part(UniformSupportIsland.cpp:2167-191)
+ add test case
+ add gui to set current head diameter and density to configurable GUI
+ add visualization of Parts
+ add check for uniqness of change in part
This commit is contained in:
Filip Sykala - NTB T15p 2025-03-07 19:27:57 +01:00 committed by Lukas Matena
parent fdcf45c02a
commit c41d4997e5
5 changed files with 1782 additions and 26 deletions

View File

@ -40,6 +40,7 @@
//#define SLA_SAMPLE_ISLAND_UTILS_STORE_ALIGNED_TO_SVG_PATH "C:/data/temp/align/island_<<COUNTER>>_aligned.svg"
//#define SLA_SAMPLE_ISLAND_UTILS_STORE_ALIGN_ONCE_TO_SVG_PATH "C:/data/temp/align_once/iter_<<COUNTER>>.svg"
//#define SLA_SAMPLE_ISLAND_UTILS_DEBUG_CELL_DISTANCE_PATH "C:/data/temp/island_cell.svg"
//#define SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH "C:/data/temp/parts.svg"
namespace {
using namespace Slic3r;
@ -1766,13 +1767,13 @@ size_t detect_interface(IslandParts &island_parts, size_t part_index, const Neig
case IslandPartType::thin:
// Near contour is type permanent no matter of width
// assert(neighbor->min_width() <= min);
if (neighbor->max_width() < min) break; // still thin part
if (neighbor->max_width() <= min) break; // still thin part
next_part_index = add_part(island_parts, part_index, IslandPartType::middle, neighbor, min, lines, config);
if (neighbor->max_width() < max) return next_part_index; // no thick part
if (neighbor->max_width() <= max) return next_part_index; // no thick part
return add_part(island_parts, next_part_index, IslandPartType::thick, neighbor, max, lines, config);
case IslandPartType::middle:
// assert(neighbor->min_width() >= min || neighbor->max_width() <= max);
if (neighbor->min_width() < min) {
if (neighbor->min_width() <= min) {
return add_part(island_parts, part_index, IslandPartType::thin, neighbor, min, lines, config);
} else if (neighbor->max_width() > max) {
return add_part(island_parts, part_index, IslandPartType::thick, neighbor, max, lines, config);
@ -1850,7 +1851,9 @@ void merge_parts_and_fix_process(IslandParts &island_parts,
// Merged parts should be the same state, it is essential for alhorithm
// Only first island part changes its type, but only before first change
assert(island_parts[index].type == island_parts[remove_index].type);
// assert(island_parts[index].type == island_parts[remove_index].type);
if (island_parts[index].type != island_parts[remove_index].type)
return; // no merge
island_parts[index].sum_lengths += island_parts[remove_index].sum_lengths;
merge_island_parts(island_parts, index, remove_index);
@ -2166,7 +2169,11 @@ std::pair<size_t, std::vector<size_t>> merge_negihbor(IslandParts &island_parts,
// collect changes from neighbors for result part + indices of neighbor parts
IslandPartChanges modified_changes;
for (const IslandPartChange &change : changes) {
remove_indices.push_back(change.part_index);
if (auto it = std::lower_bound(remove_indices.begin(), remove_indices.end(), change.part_index);
it != remove_indices.end() && *it == change.part_index)
continue; // part already added
VectorUtils::insert_sorted(remove_indices, change.part_index, std::less{});
// iterate neighbor changes and collect only changes to other neighbors
for (const IslandPartChange &n_change : island_parts[change.part_index].changes) {
if (n_change.part_index == index)
@ -2183,10 +2190,7 @@ std::pair<size_t, std::vector<size_t>> merge_negihbor(IslandParts &island_parts,
}
}
// Modified index is smallest from index or remove_indices
std::sort(remove_indices.begin(), remove_indices.end());
remove_indices.erase( // Remove duplicit inidices
std::unique(remove_indices.begin(), remove_indices.end()), remove_indices.end());
// Set modified index to smallest from index or remove_indices(are sorted)
size_t modified_index = index;
if (remove_indices.front() < index) {
std::swap(remove_indices.front(), modified_index);
@ -2330,6 +2334,145 @@ std::pair<ThinParts, ThickParts> convert_island_parts_to_thin_thick(
return result;
}
#ifdef SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
void draw(const IslandParts &parts, const ProcessItems &queue, const ProcessItem& current, const Lines& lines) {
SVG svg(SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH, LineUtils::create_bounding_box(lines));
LineUtils::draw(svg, lines, "black", 0.);
const char *thin_color = "blue";
const char *thick_color = "green";
const char *middle_color = "orange";
using Neighbor = VoronoiGraph::Node::Neighbor;
std::vector<const Neighbor *> queue_done;
auto get_neighbor = [](const VoronoiGraph::Node *from, const VoronoiGraph::Node* to)->const Neighbor* {
if (from == nullptr || to == nullptr)
return nullptr;
for (const Neighbor &n : from->neighbors)
if (n.node == to)
return &n;
return nullptr;
};
for (const ProcessItem& it : queue) {
const Neighbor *n = get_neighbor(it.prev_node, it.node);
queue_done.push_back(n);
queue_done.push_back(VoronoiGraphUtils::get_twin(*n));
}
const Neighbor *n = get_neighbor(current.prev_node, current.node);
if (n != nullptr) {
queue_done.push_back(n);
queue_done.push_back(VoronoiGraphUtils::get_twin(*n));
}
for (const IslandPart &part : parts){
for (const IslandPartChange &change : part.changes) { // add changes
queue_done.push_back(change.position.neighbor);
queue_done.push_back(VoronoiGraphUtils::get_twin(*change.position.neighbor));
}
}
auto neighbor_sort = [](const Neighbor *a, const Neighbor *b) {return a < b;};
std::sort(queue_done.begin(), queue_done.end(), neighbor_sort);
queue_done.erase(std::unique(queue_done.begin(), queue_done.end()), queue_done.end());
for (const IslandPart &part: parts){
size_t index = &part - &parts.front();
Positions ends;
const char *color =
part.type == IslandPartType::thin ? thin_color :
part.type == IslandPartType::thick ? thick_color : middle_color;
for (const IslandPartChange &change: part.changes){
if (change.part_index > index)
continue;
Point p = VoronoiGraphUtils::create_edge_point(change.position);
const auto edge = change.position.neighbor->edge;
const VD::vertex_type *v0 = edge->vertex0();
const VD::vertex_type *v1 = edge->vertex1();
Vec2d dir(v1->x() - v0->x(), v1->y() - v0->y());
dir.normalize();
dir.x() *= 0.6 / SCALING_FACTOR;
dir.y() *= 1 / SCALING_FACTOR;
Point dir_ = dir.cast<coord_t>();
auto type = parts[change.part_index].type;
const char *neighbor_color =
type == IslandPartType::thin ? thin_color :
type == IslandPartType::thick ? thick_color : middle_color;
svg.draw(p.cast<coord_t>(), "gray");
Point letter_offset(-.75/SCALING_FACTOR, -.7/SCALING_FACTOR);
svg.draw_text(letter_offset + p + dir_, std::to_string(change.part_index).c_str(), neighbor_color);
svg.draw_text(letter_offset + p - dir_, std::to_string(index).c_str(), color);
ends.push_back(change.position);
}
if (part.changes.empty())
continue;
std::vector<const Neighbor *> done = queue_done; // copy queue
std::function<void(const Neighbor *)> draw_neighbor;
draw_neighbor = [&draw_neighbor, &done, &svg, &lines, color, neighbor_sort]
(const Neighbor *neighbor) {
VectorUtils::insert_sorted(done, neighbor, neighbor_sort);
const Neighbor *twin = VoronoiGraphUtils::get_twin(*neighbor);
VectorUtils::insert_sorted(done, twin, neighbor_sort);
for (const Neighbor &n: neighbor->node->neighbors){
if (&n == twin)
continue;
if (auto it = std::lower_bound(done.begin(), done.end(), &n, neighbor_sort);
it != done.end() && *it == &n)
continue; // already done
VoronoiGraphUtils::draw(svg, *n.edge, lines, color, 3e4);
draw_neighbor(&n);//recursive call
}
};
draw_neighbor(VoronoiGraphUtils::get_twin(*part.changes.front().position.neighbor));
}
for (const ProcessItem &item : queue) {
Point from = VoronoiGraphUtils::to_point(item.prev_node->vertex);
Point to = VoronoiGraphUtils::to_point(item.node->vertex);
svg.draw(Line(from, to), "darkgray");
svg.draw(to, "darkgray");
svg.draw_text(from / 2 + to / 2, std::to_string(item.i).c_str(), "darkgray");
}
if (current.prev_node == nullptr){
if (current.node == nullptr)
return;
Point p = VoronoiGraphUtils::to_point(current.node->vertex);
svg.draw(p, "red");
svg.draw_text(p, std::to_string(current.i).c_str(), "red");
} else {
Point from = VoronoiGraphUtils::to_point(current.prev_node->vertex);
Point to = VoronoiGraphUtils::to_point(current.node->vertex);
svg.draw(Line(from, to), "red");
svg.draw(to, "red");
svg.draw_text(from / 2 + to / 2, std::to_string(current.i).c_str(), "red");
}
}
#endif // SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
#ifndef NDEBUG
bool exist_twin_change_in_part(const IslandParts &parts){
auto sort_by_neighbor = [](const IslandPartChange &c1, const IslandPartChange &c2) {
return c1.position.neighbor < c2.position.neighbor;
};
auto is_same_neighbor = [](const IslandPartChange &c1, const IslandPartChange &c2) {
return c1.position.neighbor == c2.position.neighbor;
};
for(const IslandPart &part: parts){
IslandPartChanges changes = part.changes; // copy
std::sort(changes.begin(), changes.end(), sort_by_neighbor);
if (std::unique(changes.begin(), changes.end(), is_same_neighbor) != changes.end())
return true;
}
return false;
}
#endif // DEBUG
/// <summary>
/// Separate thin(narrow) and thick(wide) part of island
/// </summary>
@ -2357,6 +2500,9 @@ std::pair<ThinParts, ThickParts> separate_thin_thick(
ProcessItem item = {/*prev_node*/ nullptr, start_node, 0}; // current processing item
ProcessItems process; // queue of nodes to process
do { // iterate over all nodes in graph and collect interfaces into island_parts
#ifdef SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
draw(island_parts, process, item, lines);
#endif // SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
assert(item.node != nullptr);
ProcessItem next_item = {nullptr, nullptr, std::numeric_limits<size_t>::max()};
for (const Neighbor &neighbor: item.node->neighbors) {
@ -2390,13 +2536,15 @@ std::pair<ThinParts, ThickParts> separate_thin_thick(
process.pop_back();
}
} while (item.node != nullptr); // loop should end by break with empty process
merge_middle_parts_into_biggest_neighbor(island_parts);
if (island_parts.size() != 1)
merge_same_neighbor_type_parts(island_parts);
if (island_parts.size() != 1)
merge_short_parts(island_parts, config.min_part_length);
assert(!exist_twin_change_in_part(island_parts));
#ifdef SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
draw(island_parts, {}, {}, lines);
#endif // SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
return convert_island_parts_to_thin_thick(island_parts, path);
}
@ -2662,6 +2810,9 @@ bool is_uniform_support_island_visualization_disabled() {
#endif
#ifdef SLA_SAMPLE_ISLAND_UTILS_STORE_ALIGNED_TO_SVG_PATH
return false;
#endif
#ifdef SLA_SAMPLE_ISLAND_UTILS_DEBUG_PARTS_PATH
return false;
#endif
return true;
}

View File

@ -1320,7 +1320,7 @@ void VoronoiGraphUtils::draw(SVG & svg,
std::stringstream ss;
ss << prefix << std::hex << reinterpret_cast<intptr_t>(addr);
std::string s = ss.str();
svg.draw_text(p, s.c_str(), color, 6);
svg.draw_text(p, s.c_str(), color, 1);
}
};
@ -1329,19 +1329,50 @@ void VoronoiGraphUtils::draw(SVG & svg,
"yellowgreen", // on way to thin (max is above thin)
"limegreen", // between (inside histerezis)
"forestgreen", // on way to thick (min is belove thick)
"darkgreen" // thick (min+max above thick)
"darkgreen", // thick (min+max above thick)
"brown" // cross histereze
};
auto get_color = [&](const VoronoiGraph::Node::Neighbor &n) {
if (n.min_width() > config.thin_max_width){
std::vector<std::string> color_description{
"thin (min+max belowe thin " + std::to_string(config.thick_min_width) + ")",
"on way to thin (max is above thin)",
"between (inside histerezis)",
"on way to thick (min is belove thick)",
"thick (min+max above thick " + std::to_string(config.thin_max_width) + ")",
"cross histereze"
};
Point legend_position = lines.front().a;
for (const Line& l: lines){
if (legend_position.x() < l.a.x())
legend_position.x() = l.a.x();
if (legend_position.y() < l.a.y())
legend_position.y() = l.a.y();
}
for (size_t i = 0; i < 6; i++) {
Point p(legend_position.x(), legend_position.y() - static_cast<coord_t>(i * 1.3 / SCALING_FACTOR));
std::string text = color_description[i] + " (" + skeleton_colors[i] + ")";
svg.draw_text(p, text.c_str(), skeleton_colors[i], 8);
}
auto get_color = [&skeleton_colors,
min_limit = config.thick_min_width,
max_limit = config.thin_max_width]
(const VoronoiGraph::Node::Neighbor &n) {
assert(n.min_width() <= n.max_width());
coord_t min_width = n.min_width();
coord_t max_width = n.max_width();
if (min_width >= max_limit){
return skeleton_colors[4];
} else if (n.max_width() < config.thick_min_width){
} else if (max_width <= min_limit){
return skeleton_colors[0];
} else if (n.min_width() < config.thin_max_width &&
n.max_width() > config.thick_min_width){
} else if (min_width >= min_limit &&
max_width <= max_limit){
return skeleton_colors[2];
} else if (n.min_width() < config.thick_min_width){
} else if (min_width <= min_limit &&
max_width >= max_limit){
return skeleton_colors[5];
} else if (min_width <= min_limit){
return skeleton_colors[1];
} else if (n.max_width() > config.thin_max_width) {
} else if (max_width >= max_limit) {
return skeleton_colors[3];
}
assert(false);
@ -1357,17 +1388,16 @@ void VoronoiGraphUtils::draw(SVG & svg,
Point to = to_point(n.edge->vertex1());
bool is_second = n.edge->vertex0() > n.edge->vertex1();
Point center = (from + to) / 2;
Point p = center + ((is_second) ? Point(0., -2e6) :
Point(0., 2e6));
Point p = center + ((is_second) ? Point(0., -2e5) : Point(0., 2e5));
print_address(p, "neighbor ptr ", (void *) &n, "gray");
if (is_second) continue;
const char *color = get_color(n);
if (pointer_caption) {
std::string width_str = "width min=" + std::to_string(n.min_width()) +
" max=" + std::to_string(n.max_width());
svg.draw_text(center + Point(-6e6, 0.), width_str.c_str(), color, 6);
svg.draw_text(center, width_str.c_str(), color, 3);
}
draw(svg, *n.edge, lines, color, width);
draw(svg, *n.edge, lines, color, 2*width);
}
}
}

View File

@ -885,8 +885,21 @@ void GLGizmoSlaSupports::draw_island_config() {
ImGui::SameLine();
ImGui::Text("head radius %.2f mm", unscale<float>(sample_config.head_radius));
bool exist_change = false;
// copied from SLAPrint::Steps::support_points()
const SLAPrintObject *po = m_c->selection_info()->print_object();
const SLAPrintObjectConfig &cfg = po->config();
float head_diameter = (cfg.support_tree_type == sla::SupportTreeType::Branching) ?
float(cfg.branchingsupport_head_front_diameter):
float(cfg.support_head_front_diameter); // SupportTreeType::Organic
std::string button_title = "apply " + std::to_string(head_diameter);
ImGui::SameLine();
if (ImGui::Button(button_title.c_str())) {
float density_relative = float(cfg.support_points_density_relative / 100.f);
sample_config = sla::SampleConfigFactory::apply_density(
sla::SampleConfigFactory::create(head_diameter), density_relative);
}
bool exist_change = false;
if (float max_for_one = unscale<float>(sample_config.max_length_for_one_support_point); // [in mm]
ImGui::InputFloat("One support", &max_for_one, .1f, 1.f, "%.2f mm")) {
sample_config.max_length_for_one_support_point = scale_(max_for_one);

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 232 KiB

View File

@ -630,3 +630,29 @@ TEST_CASE("Disable visualization", "[hide]")
CHECK(is_uniform_support_island_visualization_disabled());
}
TEST_CASE("SPE-2714 3DBenchy - Sample island with config", "[SupportIsland]") {
// set_logging_level(5);
SampleConfig cfg{
/*thin_max_distance*/ 5832568,
/*thick_inner_max_distance*/ 7290710,
/*thick_outline_max_distance*/ 5468032,
/*head_radius*/ 250000,
/*minimal_distance_from_outline*/ 250000,
/*maximal_distance_from_outline*/ 1944189,
/*max_length_for_one_support_point*/ 1869413,
/*max_length_for_two_support_points*/ 7290710,
/*max_length_ratio_for_two_support_points*/ 0.250000000f,
/*thin_max_width*/ 4673532,
/*thick_min_width*/ 4019237,
/*min_part_length*/ 5832568,
/*minimal_move*/ 100000,
/*count_iteration*/ 30,
/*max_align_distance*/ 3645355,
/*simplification_tolerance*/ 50000.000000000007
//*path*/, "C:/data/temp/islands/spe_2714_<<order>>.svg" // define OPTION_TO_STORE_ISLAND in SampleConfig.hpp
};
std::string dir = std::string(TEST_DATA_DIR PATH_SEPARATOR) + "sla_islands/";
ExPolygon island = load_svg(dir + "SPE-2714.svg"); // Bad field creation
SupportIslandPoints points = test_island_sampling(island, cfg);
CHECK(points.size() > 22); // Before fix it not finished
}