Fix skirt and brim handling for duplicated objects (instances) (#14683)

This commit is contained in:
Kiss Lorand
2026-07-19 01:57:56 +03:00
committed by GitHub
parent 3926475bb7
commit d6ca71be95
11 changed files with 267 additions and 273 deletions

View File

@@ -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<ObjectID, ExPolygons>& brimAreaMap) {
const PrintInstance& instance, size_t instance_idx, std::map<ObjectInstanceID, ExPolygons>& 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<ObjectID, ExPolygons>& brimAreaMap,
std::map<ObjectID, ExPolygons>& supportBrimAreaMap,
const float no_brim_offset, std::map<ObjectInstanceID, ExPolygons>& brimAreaMap,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& 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<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
std::vector<std::pair<ObjectID, unsigned int>> &objPrintVec,
std::vector<unsigned int>& printExtruders,
std::map<ObjectID, ExPolygons>* objectBrimAreasOut,
std::map<ObjectID, ExPolygons>* supportBrimAreasOut)
std::map<ObjectInstanceID, ExPolygons>* objectBrimAreasByInstanceOut)
{
std::map<ObjectID, double> brim_width_map;
std::map<ObjectID, ExPolygons> brimAreaMap;
std::map<ObjectID, ExPolygons> supportBrimAreaMap;
std::map<ObjectInstanceID, ExPolygons> brimAreaMap;
Flow flow = print.brim_flow();
const auto scaled_resolution = scaled<double>(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<ObjectID, ExPolygons>& src) {
std::map<ObjectID, ExPolygons> 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<ObjectID> 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

View File

@@ -2,6 +2,7 @@
#define slic3r_Brim_hpp_
#include "ExPolygon.hpp"
#include "ObjectID.hpp"
#include "Point.hpp"
#include<map>
@@ -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<ObjectID, ExtrusionEntityCollection>& brimMap,
std::map<ObjectID, ExtrusionEntityCollection>& supportBrimMap,
std::map<ObjectInstanceID, ExtrusionEntityCollection>& brimMapByInstance,
std::vector<std::pair<ObjectID, unsigned int>>& objPrintVec,
std::vector<unsigned int>& printExtruders,
std::map<ObjectID, ExPolygons>* objectBrimAreasOut = nullptr,
std::map<ObjectID, ExPolygons>* supportBrimAreasOut = nullptr);
std::map<ObjectInstanceID, ExPolygons>* 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);

View File

@@ -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<Print::SkirtBrimGroup>& 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<ObjectInstanceID>& 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<ObjectID>& object_ids) {
auto emit_brim = [this](const ExtrusionEntityCollection& brim, const std::vector<ObjectInstanceID>& 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<const PrintObject*, Point> this_object_copy(&instance_to_print.print_object, offset);

View File

@@ -359,11 +359,13 @@ private:
std::vector<coordf_t> &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<ObjectID> m_objsWithBrim; // indicates the objs with brim
std::set<ObjectID> m_objSupportsWithBrim; // indicates the objs' supports with brim
std::set<ObjectInstanceID> m_objsWithBrim; // indicates the object instances with brim
// Cache for custom seam enforcers/blockers for each layer.
SeamPlacer m_seam_placer;

View File

@@ -3,6 +3,7 @@
#include <cereal/access.hpp>
#include <cereal/types/base_class.hpp>
#include <cstddef>
namespace Slic3r {
@@ -41,6 +42,18 @@ private:
template<class Archive> 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

View File

@@ -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<ObjectSkirtHull> 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<SkirtGroupItem> 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<SkirtBrimGroupItem> 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<size_t> 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<size_t> 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<ObjectID> object_ids;
bool emits_skirt = false;
auto build_skirt_brim_groups = [&]() {
struct SkirtBrimGroupData {
Points points;
std::vector<ObjectInstanceID> instances;
bool emits_skirt = false;
};
std::map<size_t, GroupData> grouped;
std::map<size_t, SkirtBrimGroupData> 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<std::pair<size_t, Polygon>> 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<ObjectID>& group_object_ids) {
auto make_brims_for_skirt_brim_group = [this](const std::vector<ObjectInstanceID>& group_instances) {
std::vector<SkirtBrimGroup::Brim> brims;
std::vector<ObjectID> 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<ObjectInstanceID> 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<ObjectID>& 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<size_t> brim_parent(brim_object_ids.size());
std::vector<size_t> 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<size_t, std::vector<ObjectID>> 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<size_t, std::vector<ObjectInstanceID>> 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<SkirtBrimGroup::Brim> group_brims = make_group_brims(group.object_ids);
std::vector<SkirtBrimGroup::Brim> 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) });
}
}
}

View File

@@ -17,6 +17,7 @@
#include "GCode/ThumbnailData.hpp"
#include "GCode/GCodeProcessor.hpp"
#include "MultiMaterialSegmentation.hpp"
#include "ObjectID.hpp"
#include "libslic3r.h"
#include <Eigen/Geometry>
@@ -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<ObjectID, ExtrusionEntityCollection>&
get_brimMap() { return m_brimMap; }
@@ -989,11 +990,11 @@ public:
struct SkirtBrimGroup {
struct Brim {
ExtrusionEntityCollection brim;
std::vector<ObjectID> object_ids;
std::vector<ObjectInstanceID> instances;
};
ExtrusionEntityCollection skirt;
std::vector<ObjectID> object_ids;
std::vector<ObjectInstanceID> instances;
// Brims stay separate unless Combine brims merges colliding brims inside this group.
std::vector<Brim> brims;
};
@@ -1296,12 +1297,12 @@ private:
ExtrusionEntityCollection m_skirt;
std::vector<SkirtBrimGroup> 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<ObjectID, ExtrusionEntityCollection> m_brimMap;
std::map<ObjectID, ExtrusionEntityCollection> m_supportBrimMap;
// Orca: cached occupied brim footprints used when grouping per-object skirts.
std::map<ObjectID, ExPolygons> m_objectBrimAreas;
std::map<ObjectID, ExPolygons> m_supportBrimAreas;
// Orca: Actual brim paths keyed by object instance.
std::map<ObjectInstanceID, ExtrusionEntityCollection> m_brimMapByInstance;
// Orca: Translated brim areas keyed by instance, used to find touching brims.
std::map<ObjectInstanceID, ExPolygons> 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,

View File

@@ -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())));

View File

@@ -399,6 +399,26 @@ std::string slice_two_cubes_apart(double gap, std::initializer_list<Slic3r::Conf
return gcode(print);
}
void place_two_cube_instances_apart(double gap, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> 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<double> layers_with_role(const std::string &gcode, const std::string &role)
{
std::set<double> layers;

View File

@@ -108,6 +108,10 @@ void place_two_cubes_apart(double gap, std::initializer_list<Slic3r::ConfigBase:
// Slice two 20mm cubes `gap` mm apart (not auto-arranged) and return the G-code.
std::string slice_two_cubes_apart(double gap, std::initializer_list<Slic3r::ConfigBase::SetDeserializeItem> 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<Slic3r::ConfigBase::SetDeserializeItem> config_items,
Slic3r::Print &print, Slic3r::Model &model);
// Distinct layer Z heights carrying an extrusion of the given `role` (e.g. "skirt").
std::set<double> layers_with_role(const std::string &gcode, const std::string &role);

View File

@@ -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<double, int, int>({
@@ -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<std::string> 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]")
{