diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index a2942e5dae..6473dc5c21 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -34,14 +34,14 @@ static void append_and_translate(ExPolygons &dst, const ExPolygons &src, const P } // BBS: generate brim area by objs static void append_and_translate(ExPolygons& dst, const ExPolygons& src, - const PrintInstance& instance, const Print& print, std::map& brimAreaMap) { + const PrintInstance& instance, size_t instance_idx, std::map& brimAreaMap) { ExPolygons srcShifted = src; Point instance_shift = instance.shift_without_plate_offset(); for (size_t src_idx = 0; src_idx < srcShifted.size(); ++src_idx) srcShifted[src_idx].translate(instance_shift); srcShifted = diff_ex(srcShifted, dst); //expolygons_append(dst, temp2); - expolygons_append(brimAreaMap[instance.print_object->id()], std::move(srcShifted)); + expolygons_append(brimAreaMap[{ instance.print_object->id(), instance_idx }], std::move(srcShifted)); } static void append_and_translate(Polygons &dst, const Polygons &src, const PrintInstance &instance) { @@ -420,8 +420,7 @@ static ExPolygons make_brim_ears(const PrintObject* object, const double& flowWi //BBS: create all brims static ExPolygons outer_inner_brim_area(const Print& print, - const float no_brim_offset, std::map& brimAreaMap, - std::map& supportBrimAreaMap, + const float no_brim_offset, std::map& brimAreaMap, std::vector>& objPrintVec, std::vector& printExtruders) { @@ -470,7 +469,6 @@ static ExPolygons outer_inner_brim_area(const Print& print, ExPolygons brim_area_object; ExPolygons no_brim_area_object; - ExPolygons brim_area_support; ExPolygons no_brim_area_support; Polygons holes_object; Polygons holes_support; @@ -571,16 +569,18 @@ static ExPolygons outer_inner_brim_area(const Print& print, append(no_brim_area_object, objectIsland); brimToWrite.at(object->id()).obj = false; - for (const PrintInstance& instance : object->instances()) { + for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) { + const PrintInstance& instance = object->instances()[instance_idx]; if (!brim_area_object.empty()) - append_and_translate(brim_area, brim_area_object, instance, print, brimAreaMap); + append_and_translate(brim_area, brim_area_object, instance, instance_idx, brimAreaMap); append_and_translate(no_brim_area, no_brim_area_object, instance); append_and_translate(holes, holes_object, instance); append_and_translate(objectIslands, objectIsland, instance); } - if (brimAreaMap.find(object->id()) != brimAreaMap.end()) - expolygons_append(brim_area, brimAreaMap[object->id()]); + for (const auto& [key, areas] : brimAreaMap) + if (key.object_id == object->id()) + expolygons_append(brim_area, areas); } support_material_extruder = object->config().support_filament; if (support_material_extruder == 0 && object->has_support_material()) { @@ -592,32 +592,12 @@ static ExPolygons outer_inner_brim_area(const Print& print, if (support_material_extruder == extruderNo && brimToWrite.at(object->id()).sup) { if (!object->support_layers().empty() && object->support_layers().front()->support_type==stInnerNormal) { for (const Polygon& support_contour : object->support_layers().front()->support_fills.polygons_covered_by_spacing()) { - // Brim will not be generated for supports - /* - if (has_outer_brim) { - append(brim_area_support, diff_ex(offset_ex(support_contour, brim_width + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(support_contour, brim_offset))); - } - if (has_inner_brim || has_outer_brim) - append(no_brim_area_support, offset_ex(support_contour, 0)); - */ no_brim_area_support.emplace_back(support_contour); } } // BBS if (!object->support_layers().empty() && object->support_layers().front()->support_type == stInnerTree) { for (const ExPolygon &ex_poly : object->support_layers().front()->lslices) { - // BBS: additional brim width will be added if adhesion area is too small without brim - float brim_width_mod = ex_poly.area() / ex_poly.contour.length() < scaled_half_min_adh_length - && brim_width < scaled_flow_width ? brim_width + scaled_additional_brim_width : brim_width; - brim_width_mod = floor(brim_width_mod / scaled_flow_width / 2) * scaled_flow_width * 2; - // Brim will not be generated for supports - /* - if (has_outer_brim) { - append(brim_area_support, diff_ex(offset_ex(ex_poly.contour, brim_width_mod + brim_offset, jtRound, SCALED_RESOLUTION), offset_ex(ex_poly.contour, brim_offset))); - } - if (has_inner_brim) - append(brim_area_support, diff_ex(offset_ex(ex_poly.holes, -brim_offset), offset_ex(ex_poly.holes, -brim_width - brim_offset))); - */ if (!has_outer_brim) append(no_brim_area_support, diff_ex(offset(ex_poly.contour, no_brim_offset), ex_poly.holes)); if (!has_inner_brim && !has_outer_brim) @@ -630,13 +610,9 @@ static ExPolygons outer_inner_brim_area(const Print& print, } brimToWrite.at(object->id()).sup = false; for (const PrintInstance& instance : object->instances()) { - if (!brim_area_support.empty()) - append_and_translate(brim_area, brim_area_support, instance, print, supportBrimAreaMap); append_and_translate(no_brim_area, no_brim_area_support, instance); append_and_translate(holes, holes_support, instance); } - if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end()) - expolygons_append(brim_area, supportBrimAreaMap[object->id()]); } } } @@ -676,28 +652,27 @@ static ExPolygons outer_inner_brim_area(const Print& print, } - if (brimAreaMap.find(object->id()) != brimAreaMap.end()) { - brimAreaMap[object->id()] = diff_ex(brimAreaMap[object->id()], extruder_no_brim_area); - } + for (auto& [key, areas] : brimAreaMap) + if (key.object_id == object->id()) + areas = diff_ex(areas, extruder_no_brim_area); - if (supportBrimAreaMap.find(object->id()) != supportBrimAreaMap.end()) - supportBrimAreaMap[object->id()] = diff_ex(supportBrimAreaMap[object->id()], extruder_no_brim_area); } brim_area.clear(); for (const PrintObject* object : print.objects()) { // BBS: brim should be contacted to at least one object's island or brim area - if (brimAreaMap.find(object->id()) != brimAreaMap.end()) { + for (auto map_it = brimAreaMap.begin(); map_it != brimAreaMap.end(); ++map_it) { + if (map_it->first.object_id != object->id()) + continue; + // find other objects' brim area ExPolygons otherExPolys; - for (const PrintObject* otherObject : print.objects()) { - if ((otherObject->id() != object->id()) && (brimAreaMap.find(otherObject->id()) != brimAreaMap.end())) { - expolygons_append(otherExPolys, brimAreaMap[otherObject->id()]); - } - } + for (const auto& [other_key, other_areas] : brimAreaMap) + if (other_key != map_it->first) + expolygons_append(otherExPolys, other_areas); - auto tempArea = brimAreaMap[object->id()]; - brimAreaMap[object->id()].clear(); + auto tempArea = map_it->second; + map_it->second.clear(); for (int ia = 0; ia != tempArea.size(); ++ia) { // find this object's other brim area @@ -709,9 +684,9 @@ static ExPolygons outer_inner_brim_area(const Print& print, if (!intersection_ex(offsetedTa, objectIslands).empty() || !intersection_ex(offsetedTa, otherExPoly).empty() || !intersection_ex(offsetedTa, otherExPolys).empty()) - brimAreaMap[object->id()].push_back(tempArea[ia]); + map_it->second.push_back(tempArea[ia]); } - expolygons_append(brim_area, brimAreaMap[object->id()]); + expolygons_append(brim_area, map_it->second); } } return brim_area; @@ -890,19 +865,15 @@ ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& s //BBS: an overload of the orignal brim generator that generates the brim by obj and by extruders void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area, std::map& brimMap, - std::map& supportBrimMap, + std::map& brimMapByInstance, std::vector> &objPrintVec, std::vector& printExtruders, - std::map* objectBrimAreasOut, - std::map* supportBrimAreasOut) + std::map* objectBrimAreasByInstanceOut) { - std::map brim_width_map; - std::map brimAreaMap; - std::map supportBrimAreaMap; + std::map brimAreaMap; Flow flow = print.brim_flow(); - const auto scaled_resolution = scaled(print.config().resolution.value); ExPolygons islands_area_ex = outer_inner_brim_area(print, - float(flow.scaled_spacing()), brimAreaMap, supportBrimAreaMap, objPrintVec, printExtruders); + float(flow.scaled_spacing()), brimAreaMap, objPrintVec, printExtruders); // BBS: Find boundingbox of the first layer for (const ObjectID printObjID : print.print_object_ids()) { @@ -924,14 +895,10 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ ex_poly_translated.translate(instance.shift_without_plate_offset()); bbx.merge(get_extents(ex_poly_translated)); } - if (supportBrimAreaMap.find(printObjID) != supportBrimAreaMap.end()) { - for (const ExPolygon& ex_poly : supportBrimAreaMap.at(printObjID)) - bbx.merge(get_extents(ex_poly.contour)); - } - if (brimAreaMap.find(printObjID) != brimAreaMap.end()) { - for (const ExPolygon& ex_poly : brimAreaMap.at(printObjID)) - bbx.merge(get_extents(ex_poly.contour)); - } + for (const auto& [key, areas] : brimAreaMap) + if (key.object_id == printObjID) + for (const ExPolygon& ex_poly : areas) + bbx.merge(get_extents(ex_poly.contour)); object->firstLayerObjectBrimBoundingBox = bbx; } @@ -944,77 +911,24 @@ void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_ islands_area[iia].translate(plate_shift); // Orca: keep translated brim footprints for skirt grouping. - auto translate_area_map = [plate_shift](const std::map& src) { - std::map dst = src; + auto translate_area_map = [plate_shift](const auto& src) { + auto dst = src; for (auto& [_, areas] : dst) for (ExPolygon& area : areas) area.translate(plate_shift); return dst; }; - if (objectBrimAreasOut != nullptr) - *objectBrimAreasOut = translate_area_map(brimAreaMap); - if (supportBrimAreasOut != nullptr) - *supportBrimAreasOut = translate_area_map(supportBrimAreaMap); + if (objectBrimAreasByInstanceOut != nullptr) + *objectBrimAreasByInstanceOut = translate_area_map(brimAreaMap); - const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject && - (print.has_skirt() || print.has_infinite_skirt()); - const bool combine_brims = print.config().combine_brims.value && - !has_per_object_skirt_or_shield && - print.config().print_sequence != PrintSequence::ByObject; - - if (!combine_brims) { - // Orca: Generate brims separately when brims cannot be combined. - for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) { - if (!iter->second.empty()) { - brimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area))); - }; - } - for (auto iter = supportBrimAreaMap.begin(); iter != supportBrimAreaMap.end(); ++iter) { - if (!iter->second.empty()) { - supportBrimMap.insert(std::make_pair(iter->first, makeBrimInfill(iter->second, print, islands_area))); - }; - } - } else { - // Orca: Unified brim mode. - ExPolygons all_brims_merged; - std::vector brim_object_ids; - - // Add all object brims - for (auto& [obj_id, brims] : brimAreaMap) { - if (!brims.empty()) { - expolygons_append(all_brims_merged, brims); - brim_object_ids.push_back(obj_id); - } - } - - if (!all_brims_merged.empty()) { - // Merge all brims into a single continuous area - all_brims_merged = union_ex(all_brims_merged); - - // Apply a tiny morphological cleanup to reduce boolean-union micro-artifacts. - const float brim_cleanup_delta = std::max(float(scaled_resolution), float(SCALED_EPSILON)); - all_brims_merged = offset2_ex(all_brims_merged, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution); - - // Generate infill once for the merged brim area. - ExtrusionEntityCollection merged_brim = makeBrimInfill(all_brims_merged, print, islands_area); - - // In unified mode, assign the merged brim to a deterministic carrier object. - // Pick the first object in print order that actually contributed brim area. - ObjectID carrier_id; - bool carrier_found = false; - for (const auto& [obj_id, _extruder] : objPrintVec) { - if (std::find(brim_object_ids.begin(), brim_object_ids.end(), obj_id) != brim_object_ids.end()) { - carrier_id = obj_id; - carrier_found = true; - break; - } - } - - if (!carrier_found) - carrier_id = brim_object_ids.front(); - - brimMap[carrier_id] = std::move(merged_brim); - } + // Orca: Generate brims per object instance. If Combine brims is enabled, + // Print::_make_skirt() will join the touching ones. + for (auto iter = brimAreaMap.begin(); iter != brimAreaMap.end(); ++iter) { + if (!iter->second.empty()) { + ExtrusionEntityCollection brim = makeBrimInfill(iter->second, print, islands_area); + brimMap[iter->first.object_id].append(brim.entities); + brimMapByInstance.emplace(iter->first, std::move(brim)); + }; } } } // namespace Slic3r diff --git a/src/libslic3r/Brim.hpp b/src/libslic3r/Brim.hpp index 2afd812247..0603af20cc 100644 --- a/src/libslic3r/Brim.hpp +++ b/src/libslic3r/Brim.hpp @@ -2,6 +2,7 @@ #define slic3r_Brim_hpp_ #include "ExPolygon.hpp" +#include "ObjectID.hpp" #include "Point.hpp" #include @@ -12,17 +13,15 @@ namespace Slic3r { class Print; class ExtrusionEntityCollection; class PrintTryCancel; -class ObjectID; // Produce brim lines around those objects, that have the brim enabled. // Collect islands_area to be merged into the final 1st layer convex hull. void make_brim(const Print& print, PrintTryCancel try_cancel, Polygons& islands_area, std::map& brimMap, - std::map& supportBrimMap, + std::map& brimMapByInstance, std::vector>& objPrintVec, std::vector& printExtruders, - std::map* objectBrimAreasOut = nullptr, - std::map* supportBrimAreasOut = nullptr); + std::map* objectBrimAreasByInstanceOut = nullptr); ExtrusionEntityCollection makeBrimInfill(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area); ExtrusionEntityCollection makeBrimInfillFromPlateCoordinates(const ExPolygons& singleBrimArea, const Print& print, const Polygons& islands_area); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 24cbec3567..9d2e14f8a8 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -3591,19 +3591,19 @@ void GCode::_do_export(Print& print, GCodeOutputStream &file, ThumbnailsGenerato } this->m_objsWithBrim.clear(); - this->m_objSupportsWithBrim.clear(); m_brim_done = false; - // BBS: set that indicates objs with brim - for (auto iter = print.m_brimMap.begin(); iter != print.m_brimMap.end(); ++iter) { - if (!iter->second.empty()) - this->m_objsWithBrim.insert(iter->first); + // Orca: Track brims by instance. When a combined brim is printed, all of + // its instances are marked done together. + for (const Print::SkirtBrimGroup& group : print.skirt_brim_groups()) { + for (const Print::SkirtBrimGroup::Brim& brim : group.brims) { + if (brim.brim.empty()) + continue; + for (const ObjectInstanceID& instance : brim.instances) + this->m_objsWithBrim.insert(instance); + } } - for (auto iter = print.m_supportBrimMap.begin(); iter != print.m_supportBrimMap.end(); ++iter) { - if (!iter->second.empty()) - this->m_objSupportsWithBrim.insert(iter->first); - } - if (this->m_objsWithBrim.empty() && this->m_objSupportsWithBrim.empty()) m_brim_done = true; + if (this->m_objsWithBrim.empty()) m_brim_done = true; // SoftFever: calib if (print.calib_params().mode == CalibMode::Calib_PA_Line) { @@ -4900,25 +4900,30 @@ std::string GCode::generate_skirt(const Print &print, return gcode; } -static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id) +static size_t find_skirt_brim_group_idx(const Print& print, ObjectID object_id, size_t instance_id) { const std::vector& groups = print.skirt_brim_groups(); - for (size_t idx = 0; idx < groups.size(); ++idx) - if (std::find(groups[idx].object_ids.begin(), groups[idx].object_ids.end(), object_id) != groups[idx].object_ids.end()) + for (size_t idx = 0; idx < groups.size(); ++idx) { + const std::vector& instances = groups[idx].instances; + if (std::any_of(instances.begin(), instances.end(), [object_id, instance_id](const ObjectInstanceID& instance) { + return instance.object_id == object_id && instance.instance_id == instance_id; + })) return idx; + } return size_t(-1); } std::string GCode::generate_object_skirt_group(const Print &print, const PrintObject &object, + size_t instance_id, const LayerTools &layer_tools, const Layer& layer, unsigned int extruder_id) { - if (print.config().skirt_type != stPerObject || print.skirt_brim_groups().empty()) + if (print.config().skirt_type != stPerObject || !layer_tools.has_skirt || print.skirt_brim_groups().empty()) return {}; - const size_t group_idx = find_skirt_brim_group_idx(print, object.id()); + const size_t group_idx = find_skirt_brim_group_idx(print, object.id(), instance_id); if (group_idx == size_t(-1) || print.skirt_brim_groups()[group_idx].skirt.empty()) return {}; @@ -4930,15 +4935,15 @@ std::string GCode::generate_object_skirt_group(const Print &print, object_skirt_tools, layer, extruder_id, m_skirt_group_done[group_idx]); } -std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, bool first_layer) +std::string GCode::generate_object_brim(const Print &print, const PrintObject &object, size_t instance_id, bool first_layer) { if (!first_layer) return {}; - auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector& object_ids) { + auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector& instances) { std::string gcode; - const bool already_emitted = std::none_of(object_ids.begin(), object_ids.end(), [this](ObjectID object_id) { - return m_objsWithBrim.find(object_id) != m_objsWithBrim.end(); + const bool already_emitted = std::none_of(instances.begin(), instances.end(), [this](const ObjectInstanceID& instance) { + return m_objsWithBrim.find(instance) != m_objsWithBrim.end(); }); if (already_emitted || brim.empty()) return gcode; @@ -4950,32 +4955,22 @@ std::string GCode::generate_object_brim(const Print &print, const PrintObject &o gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); m_avoid_crossing_perimeters.use_external_mp(false); m_avoid_crossing_perimeters.disable_once(); - for (ObjectID object_id : object_ids) - m_objsWithBrim.erase(object_id); + for (const ObjectInstanceID& instance : instances) + m_objsWithBrim.erase(instance); return gcode; }; - const bool has_per_object_skirt_or_shield = print.config().skirt_type == stPerObject && - (print.has_skirt() || print.has_infinite_skirt()); - if (print.config().combine_brims && !has_per_object_skirt_or_shield && - print.config().print_sequence != PrintSequence::ByObject && print.m_brimMap.size() == 1) { - const auto brim_it = print.m_brimMap.begin(); - return emit_brim(brim_it->second, { brim_it->first }); - } - - const size_t group_idx = find_skirt_brim_group_idx(print, object.id()); + const ObjectInstanceID object_instance_id{ object.id(), instance_id }; + const size_t group_idx = find_skirt_brim_group_idx(print, object.id(), instance_id); if (group_idx != size_t(-1)) { std::string gcode; for (const Print::SkirtBrimGroup::Brim& brim : print.skirt_brim_groups()[group_idx].brims) - if (std::find(brim.object_ids.begin(), brim.object_ids.end(), object.id()) != brim.object_ids.end()) - gcode += emit_brim(brim.brim, brim.object_ids); + if (std::find(brim.instances.begin(), brim.instances.end(), object_instance_id) != brim.instances.end()) + gcode += emit_brim(brim.brim, brim.instances); return gcode; } - const auto brim_it = print.m_brimMap.find(object.id()); - if (brim_it == print.m_brimMap.end()) - return {}; - return emit_brim(brim_it->second, { object.id() }); + return {}; } // Bedslinger model. The heavier the bed load, the lower the achievable Y acceleration for a given @@ -6096,8 +6091,8 @@ LayerResult GCode::process_layer( const auto& inst = instance_to_print.print_object.instances()[instance_to_print.instance_id]; const LayerToPrint &layer_to_print = layers[instance_to_print.layer_id]; if (print_wipe_extrusions == (is_anything_overridden ? 1 : 0)) { - gcode += generate_object_skirt_group(print, instance_to_print.print_object, layer_tools, layer, extruder_id); - gcode += generate_object_brim(print, instance_to_print.print_object, first_layer); + gcode += generate_object_skirt_group(print, instance_to_print.print_object, instance_to_print.instance_id, layer_tools, layer, extruder_id); + gcode += generate_object_brim(print, instance_to_print.print_object, instance_to_print.instance_id, first_layer); } // To control print speed of the 1st object layer printed over raft interface. @@ -6152,18 +6147,6 @@ LayerResult GCode::process_layer( m_layer = layers[instance_to_print.layer_id].support_layer; m_object_layer_over_raft = false; - //BBS: print supports' brims first - if (this->m_objSupportsWithBrim.find(instance_to_print.print_object.id()) != this->m_objSupportsWithBrim.end() && !print_wipe_extrusions) { - this->set_origin(0., 0.); - m_avoid_crossing_perimeters.use_external_mp(); - for (const ExtrusionEntity* ee : print.m_supportBrimMap.at(instance_to_print.print_object.id()).entities) { - gcode += this->extrude_entity(*ee, "brim", NOZZLE_CONFIG(support_speed)); - } - m_avoid_crossing_perimeters.use_external_mp(false); - // Allow a straight travel move to the first object point. - m_avoid_crossing_perimeters.disable_once(); - this->m_objSupportsWithBrim.erase(instance_to_print.print_object.id()); - } // When starting a new object, use the external motion planner for the first travel move. const Point& offset = instance_to_print.print_object.instances()[instance_to_print.instance_id].shift; std::pair this_object_copy(&instance_to_print.print_object, offset); diff --git a/src/libslic3r/GCode.hpp b/src/libslic3r/GCode.hpp index e6cf9c914e..558d601e53 100644 --- a/src/libslic3r/GCode.hpp +++ b/src/libslic3r/GCode.hpp @@ -359,11 +359,13 @@ private: std::vector &skirt_done); std::string generate_object_skirt_group(const Print &print, const PrintObject &object, + size_t instance_id, const LayerTools &layer_tools, const Layer& layer, unsigned int extruder_id); std::string generate_object_brim(const Print &print, const PrintObject &object, + size_t instance_id, bool first_layer); LayerResult process_layer( @@ -533,8 +535,7 @@ private: // BBS LiftType to_lift_type(ZHopType z_hop_types); - std::set m_objsWithBrim; // indicates the objs with brim - std::set m_objSupportsWithBrim; // indicates the objs' supports with brim + std::set m_objsWithBrim; // indicates the object instances with brim // Cache for custom seam enforcers/blockers for each layer. SeamPlacer m_seam_placer; diff --git a/src/libslic3r/ObjectID.hpp b/src/libslic3r/ObjectID.hpp index 94fbb6a0ab..f2697b74f5 100644 --- a/src/libslic3r/ObjectID.hpp +++ b/src/libslic3r/ObjectID.hpp @@ -3,6 +3,7 @@ #include #include +#include namespace Slic3r { @@ -41,6 +42,18 @@ private: template void serialize(Archive &ar) { ar(id); } }; +struct ObjectInstanceID { + ObjectID object_id; + size_t instance_id { size_t(-1) }; + + bool operator==(const ObjectInstanceID& rhs) const { return object_id == rhs.object_id && instance_id == rhs.instance_id; } + bool operator!=(const ObjectInstanceID& rhs) const { return !(*this == rhs); } + bool operator<(const ObjectInstanceID& rhs) const + { + return object_id < rhs.object_id || (object_id == rhs.object_id && instance_id < rhs.instance_id); + } +}; + // Base for Model, ModelObject, ModelVolume, ModelInstance or ModelMaterial to provide a unique ID // to synchronize the front end (UI) with the back end (BackgroundSlicingProcess / Print / PrintObject). // Also base for Print, PrintObject, SLAPrint, SLAPrintObject to provide a unique ID for matching Model / ModelObject diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 931f993b1a..51b0a96b64 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2546,8 +2546,7 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) m_skirt_brim_groups.clear(); m_has_shared_per_object_skirt = false; m_skirt_convex_hull.clear(); - m_objectBrimAreas.clear(); - m_supportBrimAreas.clear(); + m_objectBrimAreasByInstance.clear(); m_first_layer_convex_hull.points.clear(); for (PrintObject *object : m_objects) object->m_skirt.clear(); @@ -2703,14 +2702,16 @@ void Print::process(long long *time_cost_with_cache, bool use_cache) if (!existObject && objectExtruderMap.find(print_object_ID) != objectExtruderMap.end()) objPrintVec.push_back(std::make_pair(print_object_ID, objectExtruderMap.at(print_object_ID))); } - // BBS: m_brimMap and m_supportBrimMap are used instead of m_brim to generate brim of objs and supports seperately + // Orca: Build both the old object-keyed brim map and the per-instance + // maps used by skirt/brim groups. m_brimMap.clear(); - m_supportBrimMap.clear(); + m_brimMapByInstance.clear(); m_first_layer_convex_hull.points.clear(); if (this->has_brim()) { Polygons islands_area; make_brim(*this, this->make_try_cancel(), islands_area, m_brimMap, - m_supportBrimMap, objPrintVec, printExtruders, &m_objectBrimAreas, &m_supportBrimAreas); + m_brimMapByInstance, objPrintVec, printExtruders, + &m_objectBrimAreasByInstance); for (Polygon& poly_ex : islands_area) poly_ex.douglas_peucker(SCALED_RESOLUTION); for (Polygon &poly : union_(this->first_layer_islands(), islands_area)) @@ -2859,7 +2860,8 @@ void Print::_make_skirt() Polygon hull; }; - // Orca: build one local occupied hull per object from object and support geometry up to skirt height. + // Orca: Build one local occupied hull per object from object/support + // geometry up to skirt height. Instances translate this hull later. std::vector object_convex_hulls; for (PrintObject *object : m_objects) { Points object_points; @@ -2969,49 +2971,49 @@ void Print::_make_skirt() object_hull.object->m_skirt.clear(); if (m_config.skirt_type == stCombined || m_config.skirt_type == stPerObject) { - struct SkirtGroupItem { + struct SkirtBrimGroupItem { Points occupied_points; ObjectID object_id; + size_t instance_id; bool emits_skirt; }; - // Orca: group items represent occupied first-layer areas. Object items emit skirts; - // obstacle-only items, such as wipe tower, only force nearby object groups to merge. - std::vector group_items; + // Orca: Each object instance can emit skirt/brim. Wipe tower is only an + // obstacle here; it may merge nearby items, but does not emit anything. + std::vector group_items; const coord_t grouping_offset = scale_(m_config.skirt_distance.value + m_config.skirt_loops.value * spacing); for (const ObjectSkirtHull& object_hull : object_convex_hulls) { PrintObject* object = object_hull.object; - Points occupied_points; - for (const PrintInstance &instance : object->instances()) { + std::vector object_item_indices; + object_item_indices.reserve(object->instances().size()); + for (size_t instance_idx = 0; instance_idx < object->instances().size(); ++instance_idx) { + const PrintInstance &instance = object->instances()[instance_idx]; Points copy_points = object_hull.hull.points; for (Point &pt : copy_points) pt += instance.shift; - append(occupied_points, copy_points); + if (copy_points.size() < 3) + continue; + + object_item_indices.push_back(group_items.size()); + group_items.push_back({ std::move(copy_points), object->id(), instance_idx, true }); } - auto append_brim_points = [&occupied_points](const ExPolygons& areas) { - for (const ExPolygon& area : areas) - append(occupied_points, area.contour.points); - }; - if (auto it = m_objectBrimAreas.find(object->id()); it != m_objectBrimAreas.end()) - append_brim_points(it->second); - if (auto it = m_supportBrimAreas.find(object->id()); it != m_supportBrimAreas.end()) - append_brim_points(it->second); - if (occupied_points.size() < 3) - continue; - - // Orca: include the object's brim/support-brim footprint before checking skirt collisions. - group_items.push_back({ std::move(occupied_points), object->id(), true }); + for (size_t item_idx : object_item_indices) { + const ObjectInstanceID key{ object->id(), group_items[item_idx].instance_id }; + if (auto instance_brim_it = m_objectBrimAreasByInstance.find(key); instance_brim_it != m_objectBrimAreasByInstance.end()) + for (const ExPolygon& area : instance_brim_it->second) + append(group_items[item_idx].occupied_points, area.contour.points); + } } // Orca: the wipe tower contributes occupied area, but does not emit a skirt by itself. Points wipe_tower_points = this->first_layer_wipe_tower_corners(); if (wipe_tower_points.size() >= 3) - group_items.push_back({ std::move(wipe_tower_points), ObjectID(), false }); + group_items.push_back({ std::move(wipe_tower_points), ObjectID(), size_t(-1), false }); std::vector parent(group_items.size()); std::iota(parent.begin(), parent.end(), 0); - // Orca: union-find keeps collision merging local without repeatedly rebuilding item lists. + // Orca: Use union-find so touching items can be merged while scanning. auto find_parent = [&parent](size_t idx) { while (parent[idx] != idx) { parent[idx] = parent[parent[idx]]; @@ -3026,24 +3028,24 @@ void Print::_make_skirt() parent[b] = a; }; - // Orca: combined skirt is the same grouping model with all items forced into one group. + // Orca: Combined skirt starts with all items in the same group. if (m_config.skirt_type == stCombined && !group_items.empty()) for (size_t i = 1; i < group_items.size(); ++i) unite(0, i); - auto build_grouped_points = [&]() { - struct GroupData { - Points points; - std::vector object_ids; - bool emits_skirt = false; + auto build_skirt_brim_groups = [&]() { + struct SkirtBrimGroupData { + Points points; + std::vector instances; + bool emits_skirt = false; }; - std::map grouped; + std::map grouped; for (size_t i = 0; i < group_items.size(); ++i) { - GroupData& group = grouped[find_parent(i)]; + SkirtBrimGroupData& group = grouped[find_parent(i)]; append(group.points, group_items[i].occupied_points); if (group_items[i].object_id.valid()) - group.object_ids.push_back(group_items[i].object_id); + group.instances.push_back({ group_items[i].object_id, group_items[i].instance_id }); group.emits_skirt = group.emits_skirt || group_items[i].emits_skirt; } return grouped; @@ -3052,16 +3054,18 @@ void Print::_make_skirt() bool groups_changed = m_config.skirt_type == stPerObject; while (groups_changed) { groups_changed = false; - auto grouped_points = build_grouped_points(); + auto grouped_points = build_skirt_brim_groups(); std::vector> group_envelopes; for (const auto& [root, group] : grouped_points) { if (group.points.size() < 3) continue; - // Orca: emitting groups are expanded to their final skirt reach; obstacle groups are not. + // Orca: Only skirt-emitting groups are expanded by skirt distance; + // obstacle-only groups stay at their occupied outline. Polygon envelope = Geometry::convex_hull(group.points); if (group.emits_skirt) { - // Orca: merge groups when a skirt envelope intersects another group or obstacle. + // Orca: If the expanded skirt outline touches another group + // or obstacle, merge them and run the pass again. Polygons envelopes = offset(envelope, grouping_offset, ClipperLib::jtRound, float(scale_(0.1))); if (envelopes.empty()) continue; @@ -3082,28 +3086,23 @@ void Print::_make_skirt() } } - auto make_group_brims = [this](const std::vector& group_object_ids) { + auto make_brims_for_skirt_brim_group = [this](const std::vector& group_instances) { std::vector brims; - std::vector brim_object_ids; - for (ObjectID object_id : group_object_ids) { - const auto brim_it = m_brimMap.find(object_id); - if (brim_it != m_brimMap.end() && !brim_it->second.empty()) - brim_object_ids.push_back(object_id); + std::vector brim_instances; + for (const ObjectInstanceID& instance : group_instances) { + const auto brim_it = m_brimMapByInstance.find(instance); + if (brim_it != m_brimMapByInstance.end() && !brim_it->second.empty()) + brim_instances.push_back(instance); } - const bool global_combined_brim = m_config.combine_brims && m_config.skirt_type != stPerObject && m_brimMap.size() == 1; - auto brim_owner_ids = [&group_object_ids, global_combined_brim](const std::vector& object_ids) { - return global_combined_brim ? group_object_ids : object_ids; - }; - - const bool combine_group_brims = m_config.combine_brims && brim_object_ids.size() > 1; + const bool combine_group_brims = m_config.combine_brims && brim_instances.size() > 1; if (!combine_group_brims) { - for (ObjectID object_id : brim_object_ids) - brims.push_back({ m_brimMap.at(object_id), brim_owner_ids({ object_id }) }); + for (const ObjectInstanceID& instance : brim_instances) + brims.push_back({ m_brimMapByInstance.at(instance), { instance } }); return brims; } - std::vector brim_parent(brim_object_ids.size()); + std::vector brim_parent(brim_instances.size()); std::iota(brim_parent.begin(), brim_parent.end(), 0); auto find_brim_parent = [&brim_parent](size_t idx) { while (brim_parent[idx] != idx) { @@ -3120,61 +3119,64 @@ void Print::_make_skirt() }; const coord_t brim_contact_distance = coord_t(brim_flow().scaled_spacing() * 2.); - for (size_t i = 0; i < brim_object_ids.size(); ++i) { - const auto area_i = m_objectBrimAreas.find(brim_object_ids[i]); - if (area_i == m_objectBrimAreas.end()) + for (size_t i = 0; i < brim_instances.size(); ++i) { + const auto area_i = m_objectBrimAreasByInstance.find(brim_instances[i]); + if (area_i == m_objectBrimAreasByInstance.end()) continue; - for (size_t j = i + 1; j < brim_object_ids.size(); ++j) { - const auto area_j = m_objectBrimAreas.find(brim_object_ids[j]); - if (area_j != m_objectBrimAreas.end() && + for (size_t j = i + 1; j < brim_instances.size(); ++j) { + const auto area_j = m_objectBrimAreasByInstance.find(brim_instances[j]); + if (area_j != m_objectBrimAreasByInstance.end() && !intersection_ex(offset_ex(area_i->second, brim_contact_distance, jtRound, SCALED_RESOLUTION), area_j->second).empty()) unite_brims(i, j); } } - std::map> combined_brim_ids; - for (size_t i = 0; i < brim_object_ids.size(); ++i) - combined_brim_ids[find_brim_parent(i)].push_back(brim_object_ids[i]); + std::map> combined_brim_ids; + for (size_t i = 0; i < brim_instances.size(); ++i) + combined_brim_ids[find_brim_parent(i)].push_back(brim_instances[i]); - for (const auto& [_, object_ids] : combined_brim_ids) { - if (object_ids.size() == 1) { - brims.push_back({ m_brimMap.at(object_ids.front()), brim_owner_ids(object_ids) }); + for (const auto& [_, instances] : combined_brim_ids) { + if (instances.size() == 1) { + const ObjectInstanceID& instance = instances.front(); + brims.push_back({ m_brimMapByInstance.at(instance), { instance } }); continue; } ExPolygons combined_area; - for (ObjectID object_id : object_ids) - expolygons_append(combined_area, m_objectBrimAreas.at(object_id)); + for (const ObjectInstanceID& instance : instances) + expolygons_append(combined_area, m_objectBrimAreasByInstance.at(instance)); combined_area = union_ex(combined_area); const float scaled_resolution = float(scaled(m_config.resolution.value)); const float brim_cleanup_delta = std::max(scaled_resolution, float(SCALED_EPSILON)); combined_area = offset2_ex(combined_area, brim_cleanup_delta, -brim_cleanup_delta, jtRound, scaled_resolution); Polygons islands_area; - brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), object_ids }); + brims.push_back({ makeBrimInfillFromPlateCoordinates(combined_area, *this, islands_area), instances }); } return brims; }; - auto grouped_points = build_grouped_points(); + auto grouped_points = build_skirt_brim_groups(); for (auto& [_, group] : grouped_points) { if (!group.emits_skirt || group.points.size() < 3) continue; - if (generate_skirt && m_config.skirt_type == stPerObject && group.object_ids.size() > 1) + if (generate_skirt && m_config.skirt_type == stPerObject && group.instances.size() > 1) m_has_shared_per_object_skirt = true; - // Orca: after merging, use the occupied outline directly; do not add skirt distance twice. + // Orca: Group points already include the occupied outline, so don't + // add skirt distance here again. ExtrusionEntityCollection group_skirt; if (generate_skirt) append_skirt_loops_for_hull(Geometry::convex_hull(group.points), group_skirt, true); - std::vector group_brims = make_group_brims(group.object_ids); + std::vector group_brims = make_brims_for_skirt_brim_group(group.instances); if (!group_skirt.empty()) { group_skirt.reverse(); - // Orca: keep m_skirt as a flattened compatibility mirror for preview/extents. + // Orca: Keep m_skirt filled for code that still reads a flat + // skirt collection. m_skirt.append(group_skirt.entities); } if (!group_skirt.empty() || !group_brims.empty()) - m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.object_ids), std::move(group_brims) }); + m_skirt_brim_groups.push_back({ std::move(group_skirt), std::move(group.instances), std::move(group_brims) }); } } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b46a16918e..b38a0ca058 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -17,6 +17,7 @@ #include "GCode/ThumbnailData.hpp" #include "GCode/GCodeProcessor.hpp" #include "MultiMaterialSegmentation.hpp" +#include "ObjectID.hpp" #include "libslic3r.h" #include @@ -974,7 +975,7 @@ public: [object_id](const PrintObject *obj) { return obj->id() == object_id; }); return (it == m_objects.end()) ? nullptr : *it; } - //BBS: Function to get m_brimMap; + // Orca: Old callers still expect object-keyed brim paths. std::map& get_brimMap() { return m_brimMap; } @@ -989,11 +990,11 @@ public: struct SkirtBrimGroup { struct Brim { ExtrusionEntityCollection brim; - std::vector object_ids; + std::vector instances; }; ExtrusionEntityCollection skirt; - std::vector object_ids; + std::vector instances; // Brims stay separate unless Combine brims merges colliding brims inside this group. std::vector brims; }; @@ -1296,12 +1297,12 @@ private: ExtrusionEntityCollection m_skirt; std::vector m_skirt_brim_groups; bool m_has_shared_per_object_skirt { false }; - // BBS: collecting extrusion paths to build brim by objs + // Orca: Object-keyed brim paths kept for existing code. std::map m_brimMap; - std::map m_supportBrimMap; - // Orca: cached occupied brim footprints used when grouping per-object skirts. - std::map m_objectBrimAreas; - std::map m_supportBrimAreas; + // Orca: Actual brim paths keyed by object instance. + std::map m_brimMapByInstance; + // Orca: Translated brim areas keyed by instance, used to find touching brims. + std::map m_objectBrimAreasByInstance; // Convex hull of the 1st layer extrusions. // It encompasses the object extrusions, support extrusions, skirt, brim, wipe tower. // It does NOT encompass user extrusions generated by custom G-code, diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index eaf5159d9c..571f61e661 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -376,10 +376,7 @@ SupportGeneratorLayersPtr generate_raft_base( Polygons trimming; // BBS: if first layer of support is intersected with object island, it must have the same function as brim unless in nobrim mode. // brim_object_gap is changed to 0 by default, it's no longer appropriate to use it to determine the gap of first layer support. - //if (object.has_brim()) - // trimming = offset(object.layers().front()->lslices, (float)scale_(object.config().brim_object_gap.value), SUPPORT_SURFACES_OFFSET_PARAMETERS); - //else - trimming = offset(object.layers().front()->lslices, (float)scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); + trimming = offset(object.layers().front()->lslices, (float) scale_(support_params.gap_xy_first_layer), SUPPORT_SURFACES_OFFSET_PARAMETERS); if (inflate_factor_1st_layer > SCALED_EPSILON) { // Inflate in multiple steps to avoid leaking of the support 1st layer through object walls. auto nsteps = std::max(5, int(ceil(inflate_factor_1st_layer / support_params.first_layer_flow.scaled_width()))); diff --git a/tests/fff_print/test_helpers.cpp b/tests/fff_print/test_helpers.cpp index c2e98b7777..47d21e5aad 100644 --- a/tests/fff_print/test_helpers.cpp +++ b/tests/fff_print/test_helpers.cpp @@ -399,6 +399,26 @@ std::string slice_two_cubes_apart(double gap, std::initializer_list config_items, + Print &print, Model &model) +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_deserialize_strict(config_items); + config.set_key_value("gcode_comments", new ConfigOptionBool(true)); + + ModelObject *object = model.add_object(); + object->name += "object.stl"; + object->add_volume(cube(20)); + object->add_instance()->set_offset(Vec3d(80, 80, 0)); + object->add_instance()->set_offset(Vec3d(80 + 20 + gap, 80, 0)); + object->ensure_on_bed(); + print.auto_assign_extruders(object); + + print.apply(model, config); + print.validate(); + print.set_status_silent(); +} + std::set layers_with_role(const std::string &gcode, const std::string &role) { std::set layers; diff --git a/tests/fff_print/test_helpers.hpp b/tests/fff_print/test_helpers.hpp index be7d00a522..cb3069ed02 100644 --- a/tests/fff_print/test_helpers.hpp +++ b/tests/fff_print/test_helpers.hpp @@ -108,6 +108,10 @@ void place_two_cubes_apart(double gap, std::initializer_list config_items); +// Place two instances of one 20mm cube `gap` mm apart edge-to-edge. +void place_two_cube_instances_apart(double gap, std::initializer_list config_items, + Slic3r::Print &print, Slic3r::Model &model); + // Distinct layer Z heights carrying an extrusion of the given `role` (e.g. "skirt"). std::set layers_with_role(const std::string &gcode, const std::string &role); diff --git a/tests/fff_print/test_skirt_brim.cpp b/tests/fff_print/test_skirt_brim.cpp index 9f81a21c5f..d957a4c649 100644 --- a/tests/fff_print/test_skirt_brim.cpp +++ b/tests/fff_print/test_skirt_brim.cpp @@ -86,6 +86,27 @@ TEST_CASE("Per-object skirts group when objects are close", "[SkirtBrim]") } } +TEST_CASE("Per-object skirt is generated per instance", "[SkirtBrim]") +{ + Print print; + Model model; + place_two_cube_instances_apart(60, { + { "skirt_type", "perobject" }, + { "skirt_height", 1 }, + { "skirt_distance", 2 }, + { "skirt_loops", 1 }, + { "brim_type", "no_brim" }, + }, print, model); + print.process(); + + REQUIRE(print.skirt_brim_groups().size() == 2); + REQUIRE(print.skirt().items_count() == 2); + for (const Print::SkirtBrimGroup &group : print.skirt_brim_groups()) { + REQUIRE(group.instances.size() == 1); + REQUIRE(group.instances.front().object_id == print.get_object(0)->id()); + } +} + TEST_CASE("Combine brims merges touching brims", "[SkirtBrim]") { auto [gap, combine, expected_brims] = GENERATE(table({ @@ -112,6 +133,45 @@ TEST_CASE("Combine brims merges touching brims", "[SkirtBrim]") } } +TEST_CASE("Object brims are generated per instance", "[SkirtBrim]") +{ + Print print; + Model model; + place_two_cube_instances_apart(60, { + { "skirt_loops", 0 }, + { "brim_type", "outer_only" }, + { "brim_width", 5 }, + { "combine_brims", 0 }, + }, print, model); + print.process(); + + REQUIRE(print.skirt_brim_groups().size() == 1); + REQUIRE(print.skirt_brim_groups().front().brims.size() == 2); + for (const Print::SkirtBrimGroup::Brim &brim : print.skirt_brim_groups().front().brims) { + REQUIRE(brim.instances.size() == 1); + REQUIRE(brim.instances.front().object_id == print.get_object(0)->id()); + } +} + +TEST_CASE("Combine brims merges neighboring object instances", "[SkirtBrim]") +{ + Print print; + Model model; + place_two_cube_instances_apart(5, { + { "skirt_loops", 0 }, + { "brim_type", "outer_only" }, + { "brim_width", 5 }, + { "combine_brims", 1 }, + }, print, model); + print.process(); + + REQUIRE(print.skirt_brim_groups().size() == 1); + REQUIRE(print.skirt_brim_groups().front().brims.size() == 1); + REQUIRE(print.skirt_brim_groups().front().brims.front().instances.size() == 2); + const std::vector expected{ "brim", "perimeter" }; + CHECK(role_sequence(gcode(print), { "brim", "perimeter" }) == expected); +} + // Each object's skirt and brim come right before that object, not all skirts then all brims first. TEST_CASE("By-layer per-object skirt and brim precede each object", "[SkirtBrim]") {