From fcd746240300cdc7bd4dadac73d1cf781147b1b6 Mon Sep 17 00:00:00 2001 From: Filip Sykala - NTB T15p Date: Thu, 7 Nov 2024 09:40:46 +0100 Subject: [PATCH] Add separation of thin and thick part of island --- .../SLA/SupportIslands/SampleConfig.hpp | 2 + .../SupportIslands/SampleConfigFactory.cpp | 1 + .../SLA/SupportIslands/SampleIslandUtils.cpp | 407 +++++++++++++++++- .../SLA/SupportIslands/VoronoiGraphUtils.cpp | 1 - 4 files changed, 408 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp index 999969f1e7..433c32b8a3 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp +++ b/src/libslic3r/SLA/SupportIslands/SampleConfig.hpp @@ -59,6 +59,8 @@ struct SampleConfig // Must be smaller or equal to max_width_for_center_support_line coord_t min_width_for_outline_support = static_cast(scale_(1.)); + // Minimal length of island's part to create tiny&thick interface + coord_t min_part_length = static_cast(scale_(1.)); // Term criteria for end of alignment // Minimal change in manhatn move of support position before termination diff --git a/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp b/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp index 6019b96725..e709eea707 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp +++ b/src/libslic3r/SLA/SupportIslands/SampleConfigFactory.cpp @@ -82,6 +82,7 @@ SampleConfig SampleConfigFactory::create(float support_head_diameter_in_mm) max_distance / 2; result.min_width_for_outline_support = result.max_width_for_center_support_line - 2 * head_diameter; result.outline_sample_distance = 3 * result.max_distance/4; + result.min_part_length = result.max_distance; // Align support points // TODO: propagate print resolution diff --git a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp index 03f6c1e466..2a15e6cb47 100644 --- a/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/SampleIslandUtils.cpp @@ -210,8 +210,6 @@ SupportIslandPoints SampleIslandUtils::uniform_cover_island( return samples; } - - Slic3r::Points SampleIslandUtils::sample_expolygon(const ExPolygon &expoly, coord_t triangle_side){ const Points &points = expoly.contour.points; assert(!points.empty()); @@ -1074,6 +1072,409 @@ SupportIslandPoints SampleIslandUtils::sample_center_circle( return result; } +/// +/// Separation of thin and thick part of island +/// +namespace { + +using VD = Slic3r::Geometry::VoronoiDiagram; +using Position = VoronoiGraph::Position; +using Positions = std::vector; +using Neighbor = VoronoiGraph::Node::Neighbor; + +/// +/// Define narrow part of island along voronoi skeleton +/// +struct ThinPart +{ + // Transition from thick to thin part (one of the ends) + // NOTE: When start.ratio <= 0 than island do not contain thick part + Position start; + + // Transition from tiny to thick part (without start position) + Positions ends; + + bool is_only_thin_part() const { return ends.empty() && start.ratio <= 0.f; } +}; +using ThinParts = std::vector; + +/// +/// Define wide(fat) part of island along voronoi skeleton +/// +struct ThickPart +{ + // Transition from Thin to thick part (one of the ends) + // NOTE: When start.ratio <= 0 than island do not contain thin part + Position start; + + // Transition from thick to thin part (without start position) + Positions ends; + + bool is_only_thick_part() const { return ends.empty() && start.ratio <= 0.f; } +}; +using ThickParts = std::vector; + +// Search for interfaces +// 1. thin to min_wide +// 2. min_wide to max_center +// 3. max_center to Thick +enum class IslandPartType { thin, middle, thick }; + +struct IslandPartChange { + Position position; + size_t part_index; +}; +using IslandPartChanges = std::vector; + +/// +/// Part of island with interfaces defined by positions +/// +struct IslandPart { + // type of island part { thin | middle | thick } + IslandPartType type; + + // Positions and index of island part change + IslandPartChanges changes; + + // sum of all lengths inside of part + // IMPROVE: better will be length of longest path + // Used as rule to connect(merge) middle part of island to its biggest neighbour + // NOTE: No solution for island with 2 biggest neighbors with same sum_lengths. + coord_t sum_lengths = 0; +}; +using IslandParts = std::vector; + +/// +/// Data for process island parts' separation +/// +struct ProcessItem { + // previously processed island node + const VoronoiGraph::Node *prev_node; + + // current island node + const VoronoiGraph::Node *node; + + // index of island part stored in island_parts + // NOTE: Can't use reference because of vector reallocation + size_t i; +}; +using ProcessItems = std::vector; + +/// +/// Add new island part +/// +/// Already existing island parts +/// Source part index +/// Type for new added part +/// Edge where appear change from one state to another +/// min or max(min_width_for_outline_support, max_width_for_center_support_line) +/// Island border +/// Minimal Island part length +/// index of new part inside island_parts +size_t add_part( + IslandParts &island_parts, + size_t part_index, + IslandPartType to_type, + const Neighbor *neighbor, + coord_t limit, + const Lines &lines, + const SampleConfig &config +) { + Position position = VoronoiGraphUtils::get_position_with_width(neighbor, limit, lines); + + // Do not create part, when it is too close to island contour + if (VoronoiGraphUtils::ends_in_distanace(position, config.min_part_length)) + return part_index; // too close to border to add part, nothing to add + + size_t new_part_index = island_parts.size(); + const Neighbor *twin = VoronoiGraphUtils::get_twin(*neighbor); + Position twin_position(twin, 1. - position.ratio); + + if (new_part_index == 1) { // Exist only initial island + // NOTE: First island part is from start shorter than SampleConfig::min_part_length + // Which is different to rest of island. + + if (VoronoiGraphUtils::ends_in_distanace(twin_position, config.min_part_length)) { + // First island is too close to border to create new island part + // First island is initialy set set thin, + // but correct type is same as type in short length distance from start + island_parts.front().type = to_type; + return part_index; + } + } + + island_parts[part_index].changes.push_back({position, new_part_index}); + island_parts[part_index].sum_lengths += position.calc_distance(); + + coord_t sum_lengths = twin_position.calc_distance(); + IslandPartChanges changes{IslandPartChange{twin_position, part_index}}; + island_parts.push_back({to_type, changes, sum_lengths}); + return new_part_index; +} + +/// +/// Detect interface between thin, middle and thick part of island +/// +/// Already existing parts +/// current part index +/// current neigbor to investigate +/// Island contour +/// Configuration of hysterezis +/// Next part index +size_t detect_interface(IslandParts &island_parts, size_t item_i, const Neighbor *neighbor, const Lines &lines, const SampleConfig &config) { + // Range for of hysterezis between thin and thick part of island + coord_t min = config.min_width_for_outline_support; + coord_t max = config.max_width_for_center_support_line; + + size_t next_part_index = item_i; + switch (island_parts[item_i].type) { + case IslandPartType::thin: + assert(neighbor->min_width() <= min); + if (neighbor->max_width() < min) break; // still thin part + next_part_index = add_part(island_parts, item_i, IslandPartType::middle, neighbor, min, lines, config); + if (next_part_index == item_i) break; // short part of island + 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) { + return add_part(island_parts, item_i, IslandPartType::thin, neighbor, min, lines, config); + } else if (neighbor->max_width() > max) { + return add_part(island_parts, item_i, IslandPartType::thick, neighbor, max, lines, config); + } + break;// still middle part + case IslandPartType::thick: + assert(neighbor->max_width() >= max); + if (neighbor->max_width() > max) break; // still thick part + next_part_index = add_part(island_parts, item_i, IslandPartType::middle, neighbor, max, lines, config); + if (next_part_index == item_i) break; // short part of island + if (neighbor->min_width() > min) return next_part_index; // no thin part + return add_part(island_parts, next_part_index, IslandPartType::thin, neighbor, min, lines, config); + default: assert(false); // unknown part type + } + + // without new interface between island parts + island_parts[item_i].sum_lengths += static_cast(neighbor->length()); + return item_i; +} + +/// +/// Merge two island parts defined by index +/// NOTE: Do not sum IslandPart::sum_lengths on purpose to be independent on the merging order +/// +/// All parts +/// Merge into +/// Merge from +void merge_island_parts(IslandParts &island_parts, size_t index, size_t remove_index){ + // merge part interfaces + IslandPartChanges &changes = island_parts[index].changes; + IslandPartChanges &remove_changes = island_parts[remove_index].changes; + + // remove changes back to merged part + auto remove_changes_end = std::remove_if(remove_changes.begin(), remove_changes.end(), + [index](const IslandPartChange &change) { return change.part_index == index; }); + + // remove changes into removed part + changes.erase(std::remove_if(changes.begin(), changes.end(), + [index](const IslandPartChange &change) { return change.part_index == index; }), + changes.end()); + + // move changes from remove part to merged part + changes.insert(changes.end(), + std::move_iterator(remove_changes.begin()), + std::move_iterator(remove_changes_end)); + + // remove island part + island_parts.erase(island_parts.begin() + remove_index); + + // fix indices inside island part changes + for (IslandPart &island_part : island_parts) { + for (IslandPartChange &change : island_part.changes) { + if (change.part_index == remove_index) + change.part_index = index; + else if (change.part_index > remove_index) + --change.part_index; + } + } +} + +/// +/// When apper loop back to already processed part of island graph this function merge island parts +/// +/// All island parts +/// To fix index +/// Index into island parts to merge +/// Index into island parts to merge +/// Queue of future processing +void merge_parts_and_fix_process(IslandParts &island_parts, + ProcessItem &item, size_t index, size_t remove_index, ProcessItems &process) { + if (remove_index == index) return; // nothing to merge, loop connect to itself + if (remove_index < index) // remove part with bigger index + std::swap(remove_index, index); + + // merged parts should be the same state, it is essential for alhorithm + assert(island_parts[index].type == island_parts[remove_index].type); + island_parts[index].sum_lengths += island_parts[remove_index].sum_lengths; + merge_island_parts(island_parts, index, remove_index); + + // fix indices in process queue + for (ProcessItem &p : process) + if (p.i == remove_index) + p.i = index; // swap to new index + else if (p.i > remove_index) + --p.i; // decrease index + + // fix index for current item + if (item.i > remove_index) + --item.i; // decrease index +} + +void merge_middle_parts_into_biggest_neighbor(IslandParts& island_parts) { + // Connect parts till there is no middle parts + for (size_t index = 0; index < island_parts.size(); ++index) { + const IslandPart &island_part = island_parts[index]; + if (island_part.type != IslandPartType::middle) continue; // only middle parts + // there must be change into middle part island always start as thin part + assert(!island_part.changes.empty()); + if (island_part.changes.empty()) continue; // weird situation + // find biggest neighbor island part + auto max_change = std::max_element(island_part.changes.begin(), island_part.changes.end(), + [&island_parts](const IslandPartChange &a, const IslandPartChange &b) { + return island_parts[a.part_index].sum_lengths < + island_parts[b.part_index].sum_lengths;}); + // NOTE: be carefull, function remove island part inside island_parts + merge_island_parts(island_parts, max_change->part_index, index); + --index; // repeat with same index + } +} + +void merge_same_neighbor_type_parts(IslandParts &island_parts) { + // connect neighbor parts with same type + for (size_t island_part_index = 0; island_part_index < island_parts.size(); ++island_part_index) { + assert(island_part.type != IslandPartType::middle); // only thin or thick parts + while (true) { + const IslandPart &island_part = island_parts[island_part_index]; + const IslandPartChanges &changes = island_part.changes; + auto change_it = std::find_if(changes.begin(), changes.end(), + [&island_parts, type = island_part.type](const IslandPartChange &change) { + return island_parts[change.part_index].type == type;}); + if (change_it == changes.end()) break; // no more changes + merge_island_parts(island_parts, island_part_index, change_it->part_index); + } + } +} + +std::pair convert_island_parts_to_thin_thick( + const IslandParts& island_parts, const Neighbor* start_neighbor) +{ + // always must be at least one island part + assert(!island_parts.empty()); + // when exist only one change there can't be any changes + assert(island_parts.size() != 1 || island_parts.front().changes.empty()); + // convert island parts into result + if (island_parts.size() == 1) + return island_parts.front().type == IslandPartType::thin ? + std::make_pair(ThinParts{ThinPart{Position{start_neighbor, -1.f}, /*ends*/ {}}}, ThickParts{}) : + std::make_pair(ThinParts{}, ThickParts{ThickPart{Position{start_neighbor, -1.f}, /*ends*/ {}}}); + + std::pair result; + ThinParts& thin_parts = result.first; + ThickParts& thick_parts = result.second; + for (const IslandPart& i:island_parts) { + if (i.type == IslandPartType::thin) { + ThinPart thin_part; + thin_part.start = i.changes.front().position; + thin_parts.reserve(i.changes.size() - 1); + std::transform(i.changes.begin()+1, i.changes.end(), std::back_inserter(thin_part.ends), + [](const IslandPartChange &change) { return change.position; }); + thin_parts.push_back(thin_part); + } else { + assert(i.type == IslandPartType::thick); + ThickPart thick_part; + thick_part.start = i.changes.front().position; + thick_parts.reserve(i.changes.size() - 1); + std::transform(i.changes.begin() + 1, i.changes.end(), std::back_inserter(thick_part.ends), + [](const IslandPartChange &change) { return change.position; }); + thick_parts.push_back(thick_part); + } + } + return result; +} + +/// +/// Separate thin(narrow) and thick(wide) part of island +/// +/// Longest path over island +/// Island border +/// Define border between thin and thick part +/// Thin and thick parts +std::pair separate_thin_thick( + const VoronoiGraph::ExPath &path, const Lines &lines, const SampleConfig &config +) { + // Check input + assert(!path.nodes.empty()); + assert(lines.size() >= 3); // at least triangle + assert(SampleConfigFactory::verify(config)); + + // Start dividing on some border of island + const VoronoiGraph::Node *start_node = path.nodes.front(); + + // CHECK that front of path is outline node + assert(start_node->neighbors.size() == 1); + if (start_node->neighbors.size() != 1) + return {}; + + const Neighbor *start_neighbor = &start_node->neighbors.front(); + assert(start_neighbor->min_width() == 0); // first neighbor must be from outline node + if (start_neighbor->min_width() != 0) + return {}; + + IslandParts island_parts{IslandPart{IslandPartType::thin, /*changes*/{}, /*sum_lengths*/0}}; + ProcessItem item = {start_node, start_neighbor->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 + assert(item.node != nullptr); + ProcessItem next_item{nullptr, nullptr, -1}; + for (const Neighbor &neighbor: item.node->neighbors) { + if (neighbor.node == item.prev_node) continue; // already done + if (next_item.node != nullptr) // already prepared item is stored into queue + process.push_back(next_item); + + size_t next_part_index = detect_interface(island_parts, item.i, &neighbor, lines, config); + next_item = ProcessItem{item.node, neighbor.node, next_part_index}; + + // exist loop back? + auto is_oposit_item = [&next_item](const ProcessItem &p) { + return p.node == next_item.prev_node && p.prev_node == next_item.node;}; + if (auto process_it = std::find_if(process.begin(), process.end(), is_oposit_item); + process_it != process.end()) { + // solve loop back + next_item.node = nullptr; + merge_parts_and_fix_process(island_parts, item, process_it->i, next_item.i, process); + // branch is already processed + process.erase(process_it); + continue; + } + } + // Select next node to process + if (next_item.node != nullptr) { + item = next_item; // copy + } else { + if (process.empty()) + break; // no more nodes to process + item = process.back(); // copy + process.pop_back(); + } + } while (item.node != nullptr); // loop should end by break with empty process + + merge_middle_parts_into_biggest_neighbor(island_parts); + merge_same_neighbor_type_parts(island_parts); + + return convert_island_parts_to_thin_thick(island_parts, start_neighbor); +} + +} // namespace + SupportIslandPoints SampleIslandUtils::sample_expath( const VoronoiGraph::ExPath &path, const Lines & lines, @@ -1113,6 +1514,8 @@ SupportIslandPoints SampleIslandUtils::sample_expath( // TODO: 3) Triangle of points // eval outline and find three point create almost equilateral triangle + auto [thin, thick] = separate_thin_thick(path, lines, config); + // IMPROVE: Erase continous sampling: Extract ExPath and than sample uniformly whole ExPath CenterStarts center_starts; const VoronoiGraph::Node *start_node = path.nodes.front(); diff --git a/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp b/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp index dc5f1b7551..871ee2f1a8 100644 --- a/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp +++ b/src/libslic3r/SLA/SupportIslands/VoronoiGraphUtils.cpp @@ -1167,7 +1167,6 @@ coord_t VoronoiGraphUtils::get_max_width(const VoronoiGraph::Node *node) return max; } -// START use instead of is_last_neighbor bool VoronoiGraphUtils::ends_in_distanace(const VoronoiGraph::Position &position, coord_t max_distance) { const VoronoiGraph::Node *node = position.neighbor->node; coord_t rest_distance = max_distance - position.calc_rest_distance();