mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-26 02:11:18 +00:00
bug fix, tests
This commit is contained in:
@@ -904,8 +904,33 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
|
||||
if (optdef && optdef->type == coStrings) {
|
||||
use_comma = false;
|
||||
}
|
||||
std::vector<std::string> array_values;
|
||||
array_values.reserve(it.value().size());
|
||||
for (auto iter = it.value().begin(); iter != it.value().end(); iter++) {
|
||||
if (iter.value().is_string()) {
|
||||
array_values.emplace_back(iter.value());
|
||||
}
|
||||
else {
|
||||
//should not happen
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<<file<<" error, invalid json array for " << it.key();
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid && optdef != nullptr && optdef->is_scalar() && optdef->type != coPoint && optdef->type != coPoint3) {
|
||||
if (array_values.size() == 1) {
|
||||
value_str = array_values.front();
|
||||
} else if (!array_values.empty() &&
|
||||
std::all_of(array_values.begin() + 1, array_values.end(),
|
||||
[&](const std::string &value) { return value == array_values.front(); })) {
|
||||
value_str = array_values.front();
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__
|
||||
<< ": collapsing redundant json array for scalar option " << it.key()
|
||||
<< " in " << file;
|
||||
}
|
||||
}
|
||||
if (valid && value_str.empty()) {
|
||||
for (const std::string &array_value : array_values) {
|
||||
if (!first) {
|
||||
if (use_comma)
|
||||
value_str += ",";
|
||||
@@ -916,19 +941,13 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex
|
||||
first = false;
|
||||
|
||||
if (use_comma)
|
||||
value_str += iter.value();
|
||||
value_str += array_value;
|
||||
else {
|
||||
value_str += "\"";
|
||||
value_str += escape_string_cstyle(iter.value());
|
||||
value_str += escape_string_cstyle(array_value);
|
||||
value_str += "\"";
|
||||
}
|
||||
}
|
||||
else {
|
||||
//should not happen
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__<< ": parse "<<file<<" error, invalid json array for " << it.key();
|
||||
valid = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (valid)
|
||||
this->set_deserialize(opt_key, value_str, substitution_context);
|
||||
|
||||
@@ -842,7 +842,8 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
const PrintRegionConfig ®ion_config = layerm.region().config();
|
||||
FlowRole extrusion_role = surface.is_top() ? frTopSolidInfill : (surface.is_solid() ? frSolidInfill : frInfill);
|
||||
bool is_bridge = layer.id() > 0 && surface.is_bridge();
|
||||
params.extruder = layerm.region().extruder(extrusion_role);
|
||||
const unsigned int effective_extruder = layerm.extruder(extrusion_role);
|
||||
params.extruder = effective_extruder;
|
||||
params.pattern = region_config.sparse_infill_pattern.value;
|
||||
params.density = float(region_config.sparse_infill_density);
|
||||
params.lateral_lattice_angle_1 = region_config.lateral_lattice_angle_1;
|
||||
@@ -939,9 +940,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
} else {
|
||||
// Internal infill. Calculating infill line spacing independent of the current layer height and 1st layer status,
|
||||
// so that internall infill will be aligned over all layers of the current region.
|
||||
params.spacing = layerm.region().flow(*layer.object(), frInfill, layer.object()->config().layer_height, false).spacing();
|
||||
params.spacing = layerm.flow(frInfill, layer.object()->config().layer_height).spacing();
|
||||
// Anchor a sparse infill to inner perimeters with the following anchor length:
|
||||
params.anchor_length = float(region_config.infill_anchor);
|
||||
params.anchor_length = float(region_config.infill_anchor);
|
||||
if (region_config.infill_anchor.percent)
|
||||
params.anchor_length = float(params.anchor_length * 0.01 * params.spacing);
|
||||
params.anchor_length_max = float(region_config.infill_anchor_max);
|
||||
@@ -953,7 +954,7 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
||||
//get locked region param
|
||||
if (params.pattern == ipLockedZag){
|
||||
const PrintObject *object = layerm.layer()->object();
|
||||
auto nozzle_diameter = float(object->print()->config().nozzle_diameter.get_at(layerm.region().extruder(extrusion_role) - 1));
|
||||
auto nozzle_diameter = float(object->print()->config().nozzle_diameter.get_at(effective_extruder - 1));
|
||||
Flow skin_flow = params.bridge ? params.flow : Flow::new_from_config_width(extrusion_role, region_config.skin_infill_line_width, nozzle_diameter, float((surface.thickness == -1) ? layer.height : surface.thickness));
|
||||
//add skin flow
|
||||
append_flow_param(lock_param.skin_flow_params, skin_flow, surface.expolygon);
|
||||
|
||||
+51
-10
@@ -10,6 +10,46 @@
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
SurfaceCollection split_fill_surfaces_by_region(const SurfaceCollection &fill_surfaces, const SurfaceCollection ®ion_slices)
|
||||
{
|
||||
SurfaceCollection out;
|
||||
if (fill_surfaces.empty() || region_slices.empty())
|
||||
return out;
|
||||
|
||||
for (size_t surface_type = 0; surface_type < size_t(stCount); ++surface_type) {
|
||||
Surfaces typed_fill_surfaces;
|
||||
for (const Surface &surface : fill_surfaces.surfaces)
|
||||
if (surface.surface_type == SurfaceType(surface_type))
|
||||
typed_fill_surfaces.emplace_back(surface);
|
||||
|
||||
if (typed_fill_surfaces.empty())
|
||||
continue;
|
||||
|
||||
ExPolygons clipped = intersection_ex(typed_fill_surfaces, region_slices.surfaces, ApplySafetyOffset::Yes);
|
||||
if (!clipped.empty())
|
||||
out.append(std::move(clipped), SurfaceType(surface_type));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
using SliceMergeKey = std::tuple<int, double, unsigned short, double, unsigned short>;
|
||||
|
||||
SliceMergeKey slice_merge_key(const Surface &surface)
|
||||
{
|
||||
return {
|
||||
int(surface.surface_type),
|
||||
surface.thickness,
|
||||
surface.thickness_layers,
|
||||
surface.bridge_angle,
|
||||
surface.extra_perimeters
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Layer::~Layer()
|
||||
{
|
||||
this->lower_layer = this->upper_layer = nullptr;
|
||||
@@ -178,6 +218,7 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b)
|
||||
void Layer::make_perimeters()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(trace) << "Generating perimeters for layer " << this->id();
|
||||
const bool disable_compatible_region_merge = this->object() != nullptr && this->object()->is_mm_painted();
|
||||
|
||||
// keep track of regions whose perimeters we have already generated
|
||||
std::vector<unsigned char> done(m_regions.size(), false);
|
||||
@@ -198,7 +239,7 @@ void Layer::make_perimeters()
|
||||
// find compatible regions
|
||||
LayerRegionPtrs layerms;
|
||||
layerms.push_back(*layerm);
|
||||
for (LayerRegionPtrs::const_iterator it = layerm + 1; it != m_regions.end(); ++it)
|
||||
for (LayerRegionPtrs::const_iterator it = layerm + 1; !disable_compatible_region_merge && it != m_regions.end(); ++it)
|
||||
if (! (*it)->slices.empty()) {
|
||||
LayerRegion* other_layerm = *it;
|
||||
const PrintRegion &other_region = other_layerm->region();
|
||||
@@ -221,17 +262,17 @@ void Layer::make_perimeters()
|
||||
// Use the region with highest infill rate, as the make_perimeters() function below decides on the gap fill based on the infill existence.
|
||||
LayerRegion *layerm_config = layerms.front();
|
||||
{
|
||||
// group slices (surfaces) according to number of extra perimeters
|
||||
std::map<unsigned short, Surfaces> slices; // extra_perimeters => [ surface, surface... ]
|
||||
// Keep surface typing intact when compatible regions are merged for perimeter generation.
|
||||
std::map<SliceMergeKey, Surfaces> slices;
|
||||
for (LayerRegion *layerm : layerms) {
|
||||
for (const Surface &surface : layerm->slices.surfaces)
|
||||
slices[surface.extra_perimeters].emplace_back(surface);
|
||||
slices[slice_merge_key(surface)].emplace_back(surface);
|
||||
if (layerm->region().config().sparse_infill_density > layerm_config->region().config().sparse_infill_density)
|
||||
layerm_config = layerm;
|
||||
}
|
||||
// merge the surfaces assigned to each group
|
||||
for (std::pair<const unsigned short,Surfaces> &surfaces_with_extra_perimeters : slices)
|
||||
new_slices.append(offset_ex(surfaces_with_extra_perimeters.second, ClipperSafetyOffset), surfaces_with_extra_perimeters.second.front());
|
||||
for (auto &entry : slices)
|
||||
new_slices.append(offset_ex(entry.second, ClipperSafetyOffset), entry.second.front());
|
||||
}
|
||||
|
||||
// make perimeters
|
||||
@@ -244,11 +285,11 @@ void Layer::make_perimeters()
|
||||
if (!fill_surfaces.surfaces.empty()) {
|
||||
for (LayerRegionPtrs::iterator l = layerms.begin(); l != layerms.end(); ++l) {
|
||||
// Separate the fill surfaces.
|
||||
ExPolygons expp = intersection_ex(fill_surfaces.surfaces, (*l)->slices.surfaces);
|
||||
(*l)->fill_expolygons = expp;
|
||||
(*l)->fill_surfaces.set(std::move(expp), fill_surfaces.surfaces.front());
|
||||
SurfaceCollection split_fill_surfaces = split_fill_surfaces_by_region(fill_surfaces, (*l)->slices);
|
||||
(*l)->fill_expolygons = to_expolygons(split_fill_surfaces.surfaces);
|
||||
(*l)->fill_surfaces = std::move(split_fill_surfaces);
|
||||
//BBS: Separate fill_no_overlap
|
||||
(*l)->fill_no_overlap_expolygons = intersection_ex((*l)->slices.surfaces, fill_no_overlap);
|
||||
(*l)->fill_no_overlap_expolygons = intersection_ex((*l)->slices.surfaces, fill_no_overlap, ApplySafetyOffset::Yes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,30 @@ namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
unsigned int effective_layer_filament_id(const Layer &layer, unsigned int filament_id)
|
||||
{
|
||||
if (filament_id == 0)
|
||||
return filament_id;
|
||||
|
||||
const PrintObject *object = layer.object();
|
||||
const Print *print = object ? object->print() : nullptr;
|
||||
if (print == nullptr)
|
||||
return filament_id;
|
||||
|
||||
const size_t num_physical = print->config().filament_diameter.size();
|
||||
if (num_physical == 0)
|
||||
return filament_id;
|
||||
|
||||
// Ordinary mixed rows still print with one physical filament per layer even
|
||||
// when region collapse is disabled, so geometry / flow decisions must use
|
||||
// that effective physical filament to avoid per-layer thin-feature drift.
|
||||
return print->mixed_filament_manager().effective_painted_region_filament_id(filament_id,
|
||||
num_physical,
|
||||
int(layer.id()),
|
||||
float(layer.print_z),
|
||||
float(layer.height));
|
||||
}
|
||||
|
||||
bool use_base_infill_filament(const PrintRegionConfig &config, int layer_index, int layer_count)
|
||||
{
|
||||
if (!config.enable_infill_filament_override.value)
|
||||
@@ -39,11 +63,15 @@ bool use_base_infill_filament(const PrintRegionConfig &config, int layer_index,
|
||||
unsigned int LayerRegion::extruder(FlowRole role) const
|
||||
{
|
||||
const PrintRegionConfig &config = this->region().config();
|
||||
unsigned int filament_id = 0;
|
||||
if (role == frInfill)
|
||||
return use_base_infill_filament(config, m_layer->id(), int(m_layer->object()->layers().size())) ? config.wall_filament.value : config.sparse_infill_filament.value;
|
||||
if (role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON)
|
||||
return use_base_infill_filament(config, m_layer->id(), int(m_layer->object()->layers().size())) ? config.wall_filament.value : config.sparse_infill_filament.value;
|
||||
return this->region().extruder(role);
|
||||
filament_id = use_base_infill_filament(config, m_layer->id(), int(m_layer->object()->layers().size())) ? config.wall_filament.value : config.sparse_infill_filament.value;
|
||||
else if (role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON)
|
||||
filament_id = use_base_infill_filament(config, m_layer->id(), int(m_layer->object()->layers().size())) ? config.wall_filament.value : config.sparse_infill_filament.value;
|
||||
else
|
||||
filament_id = this->region().extruder(role);
|
||||
|
||||
return effective_layer_filament_id(*m_layer, filament_id);
|
||||
}
|
||||
|
||||
Flow LayerRegion::flow(FlowRole role) const
|
||||
@@ -127,6 +155,10 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
const PrintConfig &print_config = this->layer()->object()->print()->config();
|
||||
const PrintRegionConfig ®ion_config = this->region().config();
|
||||
const PrintObjectConfig& object_config = this->layer()->object()->config();
|
||||
PrintRegionConfig perimeter_config = region_config;
|
||||
perimeter_config.wall_filament.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.wall_filament.value))));
|
||||
perimeter_config.sparse_infill_filament.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.sparse_infill_filament.value))));
|
||||
perimeter_config.solid_infill_filament.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.solid_infill_filament.value))));
|
||||
// This needs to be in sync with PrintObject::_slice() slicing_mode_normal_below_layer!
|
||||
bool spiral_mode = print_config.spiral_mode &&
|
||||
//FIXME account for raft layers.
|
||||
@@ -140,7 +172,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe
|
||||
this->layer()->height,
|
||||
this->layer()->slice_z,
|
||||
this->flow(frPerimeter),
|
||||
®ion_config,
|
||||
&perimeter_config,
|
||||
&this->layer()->object()->config(),
|
||||
&print_config,
|
||||
spiral_mode,
|
||||
|
||||
@@ -1830,15 +1830,14 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
||||
{
|
||||
const size_t num_layers = segmented_regions.size();
|
||||
std::vector<std::vector<ExPolygons>> segmented_regions_merged(num_layers);
|
||||
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_facets_states - 1));
|
||||
segmented_regions_merged.assign(num_layers, std::vector<ExPolygons>(num_facets_states));
|
||||
assert(!top_and_bottom_layers.size() || num_facets_states == top_and_bottom_layers.size());
|
||||
|
||||
BOOST_LOG_TRIVIAL(debug) << "Print object segmentation - Merging segmented layers in parallel - Begin";
|
||||
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range<size_t> &range) {
|
||||
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
|
||||
assert(segmented_regions[layer_idx].size() == num_facets_states);
|
||||
// Zero is skipped because it is the default color of the volume
|
||||
for (size_t extruder_id = 1; extruder_id < num_facets_states; ++extruder_id) {
|
||||
for (size_t extruder_id = 0; extruder_id < num_facets_states; ++extruder_id) {
|
||||
throw_on_cancel_callback();
|
||||
if (!segmented_regions[layer_idx][extruder_id].empty()) {
|
||||
ExPolygons segmented_regions_trimmed = segmented_regions[layer_idx][extruder_id];
|
||||
@@ -1850,16 +1849,16 @@ static std::vector<std::vector<ExPolygons>> merge_segmented_layers(const std::ve
|
||||
}
|
||||
}
|
||||
|
||||
segmented_regions_merged[layer_idx][extruder_id - 1] = std::move(segmented_regions_trimmed);
|
||||
segmented_regions_merged[layer_idx][extruder_id] = std::move(segmented_regions_trimmed);
|
||||
}
|
||||
|
||||
if (!top_and_bottom_layers.empty() && !top_and_bottom_layers[extruder_id][layer_idx].empty()) {
|
||||
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id - 1].empty();
|
||||
append(segmented_regions_merged[layer_idx][extruder_id - 1], top_and_bottom_layers[extruder_id][layer_idx]);
|
||||
bool was_top_and_bottom_empty = segmented_regions_merged[layer_idx][extruder_id].empty();
|
||||
append(segmented_regions_merged[layer_idx][extruder_id], top_and_bottom_layers[extruder_id][layer_idx]);
|
||||
|
||||
// Remove dimples (#7235) appearing after merging side segmentation of the model with tops and bottoms painted layers.
|
||||
if (!was_top_and_bottom_empty)
|
||||
segmented_regions_merged[layer_idx][extruder_id - 1] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id - 1]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
||||
segmented_regions_merged[layer_idx][extruder_id] = offset2_ex(union_ex(segmented_regions_merged[layer_idx][extruder_id]), float(SCALED_EPSILON), -float(SCALED_EPSILON));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,67 @@ namespace Slic3r {
|
||||
|
||||
using namespace Slic3r::Feature::FuzzySkin;
|
||||
|
||||
static void append_collapsed_infill_gap_fill(const ExPolygons &areas,
|
||||
const Flow &flow,
|
||||
const double simplify_resolution,
|
||||
const float filter_out_gap_fill,
|
||||
ExtrusionEntityCollection *gap_fill)
|
||||
{
|
||||
if (gap_fill == nullptr || areas.empty())
|
||||
return;
|
||||
|
||||
const double min = 0.2 * flow.scaled_spacing() * (1 - INSET_OVERLAP_TOLERANCE);
|
||||
const double max = 2.0 * flow.scaled_spacing();
|
||||
ExPolygons gaps_ex = diff_ex(
|
||||
opening_ex(areas, float(min / 2.0)),
|
||||
offset2_ex(areas, -float(max / 2.0), float(max / 2.0 + ClipperSafetyOffset)));
|
||||
if (gaps_ex.empty())
|
||||
gaps_ex = union_ex(areas);
|
||||
|
||||
ThickPolylines polylines;
|
||||
for (ExPolygon &ex : gaps_ex) {
|
||||
ex.douglas_peucker(simplify_resolution);
|
||||
try {
|
||||
ex.medial_axis(min, max, &polylines);
|
||||
} catch (...) {
|
||||
}
|
||||
}
|
||||
|
||||
auto filter_short_polylines = [&](ThickPolylines &src) {
|
||||
src.erase(std::remove_if(src.begin(),
|
||||
src.end(),
|
||||
[&](const ThickPolyline &p) { return p.length() < scale_(filter_out_gap_fill); }),
|
||||
src.end());
|
||||
};
|
||||
|
||||
filter_short_polylines(polylines);
|
||||
|
||||
if (polylines.empty()) {
|
||||
for (const double inset_factor : {0.25, 0.125}) {
|
||||
ExPolygons inset_loops = offset_ex(areas, -float(flow.scaled_width() * inset_factor));
|
||||
if (inset_loops.empty())
|
||||
continue;
|
||||
|
||||
ThickPolylines inset_polylines = to_thick_polylines(to_polylines(std::move(inset_loops)), flow.scaled_width());
|
||||
filter_short_polylines(inset_polylines);
|
||||
if (inset_polylines.empty())
|
||||
continue;
|
||||
|
||||
ExtrusionEntityCollection gap_fill_entities;
|
||||
variable_width(inset_polylines, erGapFill, flow, gap_fill_entities.entities);
|
||||
gap_fill->append(std::move(gap_fill_entities.entities));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (polylines.empty())
|
||||
return;
|
||||
|
||||
ExtrusionEntityCollection gap_fill_entities;
|
||||
variable_width(polylines, erGapFill, flow, gap_fill_entities.entities);
|
||||
gap_fill->append(std::move(gap_fill_entities.entities));
|
||||
}
|
||||
|
||||
// Hierarchy of perimeters.
|
||||
class PerimeterGeneratorLoop {
|
||||
public:
|
||||
@@ -1208,6 +1269,7 @@ void PerimeterGenerator::process_classic()
|
||||
ExPolygons gaps;
|
||||
ExPolygons top_fills;
|
||||
ExPolygons fill_clip;
|
||||
ExPolygons collapsed_gap_fill_source;
|
||||
if (loop_number >= 0) {
|
||||
// In case no perimeters are to be generated, loop_number will equal to -1.
|
||||
std::vector<PerimeterGeneratorLoops> contours(loop_number+1); // depth => loops
|
||||
@@ -1536,6 +1598,9 @@ void PerimeterGenerator::process_classic()
|
||||
}
|
||||
}
|
||||
}
|
||||
collapsed_gap_fill_source = entities.empty() ?
|
||||
ExPolygons() :
|
||||
diff_ex(ExPolygons { surface.expolygon }, entities.polygons_covered_by_width(10.f));
|
||||
|
||||
// append perimeters for this slice as a collection
|
||||
if (! entities.empty())
|
||||
@@ -1634,6 +1699,17 @@ void PerimeterGenerator::process_classic()
|
||||
not_filled_exp,
|
||||
float(-inset - min_perimeter_infill_spacing / 2.),
|
||||
float(min_perimeter_infill_spacing / 2.));
|
||||
if (infill_exp.empty() && has_gap_fill) {
|
||||
ExPolygons gap_fill_source = not_filled_exp.empty() ? collapsed_gap_fill_source : not_filled_exp;
|
||||
if (gap_fill_source.empty())
|
||||
gap_fill_source = ExPolygons { surface.expolygon };
|
||||
append_collapsed_infill_gap_fill(gap_fill_source,
|
||||
this->solid_infill_flow,
|
||||
surface_simplify_resolution,
|
||||
config->filter_out_gap_fill.value,
|
||||
this->gap_fill);
|
||||
}
|
||||
|
||||
// append infill areas to fill_surfaces
|
||||
//if any top_fills, grow them by ext_perimeter_spacing/2 to have the real un-anchored fill
|
||||
ExPolygons top_infill_exp = intersection_ex(fill_clip, offset_ex(top_fills, double(ext_perimeter_spacing / 2)));
|
||||
@@ -2450,6 +2526,7 @@ void PerimeterGenerator::process_arachne()
|
||||
steep_overhang_contour = true;
|
||||
steep_overhang_hole = true;
|
||||
}
|
||||
ExPolygons collapsed_gap_fill_source;
|
||||
if (ExtrusionEntityCollection extrusion_coll = traverse_extrusions(*this, ordered_extrusions, steep_overhang_contour, steep_overhang_hole); !extrusion_coll.empty()) {
|
||||
// All walls are counter-clockwise initially, so we don't need to reorient it if that's what we want
|
||||
if (wall_direction != WallDirection::CounterClockwise) {
|
||||
@@ -2457,6 +2534,7 @@ void PerimeterGenerator::process_arachne()
|
||||
// Reverse internal only if the wall direction is auto
|
||||
this->config->overhang_reverse_internal_only && wall_direction == WallDirection::Auto);
|
||||
}
|
||||
collapsed_gap_fill_source = diff_ex(ExPolygons { surface.expolygon }, extrusion_coll.polygons_covered_by_width(10.f));
|
||||
this->loops->append(extrusion_coll);
|
||||
}
|
||||
|
||||
@@ -2496,6 +2574,17 @@ void PerimeterGenerator::process_arachne()
|
||||
not_filled_exp,
|
||||
float(-min_perimeter_infill_spacing / 2.),
|
||||
float(inset + min_perimeter_infill_spacing / 2.));
|
||||
if (infill_exp.empty() && this->config->gap_infill_speed.value > 0) {
|
||||
ExPolygons gap_fill_source = not_filled_exp.empty() ? collapsed_gap_fill_source : not_filled_exp;
|
||||
if (gap_fill_source.empty())
|
||||
gap_fill_source = ExPolygons { surface.expolygon };
|
||||
append_collapsed_infill_gap_fill(gap_fill_source,
|
||||
this->solid_infill_flow,
|
||||
surface_simplify_resolution,
|
||||
config->filter_out_gap_fill.value,
|
||||
this->gap_fill);
|
||||
}
|
||||
|
||||
// append infill areas to fill_surfaces
|
||||
if (!top_expolygons.empty()) {
|
||||
infill_exp = union_ex(infill_exp, offset_ex(top_expolygons, double(top_inset)));
|
||||
|
||||
@@ -1165,12 +1165,6 @@ void PresetCollection::load_presets(
|
||||
if (Slic3r::is_json_file(file_name)) {
|
||||
// Remove the .ini suffix.
|
||||
std::string name = file_name.erase(file_name.size() - 5);
|
||||
if (this->find_preset(name, false)) {
|
||||
// This happens when there's is a preset (most likely legacy one) with the same name as a system preset
|
||||
// that's already been loaded from a bundle.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << name;
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Preset preset(m_type, name, false);
|
||||
preset.file = dir_entry.path().string();
|
||||
@@ -1201,6 +1195,28 @@ void PresetCollection::load_presets(
|
||||
continue;
|
||||
}
|
||||
|
||||
auto type_it = key_values.find(BBL_JSON_KEY_TYPE);
|
||||
if (type_it != key_values.end()) {
|
||||
Preset::Type file_type = Preset::get_type_from_string(type_it->second);
|
||||
if (file_type != m_type) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(
|
||||
": skip preset %1% because file type %2% does not match collection type %3%")
|
||||
% preset.file % type_it->second % Preset::get_type_string(m_type);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto name_it = key_values.find(BBL_JSON_KEY_NAME);
|
||||
if (name_it != key_values.end() && !name_it->second.empty())
|
||||
preset.name = name_it->second;
|
||||
|
||||
if (this->find_preset(preset.name, false)) {
|
||||
// This happens when there is a preset with the same logical name already loaded,
|
||||
// typically from a system bundle or from another JSON file with a different filename.
|
||||
BOOST_LOG_TRIVIAL(warning) << "Preset already present, not loading: " << preset.name;
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string version_str = key_values[BBL_JSON_KEY_VERSION];
|
||||
boost::optional<Semver> version = Semver::parse(version_str);
|
||||
if (!version) continue;
|
||||
@@ -1245,7 +1261,7 @@ void PresetCollection::load_presets(
|
||||
// Find a default preset for the config. The PrintPresetCollection provides different default preset based on the "printer_technology" field.
|
||||
preset.config = default_preset.config;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " load preset: " << preset.name << " and filament_id: " << preset.filament_id << " and base_id: " << preset.base_id;
|
||||
preset.config.apply(std::move(config));
|
||||
Preset::normalize(preset.config);
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
|
||||
@@ -1752,6 +1752,27 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
this->load_installed_filaments(config);
|
||||
this->load_installed_sla_materials(config);
|
||||
|
||||
auto is_default_printer_selection = [](const std::string &name) {
|
||||
return name.empty() || name == "Default Printer";
|
||||
};
|
||||
auto is_placeholder_print_selection = [](const std::string &name) {
|
||||
return name.empty() || name == "Default Setting" || boost::algorithm::iends_with(name, "_common");
|
||||
};
|
||||
auto is_placeholder_filament_selection = [](const std::string &name) {
|
||||
return name.empty() || name == "Default Filament";
|
||||
};
|
||||
auto unique_visible_printer = [this]() -> const Preset* {
|
||||
const Preset *visible_printer = nullptr;
|
||||
for (const Preset &preset : printers) {
|
||||
if (preset.is_default || !preset.is_visible)
|
||||
continue;
|
||||
if (visible_printer != nullptr)
|
||||
return nullptr;
|
||||
visible_printer = &preset;
|
||||
}
|
||||
return visible_printer;
|
||||
};
|
||||
|
||||
// Parse the initial print / filament / printer profile names.
|
||||
// std::string initial_sla_print_profile_name = remove_ini_suffix(config.get("presets", PRESET_SLA_PRINT_NAME));
|
||||
// std::string initial_sla_material_profile_name = remove_ini_suffix(config.get("presets", PRESET_SLA_MATERIALS_NAME));
|
||||
@@ -1766,7 +1787,10 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
const Preset *initial_printer = printers.find_preset(initial_printer_profile_name);
|
||||
// If executed due to a Config Wizard update, preferred_printer contains the first newly installed printer, otherwise nullptr.
|
||||
const Preset *preferred_printer = printers.find_system_preset_by_model_and_variant(preferred_selection.printer_model_id, preferred_selection.printer_variant);
|
||||
printers.select_preset_by_name(preferred_printer ? preferred_printer->name : initial_printer_profile_name, true);
|
||||
const Preset *fallback_printer = preferred_printer == nullptr && is_default_printer_selection(initial_printer_profile_name) ?
|
||||
unique_visible_printer() : nullptr;
|
||||
const Preset *selected_printer = preferred_printer ? preferred_printer : fallback_printer ? fallback_printer : initial_printer;
|
||||
printers.select_preset_by_name(selected_printer ? selected_printer->name : initial_printer_profile_name, true);
|
||||
CNumericLocalesSetter locales_setter;
|
||||
|
||||
// Orca: load from orca_presets
|
||||
@@ -1774,16 +1798,21 @@ void PresetBundle::load_selections(AppConfig &config, const PresetPreferences& p
|
||||
std::string initial_print_profile_name = config.get_printer_setting(initial_printer_profile_name, PRESET_PRINT_NAME);
|
||||
std::string initial_filament_profile_name = config.get_printer_setting(initial_printer_profile_name, PRESET_FILAMENT_NAME);
|
||||
|
||||
//BBS: set default print/filament profiles to BBL's default setting
|
||||
if (preferred_printer)
|
||||
// Normalize placeholder selections to the active printer defaults.
|
||||
if (selected_printer)
|
||||
{
|
||||
const std::string& prefered_print_profile = preferred_printer->config.opt_string("default_print_profile");
|
||||
if ((!initial_print_profile_name.compare("Default Setting")) && (prefered_print_profile.size() > 0))
|
||||
const std::string& prefered_print_profile = selected_printer->config.opt_string("default_print_profile");
|
||||
if (is_placeholder_print_selection(initial_print_profile_name) && !prefered_print_profile.empty())
|
||||
initial_print_profile_name = prefered_print_profile;
|
||||
|
||||
const std::vector<std::string>& prefered_filament_profiles = preferred_printer->config.option<ConfigOptionStrings>("default_filament_profile")->values;
|
||||
if ((!initial_filament_profile_name.compare("Default Filament")) && (prefered_filament_profiles.size() > 0))
|
||||
const std::vector<std::string>& prefered_filament_profiles = selected_printer->config.option<ConfigOptionStrings>("default_filament_profile")->values;
|
||||
if (is_placeholder_filament_selection(initial_filament_profile_name) && !prefered_filament_profiles.empty())
|
||||
initial_filament_profile_name = prefered_filament_profiles[0];
|
||||
|
||||
if (fallback_printer != nullptr) {
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << boost::format(": recovered active printer selection from %1% to %2%")
|
||||
% initial_printer_profile_name % fallback_printer->name;
|
||||
}
|
||||
}
|
||||
|
||||
// Selects the profile, leaves it to -1 if the initial profile name is empty or if it was not found.
|
||||
@@ -2851,6 +2880,46 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid type for " << it.key();
|
||||
}
|
||||
};
|
||||
auto append_json_subfiles = [](const fs::path& vendor_dir,
|
||||
const fs::path& section_dir,
|
||||
std::vector<std::pair<std::string, std::string>>& subfile_map,
|
||||
bool recursive) {
|
||||
if (!fs::exists(section_dir))
|
||||
return;
|
||||
|
||||
std::vector<fs::path> files;
|
||||
if (recursive) {
|
||||
for (const auto& entry : fs::recursive_directory_iterator(section_dir)) {
|
||||
if (fs::is_regular_file(entry.path()) && Slic3r::is_json_file(entry.path().filename().string()))
|
||||
files.push_back(entry.path());
|
||||
}
|
||||
} else {
|
||||
for (const auto& entry : fs::directory_iterator(section_dir)) {
|
||||
if (fs::is_regular_file(entry.path()) && Slic3r::is_json_file(entry.path().filename().string()))
|
||||
files.push_back(entry.path());
|
||||
}
|
||||
}
|
||||
|
||||
auto priority = [](const fs::path& file) {
|
||||
const std::string stem = file.stem().string();
|
||||
if (stem == "fdm_filament_common")
|
||||
return 0;
|
||||
if (boost::algorithm::iends_with(stem, "@base"))
|
||||
return 1;
|
||||
if (boost::algorithm::iends_with(stem, "_common"))
|
||||
return 2;
|
||||
return 3;
|
||||
};
|
||||
std::sort(files.begin(), files.end(), [&](const fs::path& lhs, const fs::path& rhs) {
|
||||
const int lhs_priority = priority(lhs);
|
||||
const int rhs_priority = priority(rhs);
|
||||
if (lhs_priority != rhs_priority)
|
||||
return lhs_priority < rhs_priority;
|
||||
return lhs.generic_string() < rhs.generic_string();
|
||||
});
|
||||
for (const fs::path& file : files)
|
||||
subfile_map.emplace_back(file.stem().string(), file.lexically_relative(vendor_dir).generic_string());
|
||||
};
|
||||
try {
|
||||
boost::nowide::ifstream ifs(root_file);
|
||||
json j;
|
||||
@@ -2905,6 +2974,29 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
//goto __error_process;
|
||||
}
|
||||
|
||||
if (vendor_name == ORCA_FILAMENT_LIBRARY && filament_subfiles.empty()) {
|
||||
const fs::path vendor_dir = fs::path(path) / vendor_name;
|
||||
const fs::path filament_dir = vendor_dir / "filament";
|
||||
|
||||
// OrcaFilamentLibrary keeps its profiles on disk but ships an empty manifest. Load the shared
|
||||
// base presets first, then the top-level generic presets, then the vendor-specific subfolders.
|
||||
append_json_subfiles(vendor_dir, filament_dir / "base", filament_subfiles, true);
|
||||
append_json_subfiles(vendor_dir, filament_dir, filament_subfiles, false);
|
||||
if (fs::exists(filament_dir)) {
|
||||
std::vector<fs::path> vendor_dirs;
|
||||
for (const auto& entry : fs::directory_iterator(filament_dir)) {
|
||||
if (fs::is_directory(entry.path()) && entry.path().filename() != "base")
|
||||
vendor_dirs.push_back(entry.path());
|
||||
}
|
||||
std::sort(vendor_dirs.begin(), vendor_dirs.end());
|
||||
for (const fs::path& dir_entry : vendor_dirs)
|
||||
append_json_subfiles(vendor_dir, dir_entry, filament_subfiles, true);
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": discovered " << filament_subfiles.size()
|
||||
<< " OrcaFilamentLibrary filament profiles from disk";
|
||||
}
|
||||
|
||||
if (startup_profile) {
|
||||
startup_profile_log("PresetBundle::load_vendor_configs_from_json vendor=" + vendor_name +
|
||||
" machine_models=" + std::to_string(machine_model_subfiles.size()) +
|
||||
@@ -3068,6 +3160,18 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_vendor_configs_
|
||||
auto filament_it = key_values.find(BBL_JSON_KEY_FILAMENT_ID);
|
||||
if (filament_it != key_values.end())
|
||||
filament_id = filament_it->second;
|
||||
if (filament_id.empty() &&
|
||||
presets_collection->type() == Preset::TYPE_FILAMENT &&
|
||||
flags.has(LoadConfigBundleAttribute::LoadSystem) &&
|
||||
!setting_id.empty())
|
||||
{
|
||||
// Some system bundles only provide setting_id for filaments. Treat it as a stable fallback
|
||||
// instead of aborting the entire vendor import and losing all dependent presets.
|
||||
filament_id = setting_id;
|
||||
BOOST_LOG_TRIVIAL(warning) << __FUNCTION__
|
||||
<< ": missing filament_id for " << preset_name
|
||||
<< ", falling back to setting_id " << setting_id;
|
||||
}
|
||||
//check whether it inherits other preset or not
|
||||
auto it1 = key_values.find(BBL_JSON_KEY_INHERITS);
|
||||
if (it1 != key_values.end()) {
|
||||
|
||||
@@ -709,6 +709,27 @@ PrintObjectRegions::BoundingBox find_modifier_volume_extents(const PrintObjectRe
|
||||
return out;
|
||||
}
|
||||
|
||||
static const ModelVolume *root_model_part_for_parent_region(const PrintObjectRegions::LayerRangeRegions &layer_range, int parent_region_id)
|
||||
{
|
||||
if (parent_region_id < 0 || parent_region_id >= int(layer_range.volume_regions.size()))
|
||||
return nullptr;
|
||||
|
||||
const PrintObjectRegions::VolumeRegion *region = &layer_range.volume_regions[size_t(parent_region_id)];
|
||||
while (region != nullptr && !region->model_volume->is_model_part()) {
|
||||
if (region->parent < 0 || region->parent >= int(layer_range.volume_regions.size()))
|
||||
return nullptr;
|
||||
region = &layer_range.volume_regions[size_t(region->parent)];
|
||||
}
|
||||
|
||||
return (region != nullptr && region->model_volume->is_model_part()) ? region->model_volume : nullptr;
|
||||
}
|
||||
|
||||
static bool mm_paint_applies_to_parent_region(const PrintObjectRegions::LayerRangeRegions &layer_range, int parent_region_id)
|
||||
{
|
||||
const ModelVolume *root_model_part = root_model_part_for_parent_region(layer_range, parent_region_id);
|
||||
return root_model_part != nullptr && root_model_part->is_mm_painted();
|
||||
}
|
||||
|
||||
PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &default_or_parent_region_config, const DynamicPrintConfig *layer_range_config, const ModelVolume &volume, size_t num_extruders);
|
||||
|
||||
void print_region_ref_inc(PrintRegion &r) { ++ r.m_ref_cnt; }
|
||||
@@ -798,6 +819,8 @@ bool verify_update_print_object_regions(
|
||||
// Verify and / or update PrintRegions produced by color painting.
|
||||
for (const PrintObjectRegions::LayerRangeRegions &layer_range : print_object_regions.layer_ranges)
|
||||
for (const PrintObjectRegions::PaintedRegion ®ion : layer_range.painted_regions) {
|
||||
if (!mm_paint_applies_to_parent_region(layer_range, region.parent))
|
||||
return false;
|
||||
const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[region.parent];
|
||||
PrintRegionConfig cfg = parent_region.region->config();
|
||||
cfg.wall_filament.value = region.extruder_id;
|
||||
@@ -993,7 +1016,11 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
region_set.emplace(it, region);
|
||||
return region;
|
||||
};
|
||||
|
||||
auto create_unique_region = [&all_regions](PrintRegionConfig &&config) -> PrintRegion* {
|
||||
size_t hash = config.hash();
|
||||
all_regions.emplace_back(std::make_unique<PrintRegion>(std::move(config), hash, int(all_regions.size())));
|
||||
return all_regions.back().get();
|
||||
};
|
||||
// Chain the regions in the order they are stored in the volumes list.
|
||||
for (int volume_id = 0; volume_id < int(model_volumes.size()); ++ volume_id) {
|
||||
const ModelVolume &volume = *model_volumes[volume_id];
|
||||
@@ -1043,12 +1070,13 @@ static PrintObjectRegions* generate_print_object_regions(
|
||||
for (unsigned int painted_extruder_id : painting_extruders)
|
||||
for (int parent_region_id = 0; parent_region_id < int(layer_range.volume_regions.size()); ++ parent_region_id)
|
||||
if (const PrintObjectRegions::VolumeRegion &parent_region = layer_range.volume_regions[parent_region_id];
|
||||
parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) {
|
||||
(parent_region.model_volume->is_model_part() || parent_region.model_volume->is_modifier()) &&
|
||||
mm_paint_applies_to_parent_region(layer_range, parent_region_id)) {
|
||||
PrintRegionConfig cfg = parent_region.region->config();
|
||||
cfg.wall_filament.value = painted_extruder_id;
|
||||
cfg.solid_infill_filament.value = painted_extruder_id;
|
||||
cfg.sparse_infill_filament.value = painted_extruder_id;
|
||||
PrintRegion *painted_region = get_create_region(std::move(cfg));
|
||||
PrintRegion *painted_region = create_unique_region(std::move(cfg));
|
||||
if (painted_region->config().wall_filament.value != painted_extruder_id ||
|
||||
painted_region->config().solid_infill_filament.value != painted_extruder_id ||
|
||||
painted_region->config().sparse_infill_filament.value != painted_extruder_id) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <iomanip>
|
||||
#include <limits>
|
||||
#include <numeric>
|
||||
#include <sstream>
|
||||
|
||||
#include <tbb/parallel_for.h>
|
||||
|
||||
@@ -30,6 +31,10 @@ namespace Slic3r {
|
||||
bool PrintObject::clip_multipart_objects = true;
|
||||
bool PrintObject::infill_only_where_needed = false;
|
||||
|
||||
static bool has_surface_emboss_mixed_volume(const PrintObject &print_object);
|
||||
static std::string surface_emboss_mixed_debug_file_path(const PrintObject &print_object);
|
||||
static void reset_surface_emboss_mixed_debug_file(const PrintObject &print_object);
|
||||
|
||||
LayerPtrs new_layers(
|
||||
PrintObject *print_object,
|
||||
// Object layers (pairs of bottom/top Z coordinate), without the raft.
|
||||
@@ -808,6 +813,12 @@ void PrintObject::slice()
|
||||
m_typed_slices = false;
|
||||
this->clear_layers();
|
||||
m_layers = new_layers(this, generate_object_layers(m_slicing_params, layer_height_profile, m_config.precise_z_height.value));
|
||||
if (has_surface_emboss_mixed_volume(*this)) {
|
||||
reset_surface_emboss_mixed_debug_file(*this);
|
||||
BOOST_LOG_TRIVIAL(warning) << "Surface emboss mixed debug enabled"
|
||||
<< " object=" << (this->model_object() ? this->model_object()->name : std::string("<unknown>"))
|
||||
<< " debug_file=" << surface_emboss_mixed_debug_file_path(*this);
|
||||
}
|
||||
this->slice_volumes();
|
||||
m_print->throw_if_canceled();
|
||||
int firstLayerReplacedBy = 0;
|
||||
@@ -872,6 +883,13 @@ static coordf_t float_from_full_config(const DynamicPrintConfig &full_cfg, const
|
||||
return coordf_t(full_cfg.opt_float(key));
|
||||
}
|
||||
|
||||
static inline unsigned int segmentation_channel_filament_id(size_t channel_idx)
|
||||
{
|
||||
// MM segmentation reserves channel 0 for the parent/default region.
|
||||
// All remaining channels already line up with the 1-based filament IDs.
|
||||
return unsigned(channel_idx);
|
||||
}
|
||||
|
||||
static bool apply_mixed_surface_indentation(PrintObject &print_object, std::vector<std::vector<ExPolygons>> &segmentation)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
@@ -2290,11 +2308,37 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
const auto &mixed_rows = mixed_mgr.mixed_filaments();
|
||||
size_t pointillism_rows = 0;
|
||||
for (const MixedFilament &mf : mixed_rows) {
|
||||
const std::vector<unsigned int> sequence = pointillism_sequence_for_row(mf, num_physical);
|
||||
std::vector<uint8_t> pointillism_row_eligible(mixed_rows.size(), uint8_t(0));
|
||||
for (size_t row_idx = 0; row_idx < mixed_rows.size(); ++row_idx) {
|
||||
const std::vector<unsigned int> sequence = pointillism_sequence_for_row(mixed_rows[row_idx], num_physical);
|
||||
if (unique_extruder_count(sequence, num_physical) >= 2)
|
||||
++pointillism_rows;
|
||||
pointillism_row_eligible[row_idx] = uint8_t(1);
|
||||
}
|
||||
|
||||
size_t pointillism_rows = 0;
|
||||
if (!pointillism_row_eligible.empty()) {
|
||||
std::vector<uint8_t> pointillism_row_active(pointillism_row_eligible.size(), uint8_t(0));
|
||||
for (size_t layer_id = 0; layer_id < segmentation.size(); ++layer_id) {
|
||||
const auto &layer_segmentation = segmentation[layer_id];
|
||||
for (size_t channel_idx = 1; channel_idx < layer_segmentation.size(); ++channel_idx) {
|
||||
if (layer_segmentation[channel_idx].empty())
|
||||
continue;
|
||||
|
||||
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
||||
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
||||
continue;
|
||||
|
||||
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
||||
if (mixed_idx < 0 || size_t(mixed_idx) >= pointillism_row_eligible.size())
|
||||
continue;
|
||||
const size_t row_idx = size_t(mixed_idx);
|
||||
if (pointillism_row_eligible[row_idx] == 0 || pointillism_row_active[row_idx] != 0)
|
||||
continue;
|
||||
|
||||
pointillism_row_active[row_idx] = uint8_t(1);
|
||||
++pointillism_rows;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pointillism_rows > 0) {
|
||||
@@ -2373,7 +2417,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
||||
if (state_masks.empty())
|
||||
continue;
|
||||
const unsigned int state_id = unsigned(channel_idx + 1);
|
||||
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
||||
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
||||
continue;
|
||||
|
||||
@@ -2443,7 +2487,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
const ExPolygons &state_masks = segmentation[layer_id][channel_idx];
|
||||
if (state_masks.empty())
|
||||
continue;
|
||||
const unsigned int state_id = unsigned(channel_idx + 1);
|
||||
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
||||
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
||||
continue;
|
||||
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
||||
@@ -2757,7 +2801,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
if (state_masks.empty())
|
||||
continue;
|
||||
|
||||
const unsigned int state_id = unsigned(channel_idx + 1);
|
||||
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
||||
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
||||
continue;
|
||||
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
||||
@@ -2846,7 +2890,7 @@ static void build_local_z_plan(PrintObject &print_object, const std::vector<std:
|
||||
if (state_masks.empty())
|
||||
continue;
|
||||
|
||||
const unsigned int state_id = unsigned(channel_idx + 1);
|
||||
const unsigned int state_id = segmentation_channel_filament_id(channel_idx);
|
||||
if (!mixed_mgr.is_mixed(state_id, num_physical))
|
||||
continue;
|
||||
const int mixed_idx = mixed_mgr.mixed_index_from_filament_id(state_id, num_physical);
|
||||
@@ -2965,20 +3009,61 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
||||
double z = print_object.get_layer(int(range.begin()))->slice_z;
|
||||
auto it_layer_range = layer_range_first(layer_ranges, z);
|
||||
// MM segmentation channels correspond to filament IDs (1-based), which now
|
||||
// include enabled mixed / virtual filaments.
|
||||
const size_t num_extruders = segmentation.empty() ? 0 : segmentation.front().size();
|
||||
// MM segmentation channel 0 is the underlying / default color of the parent
|
||||
// region. Remaining channels correspond to filament IDs (1-based), which
|
||||
// now include enabled mixed / virtual filaments.
|
||||
const size_t num_channels = segmentation.empty() ? 0 : segmentation.front().size();
|
||||
const size_t num_extruders = num_channels > 0 ? num_channels - 1 : 0;
|
||||
|
||||
struct ByExtruder {
|
||||
ExPolygons expolygons;
|
||||
BoundingBox bbox;
|
||||
};
|
||||
|
||||
auto intersect_surfaces_preserve_types = [](const SurfaceCollection &src, const ExPolygons &mask) {
|
||||
SurfaceCollection out;
|
||||
if (src.empty() || mask.empty())
|
||||
return out;
|
||||
|
||||
std::array<SurfacesPtr, size_t(stCount)> by_surface;
|
||||
for (const Surface &surface : src.surfaces)
|
||||
by_surface[size_t(surface.surface_type)].emplace_back(&surface);
|
||||
|
||||
for (size_t surface_type = 0; surface_type < size_t(stCount); ++surface_type) {
|
||||
const SurfacesPtr &typed_surfaces = by_surface[surface_type];
|
||||
if (typed_surfaces.empty())
|
||||
continue;
|
||||
ExPolygons clipped = intersection_ex(typed_surfaces, mask);
|
||||
if (!clipped.empty())
|
||||
out.append(std::move(clipped), SurfaceType(surface_type));
|
||||
}
|
||||
return out;
|
||||
};
|
||||
|
||||
struct ByRegion {
|
||||
ExPolygons expolygons;
|
||||
SurfaceCollection surfaces;
|
||||
bool needs_merge { false };
|
||||
};
|
||||
|
||||
auto normalize_region_surfaces = [](SurfaceCollection &src) {
|
||||
if (src.surfaces.empty())
|
||||
return;
|
||||
|
||||
std::array<ExPolygons, size_t(stCount)> by_surface;
|
||||
for (Surface &surface : src.surfaces)
|
||||
by_surface[size_t(surface.surface_type)].emplace_back(std::move(surface.expolygon));
|
||||
|
||||
src.surfaces.clear();
|
||||
for (size_t surface_type = 0; surface_type < size_t(stCount); ++surface_type) {
|
||||
ExPolygons &typed = by_surface[surface_type];
|
||||
if (typed.empty())
|
||||
continue;
|
||||
if (typed.size() > 1)
|
||||
typed = closing_ex(std::move(typed), scaled<float>(10. * EPSILON));
|
||||
src.append(std::move(typed), SurfaceType(surface_type));
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<ByExtruder> by_extruder;
|
||||
std::vector<ByRegion> by_region;
|
||||
for (size_t layer_id = range.begin(); layer_id < range.end(); ++layer_id) {
|
||||
@@ -2987,14 +3072,20 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
||||
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
||||
// Gather per extruder expolygons.
|
||||
assert(segmentation[layer_id].size() == num_extruders);
|
||||
assert(segmentation[layer_id].size() == num_channels);
|
||||
by_extruder.assign(num_extruders, ByExtruder());
|
||||
by_region.assign(layer.region_count(), ByRegion());
|
||||
bool layer_split = false;
|
||||
size_t missing_target_regions = 0;
|
||||
std::vector<int> missing_target_extruders;
|
||||
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id) {
|
||||
const unsigned int channel_id = unsigned(extruder_id + 1);
|
||||
ExPolygons default_segmentation = num_channels > 0 ? std::move(segmentation[layer_id][0]) : ExPolygons();
|
||||
BoundingBox default_bbox;
|
||||
if (!default_segmentation.empty()) {
|
||||
default_bbox = get_extents(default_segmentation);
|
||||
layer_split = true;
|
||||
}
|
||||
for (size_t channel_idx = 1; channel_idx < num_channels; ++ channel_idx) {
|
||||
const unsigned int channel_id = unsigned(channel_idx);
|
||||
const unsigned int effective_filament_id = collapse_mixed_regions ?
|
||||
mixed_mgr.effective_painted_region_filament_id(channel_id,
|
||||
num_physical,
|
||||
@@ -3006,9 +3097,9 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
float(base_height)) :
|
||||
channel_id;
|
||||
const size_t effective_idx =
|
||||
effective_filament_id >= 1 && effective_filament_id <= num_extruders ? size_t(effective_filament_id - 1) : extruder_id;
|
||||
effective_filament_id >= 1 && effective_filament_id <= num_extruders ? size_t(effective_filament_id - 1) : size_t(channel_idx - 1);
|
||||
ByExtruder ®ion = by_extruder[effective_idx];
|
||||
append(region.expolygons, std::move(segmentation[layer_id][extruder_id]));
|
||||
append(region.expolygons, std::move(segmentation[layer_id][channel_idx]));
|
||||
if (! region.expolygons.empty()) {
|
||||
region.bbox = get_extents(region.expolygons);
|
||||
layer_split = true;
|
||||
@@ -3022,22 +3113,31 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
// layer_range.painted_regions are sorted by extruder ID and parent PrintObject region ID.
|
||||
auto it_painted_region_begin = layer_range.painted_regions.cbegin();
|
||||
for (int parent_layer_region_idx = 0; parent_layer_region_idx < layer.region_count(); ++parent_layer_region_idx) {
|
||||
if (it_painted_region_begin == layer_range.painted_regions.cend())
|
||||
continue;
|
||||
|
||||
const LayerRegion &parent_layer_region = *layer.get_region(parent_layer_region_idx);
|
||||
const PrintRegion &parent_print_region = parent_layer_region.region();
|
||||
assert(parent_print_region.print_object_region_id() == parent_layer_region_idx);
|
||||
if (parent_layer_region.slices.empty())
|
||||
continue;
|
||||
|
||||
auto preserve_parent_region = [&by_region, &parent_layer_region, &parent_print_region]() {
|
||||
if (!parent_layer_region.slices.empty())
|
||||
by_region[parent_print_region.print_object_region_id()].surfaces = parent_layer_region.slices;
|
||||
};
|
||||
|
||||
if (it_painted_region_begin == layer_range.painted_regions.cend()) {
|
||||
preserve_parent_region();
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the first PaintedRegion, which overrides the parent PrintRegion.
|
||||
auto it_first_painted_region = std::find_if(it_painted_region_begin, layer_range.painted_regions.cend(), [&layer_range, &parent_print_region](const auto &painted_region) {
|
||||
return layer_range.volume_regions[painted_region.parent].region->print_object_region_id() == parent_print_region.print_object_region_id();
|
||||
});
|
||||
|
||||
if (it_first_painted_region == layer_range.painted_regions.cend())
|
||||
if (it_first_painted_region == layer_range.painted_regions.cend()) {
|
||||
preserve_parent_region();
|
||||
continue; // This LayerRegion isn't overrides by any PaintedRegion.
|
||||
}
|
||||
|
||||
assert(&parent_print_region == layer_range.volume_regions[it_first_painted_region->parent].region);
|
||||
|
||||
@@ -3045,11 +3145,14 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
it_painted_region_begin = it_first_painted_region;
|
||||
|
||||
const BoundingBox parent_layer_region_bbox = get_extents(parent_layer_region.slices.surfaces);
|
||||
bool self_trimmed = false;
|
||||
int self_extruder_id = -1; // 1-based extruder ID
|
||||
ExPolygons explicit_self_expolygons;
|
||||
ExPolygons default_self_expolygons;
|
||||
if (const int cfg_wall = parent_print_region.config().wall_filament.value;
|
||||
cfg_wall >= 1 && cfg_wall <= int(by_extruder.size()))
|
||||
self_extruder_id = cfg_wall;
|
||||
if (default_bbox.defined && parent_layer_region_bbox.overlap(default_bbox))
|
||||
default_self_expolygons = intersection_ex(parent_layer_region.slices.surfaces, default_segmentation);
|
||||
std::vector<bool> assigned_extruder(by_extruder.size(), false);
|
||||
std::vector<int> alias_to_self_extruders;
|
||||
for (int extruder_id = 1; extruder_id <= int(by_extruder.size()); ++extruder_id) {
|
||||
@@ -3078,6 +3181,13 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
self_extruder_id = extruder_id;
|
||||
if (extruder_id != self_extruder_id)
|
||||
alias_to_self_extruders.emplace_back(extruder_id);
|
||||
ExPolygons self_segmented = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
|
||||
if (!self_segmented.empty()) {
|
||||
if (explicit_self_expolygons.empty())
|
||||
explicit_self_expolygons = std::move(self_segmented);
|
||||
else
|
||||
append(explicit_self_expolygons, std::move(self_segmented));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -3088,44 +3198,74 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
ExPolygons stolen = intersection_ex(parent_layer_region.slices.surfaces, segmented.expolygons);
|
||||
if (!stolen.empty()) {
|
||||
ByRegion &dst = by_region[target_region_id];
|
||||
if (dst.expolygons.empty()) {
|
||||
dst.expolygons = std::move(stolen);
|
||||
SurfaceCollection stolen_surfaces = intersect_surfaces_preserve_types(parent_layer_region.slices, stolen);
|
||||
if (stolen_surfaces.empty())
|
||||
continue;
|
||||
if (dst.surfaces.empty()) {
|
||||
dst.surfaces = std::move(stolen_surfaces);
|
||||
} else {
|
||||
append(dst.expolygons, std::move(stolen));
|
||||
dst.surfaces.append(std::move(stolen_surfaces));
|
||||
dst.needs_merge = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!self_trimmed) {
|
||||
// Trim slices of this LayerRegion with all the MM regions.
|
||||
Polygons mine = to_polygons(parent_layer_region.slices.surfaces);
|
||||
for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) {
|
||||
const ByExtruder &segmented = by_extruder[extruder_idx];
|
||||
if (!assigned_extruder[extruder_idx])
|
||||
continue;
|
||||
if (int(extruder_idx + 1) != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
|
||||
mine = diff(mine, segmented.expolygons);
|
||||
if (mine.empty())
|
||||
break;
|
||||
}
|
||||
// Trim slices of this LayerRegion with all the MM regions.
|
||||
Polygons mine = to_polygons(parent_layer_region.slices.surfaces);
|
||||
for (size_t extruder_idx = 0; extruder_idx < by_extruder.size(); ++extruder_idx) {
|
||||
const ByExtruder &segmented = by_extruder[extruder_idx];
|
||||
if (!assigned_extruder[extruder_idx])
|
||||
continue;
|
||||
if (int(extruder_idx + 1) != self_extruder_id && segmented.bbox.defined && parent_layer_region_bbox.overlap(segmented.bbox)) {
|
||||
mine = diff(mine, segmented.expolygons);
|
||||
if (mine.empty())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!explicit_self_expolygons.empty())
|
||||
explicit_self_expolygons = union_ex(explicit_self_expolygons);
|
||||
if (!default_self_expolygons.empty())
|
||||
default_self_expolygons = union_ex(default_self_expolygons);
|
||||
|
||||
ExPolygons preserved_self_expolygons;
|
||||
if (!explicit_self_expolygons.empty())
|
||||
append(preserved_self_expolygons, explicit_self_expolygons);
|
||||
if (!default_self_expolygons.empty())
|
||||
append(preserved_self_expolygons, default_self_expolygons);
|
||||
if (!preserved_self_expolygons.empty())
|
||||
preserved_self_expolygons = union_ex(preserved_self_expolygons);
|
||||
|
||||
ExPolygons mine_expolygons;
|
||||
if (!mine.empty()) {
|
||||
if (!preserved_self_expolygons.empty())
|
||||
mine = diff(mine, preserved_self_expolygons);
|
||||
|
||||
// Filter out unprintable polygons produced by subtraction multi-material painted regions from layerm.region().
|
||||
// ExPolygon returned from multi-material segmentation does not precisely match ExPolygons in layerm.region()
|
||||
// (because of preprocessing of the input regions in multi-material segmentation). Therefore, subtraction from
|
||||
// layerm.region() could produce a huge number of small unprintable regions for the model's base extruder.
|
||||
// This could, on some models, produce bulges with the model's base color (#7109).
|
||||
if (!mine.empty()) {
|
||||
if (!mine.empty())
|
||||
mine = opening(union_ex(mine), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
||||
}
|
||||
|
||||
if (!mine.empty()) {
|
||||
if (!mine.empty())
|
||||
mine_expolygons = union_ex(mine);
|
||||
}
|
||||
|
||||
if (!preserved_self_expolygons.empty()) {
|
||||
append(mine_expolygons, preserved_self_expolygons);
|
||||
mine_expolygons = union_ex(mine_expolygons);
|
||||
}
|
||||
|
||||
if (!mine_expolygons.empty()) {
|
||||
SurfaceCollection mine_surfaces = intersect_surfaces_preserve_types(parent_layer_region.slices, mine_expolygons);
|
||||
if (!mine_surfaces.empty()) {
|
||||
ByRegion &dst = by_region[parent_print_region.print_object_region_id()];
|
||||
if (dst.expolygons.empty()) {
|
||||
dst.expolygons = union_ex(mine);
|
||||
if (dst.surfaces.empty()) {
|
||||
dst.surfaces = std::move(mine_surfaces);
|
||||
} else {
|
||||
append(dst.expolygons, union_ex(mine));
|
||||
dst.surfaces.append(std::move(mine_surfaces));
|
||||
dst.needs_merge = true;
|
||||
}
|
||||
}
|
||||
@@ -3172,15 +3312,428 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector<
|
||||
ByRegion &src = by_region[region_id];
|
||||
if (src.needs_merge) {
|
||||
// Multiple regions were merged into one.
|
||||
src.expolygons = closing_ex(src.expolygons, scaled<float>(10. * EPSILON));
|
||||
normalize_region_surfaces(src.surfaces);
|
||||
}
|
||||
|
||||
layer.get_region(region_id)->slices.set(std::move(src.expolygons), stInternal);
|
||||
layer.get_region(region_id)->slices.set(std::move(src.surfaces));
|
||||
}
|
||||
|
||||
dump_surface_emboss_mixed_layer_state("post-mm-segmentation",
|
||||
print_object,
|
||||
layer_id,
|
||||
layer,
|
||||
layer_range,
|
||||
&segmentation[layer_id]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static float emboss_surface_mixed_shell_override_delta(const LayerRegion &layerm, const ModelVolume &volume);
|
||||
|
||||
struct SurfaceEmbossMixedDebugCandidate
|
||||
{
|
||||
const ModelVolume *volume { nullptr };
|
||||
int region_id { -1 };
|
||||
};
|
||||
|
||||
static bool has_surface_emboss_mixed_volume(const PrintObject &print_object)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
if (print == nullptr)
|
||||
return false;
|
||||
|
||||
const size_t num_physical = print->config().filament_diameter.size();
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
for (const ModelVolume *volume : print_object.model_object()->volumes)
|
||||
if (volume->is_model_part() &&
|
||||
volume->emboss_shape.has_value() &&
|
||||
volume->emboss_shape->projection.use_surface &&
|
||||
mixed_mgr.is_mixed(unsigned(std::max(0, volume->extruder_id())), num_physical))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string surface_emboss_mixed_debug_file_path(const PrintObject &print_object)
|
||||
{
|
||||
return debug_out_path("emboss-mixed/obj-%d-debug.txt", int(print_object.id().id));
|
||||
}
|
||||
|
||||
static void reset_surface_emboss_mixed_debug_file(const PrintObject &print_object)
|
||||
{
|
||||
std::ofstream out(surface_emboss_mixed_debug_file_path(print_object), std::ios::out | std::ios::trunc);
|
||||
out << "surface emboss mixed debug"
|
||||
<< " object_id=" << int(print_object.id().id)
|
||||
<< " object_name=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
||||
<< "\n";
|
||||
}
|
||||
|
||||
static void append_surface_emboss_mixed_debug_line(const PrintObject &print_object, const std::string &line)
|
||||
{
|
||||
std::ofstream out(surface_emboss_mixed_debug_file_path(print_object), std::ios::out | std::ios::app);
|
||||
out << line << '\n';
|
||||
}
|
||||
|
||||
static std::vector<SurfaceEmbossMixedDebugCandidate> collect_surface_emboss_mixed_debug_candidates(
|
||||
const Layer &layer,
|
||||
const PrintObjectRegions::LayerRangeRegions &layer_range,
|
||||
const MixedFilamentManager &mixed_mgr,
|
||||
size_t num_physical)
|
||||
{
|
||||
std::vector<SurfaceEmbossMixedDebugCandidate> out;
|
||||
std::vector<int> processed_region_ids;
|
||||
processed_region_ids.reserve(layer_range.volume_regions.size());
|
||||
|
||||
for (const PrintObjectRegions::VolumeRegion &volume_region : layer_range.volume_regions) {
|
||||
const ModelVolume *volume = volume_region.model_volume;
|
||||
if (volume == nullptr || !volume->is_model_part() || !volume->emboss_shape.has_value() || !volume->emboss_shape->projection.use_surface)
|
||||
continue;
|
||||
if (volume_region.region == nullptr)
|
||||
continue;
|
||||
|
||||
const int region_id = volume_region.region->print_object_region_id();
|
||||
if (region_id < 0 || region_id >= layer.region_count())
|
||||
continue;
|
||||
if (std::find(processed_region_ids.begin(), processed_region_ids.end(), region_id) != processed_region_ids.end())
|
||||
continue;
|
||||
processed_region_ids.emplace_back(region_id);
|
||||
|
||||
if (!mixed_mgr.is_mixed(unsigned(std::max(0, volume_region.region->config().wall_filament.value)), num_physical))
|
||||
continue;
|
||||
|
||||
out.push_back({ volume, region_id });
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
static void export_surface_emboss_mixed_layer_svg(
|
||||
const char *stage,
|
||||
const PrintObject &print_object,
|
||||
size_t layer_id,
|
||||
const Layer &layer,
|
||||
const std::vector<SurfaceEmbossMixedDebugCandidate> &candidates,
|
||||
const ExPolygons *overlay,
|
||||
const std::string &overlay_legend)
|
||||
{
|
||||
std::vector<std::pair<ExPolygons, SVG::ExPolygonAttributes>> items;
|
||||
items.reserve(size_t(layer.region_count()) + ((overlay != nullptr && !overlay->empty()) ? 1 : 0));
|
||||
|
||||
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
||||
const LayerRegion *layerm = layer.get_region(region_id);
|
||||
if (layerm == nullptr || layerm->slices.empty())
|
||||
continue;
|
||||
|
||||
ExPolygons expolygons = to_expolygons(layerm->slices.surfaces);
|
||||
if (expolygons.empty())
|
||||
continue;
|
||||
|
||||
const bool is_candidate = std::find_if(candidates.begin(), candidates.end(), [region_id](const auto &candidate) {
|
||||
return candidate.region_id == region_id;
|
||||
}) != candidates.end();
|
||||
|
||||
SVG::ExPolygonAttributes attrs(
|
||||
"region " + std::to_string(region_id) + " wall=" + std::to_string(layerm->region().config().wall_filament.value),
|
||||
is_candidate ? "#3b82f6" : "#bfc5cc",
|
||||
is_candidate ? 0.35f : 0.14f);
|
||||
attrs.outline_width = scale_(0.05f);
|
||||
attrs.color_contour = is_candidate ? "blue" : "black";
|
||||
attrs.color_holes = attrs.color_contour;
|
||||
items.emplace_back(std::move(expolygons), std::move(attrs));
|
||||
}
|
||||
|
||||
if (overlay != nullptr && !overlay->empty()) {
|
||||
SVG::ExPolygonAttributes attrs(overlay_legend, "#ef4444", 0.28f);
|
||||
attrs.outline_width = scale_(0.05f);
|
||||
attrs.color_contour = "red";
|
||||
attrs.color_holes = "red";
|
||||
items.emplace_back(*overlay, std::move(attrs));
|
||||
}
|
||||
|
||||
if (!items.empty())
|
||||
SVG::export_expolygons(debug_out_path("emboss-mixed/obj-%d-layer-%03d-%s.svg",
|
||||
int(print_object.id().id),
|
||||
int(layer_id),
|
||||
stage),
|
||||
items);
|
||||
}
|
||||
|
||||
static void dump_surface_emboss_mixed_layer_state(
|
||||
const char *stage,
|
||||
const PrintObject &print_object,
|
||||
size_t layer_id,
|
||||
const Layer &layer,
|
||||
const PrintObjectRegions::LayerRangeRegions &layer_range,
|
||||
const std::vector<ExPolygons> *segmentation_layer = nullptr)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
if (print == nullptr)
|
||||
return;
|
||||
|
||||
const size_t num_physical = print->config().filament_diameter.size();
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
const std::vector<SurfaceEmbossMixedDebugCandidate> candidates =
|
||||
collect_surface_emboss_mixed_debug_candidates(layer, layer_range, mixed_mgr, num_physical);
|
||||
if (candidates.empty())
|
||||
return;
|
||||
|
||||
std::ostringstream header;
|
||||
header << std::fixed << std::setprecision(4)
|
||||
<< "stage=" << stage
|
||||
<< " layer=" << layer_id
|
||||
<< " print_z=" << layer.print_z
|
||||
<< " slice_z=" << layer.slice_z
|
||||
<< " regions=" << layer.region_count()
|
||||
<< " candidates=" << candidates.size();
|
||||
append_surface_emboss_mixed_debug_line(print_object, header.str());
|
||||
|
||||
for (int region_id = 0; region_id < layer.region_count(); ++region_id) {
|
||||
const LayerRegion *layerm = layer.get_region(region_id);
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
const double slice_area = layerm->slices.empty() ? 0.0 : std::abs(area(to_expolygons(layerm->slices.surfaces)));
|
||||
std::ostringstream line;
|
||||
line << std::fixed << std::setprecision(4)
|
||||
<< " region=" << region_id
|
||||
<< " wall=" << layerm->region().config().wall_filament.value
|
||||
<< " sparse=" << layerm->region().config().sparse_infill_filament.value
|
||||
<< " solid=" << layerm->region().config().solid_infill_filament.value
|
||||
<< " area=" << slice_area;
|
||||
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
||||
}
|
||||
|
||||
for (const SurfaceEmbossMixedDebugCandidate &candidate : candidates) {
|
||||
const LayerRegion *layerm = layer.get_region(candidate.region_id);
|
||||
if (layerm == nullptr)
|
||||
continue;
|
||||
|
||||
const double slice_area = layerm->slices.empty() ? 0.0 : std::abs(area(to_expolygons(layerm->slices.surfaces)));
|
||||
const float shell_delta_scaled = emboss_surface_mixed_shell_override_delta(*layerm, *candidate.volume);
|
||||
std::ostringstream line;
|
||||
line << std::fixed << std::setprecision(4)
|
||||
<< " candidate region=" << candidate.region_id
|
||||
<< " volume_name=" << candidate.volume->name
|
||||
<< " volume_extruder=" << candidate.volume->extruder_id()
|
||||
<< " cfg_wall=" << layerm->region().config().wall_filament.value
|
||||
<< " depth=" << float(candidate.volume->emboss_shape->projection.depth)
|
||||
<< " shell_delta_mm=" << unscale<double>(shell_delta_scaled)
|
||||
<< " area=" << slice_area;
|
||||
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
||||
|
||||
if (segmentation_layer != nullptr) {
|
||||
const int cfg_wall = layerm->region().config().wall_filament.value;
|
||||
if (cfg_wall >= 1 && cfg_wall <= int(segmentation_layer->size())) {
|
||||
const double seg_area = std::abs(area((*segmentation_layer)[size_t(cfg_wall - 1)]));
|
||||
std::ostringstream seg_line;
|
||||
seg_line << std::fixed << std::setprecision(4)
|
||||
<< " segmentation channel=" << cfg_wall
|
||||
<< " area=" << seg_area;
|
||||
append_surface_emboss_mixed_debug_line(print_object, seg_line.str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export_surface_emboss_mixed_layer_svg(stage, print_object, layer_id, layer, candidates, nullptr, "");
|
||||
}
|
||||
|
||||
static float emboss_surface_mixed_shell_override_delta(const LayerRegion &layerm, const ModelVolume &volume)
|
||||
{
|
||||
if (!volume.emboss_shape.has_value() || !volume.emboss_shape->projection.use_surface)
|
||||
return 0.f;
|
||||
|
||||
const float depth_mm = std::max(0.f, float(volume.emboss_shape->projection.depth));
|
||||
if (depth_mm <= EPSILON)
|
||||
return 0.f;
|
||||
|
||||
const PrintRegionConfig &config = layerm.region().config();
|
||||
if (config.wall_loops.value <= 0)
|
||||
return 0.f;
|
||||
|
||||
const Flow ext_flow = layerm.flow(frExternalPerimeter);
|
||||
const Flow perimeter_flow = layerm.flow(frPerimeter);
|
||||
const coord_t shell_scaled = ext_flow.scaled_width() / 2 +
|
||||
ext_flow.scaled_spacing() / 2 +
|
||||
std::max(0, config.wall_loops.value - 1) * perimeter_flow.scaled_spacing();
|
||||
const float shell_depth_mm = float(unscale<double>(shell_scaled));
|
||||
const float delta_mm = std::max(0.f, shell_depth_mm - depth_mm);
|
||||
return delta_mm <= EPSILON ? 0.f : scaled<float>(delta_mm);
|
||||
}
|
||||
|
||||
template<typename ThrowOnCancel>
|
||||
static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
|
||||
{
|
||||
const Print *print = print_object.print();
|
||||
if (print == nullptr || print_object.layer_count() == 0 || print_object.shared_regions() == nullptr)
|
||||
return false;
|
||||
|
||||
const size_t num_physical = print->config().filament_diameter.size();
|
||||
const MixedFilamentManager &mixed_mgr = print->mixed_filament_manager();
|
||||
const auto &volumes = print_object.model_object()->volumes;
|
||||
if (num_physical == 0 ||
|
||||
std::find_if(volumes.begin(), volumes.end(), [&mixed_mgr, num_physical](const ModelVolume *volume) {
|
||||
return volume->is_model_part() &&
|
||||
volume->emboss_shape.has_value() &&
|
||||
volume->emboss_shape->projection.use_surface &&
|
||||
mixed_mgr.is_mixed(unsigned(std::max(0, volume->extruder_id())), num_physical);
|
||||
}) == volumes.end())
|
||||
return false;
|
||||
|
||||
const auto &layer_ranges = print_object.shared_regions()->layer_ranges;
|
||||
auto it_layer_range = layer_range_first(layer_ranges, print_object.get_layer(0)->slice_z);
|
||||
|
||||
size_t changed_layers = 0;
|
||||
size_t changed_regions = 0;
|
||||
size_t stolen_regions = 0;
|
||||
|
||||
for (size_t layer_id = 0; layer_id < print_object.layer_count(); ++layer_id) {
|
||||
throw_on_cancel();
|
||||
|
||||
Layer &layer = *print_object.get_layer(int(layer_id));
|
||||
it_layer_range = layer_range_next(layer_ranges, it_layer_range, layer.slice_z);
|
||||
const PrintObjectRegions::LayerRangeRegions &layer_range = *it_layer_range;
|
||||
const std::vector<SurfaceEmbossMixedDebugCandidate> candidates =
|
||||
collect_surface_emboss_mixed_debug_candidates(layer, layer_range, mixed_mgr, num_physical);
|
||||
if (!candidates.empty())
|
||||
dump_surface_emboss_mixed_layer_state("pre-emboss-override", print_object, layer_id, layer, layer_range);
|
||||
|
||||
bool layer_changed = false;
|
||||
ExPolygons layer_masks;
|
||||
std::vector<int> processed_region_ids;
|
||||
processed_region_ids.reserve(layer_range.volume_regions.size());
|
||||
|
||||
for (const PrintObjectRegions::VolumeRegion &volume_region : layer_range.volume_regions) {
|
||||
const ModelVolume *volume = volume_region.model_volume;
|
||||
if (volume == nullptr || !volume->is_model_part() || !volume->emboss_shape.has_value() || !volume->emboss_shape->projection.use_surface)
|
||||
continue;
|
||||
if (volume_region.region == nullptr)
|
||||
continue;
|
||||
|
||||
const int region_id = volume_region.region->print_object_region_id();
|
||||
if (region_id < 0 || region_id >= layer.region_count())
|
||||
continue;
|
||||
if (std::find(processed_region_ids.begin(), processed_region_ids.end(), region_id) != processed_region_ids.end())
|
||||
continue;
|
||||
processed_region_ids.emplace_back(region_id);
|
||||
|
||||
const unsigned int filament_id = unsigned(std::max(0, volume_region.region->config().wall_filament.value));
|
||||
if (!mixed_mgr.is_mixed(filament_id, num_physical))
|
||||
continue;
|
||||
|
||||
LayerRegion *emboss_layerm = layer.get_region(region_id);
|
||||
if (emboss_layerm == nullptr || emboss_layerm->slices.empty())
|
||||
continue;
|
||||
|
||||
ExPolygons override_mask = to_expolygons(emboss_layerm->slices.surfaces);
|
||||
if (override_mask.empty())
|
||||
continue;
|
||||
|
||||
if (const float delta_scaled = emboss_surface_mixed_shell_override_delta(*emboss_layerm, *volume);
|
||||
delta_scaled > float(EPSILON)) {
|
||||
override_mask = offset_ex(override_mask, delta_scaled);
|
||||
if (override_mask.empty())
|
||||
continue;
|
||||
if (layer_masks.empty())
|
||||
layer_masks = collect_layer_region_slices(layer);
|
||||
if (!layer_masks.empty())
|
||||
override_mask = intersection_ex(override_mask, layer_masks);
|
||||
if (override_mask.empty())
|
||||
continue;
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream line;
|
||||
line << std::fixed << std::setprecision(4)
|
||||
<< "stage=override-mask"
|
||||
<< " layer=" << layer_id
|
||||
<< " region=" << region_id
|
||||
<< " volume_name=" << volume->name
|
||||
<< " volume_extruder=" << volume->extruder_id()
|
||||
<< " cfg_wall=" << volume_region.region->config().wall_filament.value
|
||||
<< " depth=" << float(volume->emboss_shape->projection.depth)
|
||||
<< " shell_delta_mm=" << unscale<double>(emboss_surface_mixed_shell_override_delta(*emboss_layerm, *volume))
|
||||
<< " mask_area=" << std::abs(area(override_mask));
|
||||
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
||||
}
|
||||
const std::string overlay_stage = "override-mask-r" + std::to_string(region_id);
|
||||
export_surface_emboss_mixed_layer_svg(overlay_stage.c_str(),
|
||||
print_object,
|
||||
layer_id,
|
||||
layer,
|
||||
candidates,
|
||||
&override_mask,
|
||||
"override mask");
|
||||
|
||||
ExPolygons emboss_slices = to_expolygons(emboss_layerm->slices.surfaces);
|
||||
bool emboss_changed = false;
|
||||
|
||||
for (int target_region_id = 0; target_region_id < layer.region_count(); ++target_region_id) {
|
||||
if (target_region_id == region_id)
|
||||
continue;
|
||||
|
||||
LayerRegion *target_layerm = layer.get_region(target_region_id);
|
||||
if (target_layerm == nullptr || target_layerm->slices.empty())
|
||||
continue;
|
||||
if (target_layerm->region().config().wall_filament.value == int(filament_id))
|
||||
continue;
|
||||
|
||||
ExPolygons stolen = intersection_ex(target_layerm->slices.surfaces, override_mask);
|
||||
if (stolen.empty())
|
||||
continue;
|
||||
|
||||
append(emboss_slices, stolen);
|
||||
emboss_changed = true;
|
||||
++stolen_regions;
|
||||
|
||||
std::ostringstream line;
|
||||
line << std::fixed << std::setprecision(4)
|
||||
<< "stage=override-steal"
|
||||
<< " layer=" << layer_id
|
||||
<< " emboss_region=" << region_id
|
||||
<< " from_region=" << target_region_id
|
||||
<< " from_wall=" << target_layerm->region().config().wall_filament.value
|
||||
<< " stolen_area=" << std::abs(area(stolen));
|
||||
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
||||
|
||||
Polygons remaining = diff(to_polygons(target_layerm->slices.surfaces), override_mask);
|
||||
if (!remaining.empty())
|
||||
remaining = opening(union_ex(remaining), scaled<float>(5. * EPSILON), scaled<float>(5. * EPSILON));
|
||||
target_layerm->slices.set(union_ex(remaining), stInternal);
|
||||
layer_changed = true;
|
||||
}
|
||||
|
||||
if (emboss_changed) {
|
||||
if (emboss_slices.size() > 1)
|
||||
emboss_slices = closing_ex(emboss_slices, scaled<float>(10. * EPSILON));
|
||||
emboss_layerm->slices.set(std::move(emboss_slices), stInternal);
|
||||
++changed_regions;
|
||||
layer_changed = true;
|
||||
} else {
|
||||
std::ostringstream line;
|
||||
line << "stage=override-no-steal"
|
||||
<< " layer=" << layer_id
|
||||
<< " region=" << region_id
|
||||
<< " volume_name=" << volume->name;
|
||||
append_surface_emboss_mixed_debug_line(print_object, line.str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates.empty())
|
||||
dump_surface_emboss_mixed_layer_state("post-emboss-override", print_object, layer_id, layer, layer_range);
|
||||
|
||||
if (layer_changed)
|
||||
++changed_layers;
|
||||
}
|
||||
|
||||
if (changed_regions == 0)
|
||||
return false;
|
||||
|
||||
BOOST_LOG_TRIVIAL(warning) << "Surface emboss mixed-region override applied"
|
||||
<< " object=" << (print_object.model_object() ? print_object.model_object()->name : std::string("<unknown>"))
|
||||
<< " changed_layers=" << changed_layers
|
||||
<< " changed_regions=" << changed_regions
|
||||
<< " stolen_regions=" << stolen_regions;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename ThrowOnCancel>
|
||||
void apply_fuzzy_skin_segmentation(PrintObject &print_object, ThrowOnCancel throw_on_cancel)
|
||||
{
|
||||
@@ -3398,6 +3951,8 @@ void PrintObject::slice_volumes()
|
||||
apply_fuzzy_skin_segmentation(*this, [print]() { print->throw_if_canceled(); });
|
||||
}
|
||||
|
||||
apply_surface_emboss_mixed_region_override(*this, [print]() { print->throw_if_canceled(); });
|
||||
|
||||
InterlockingGenerator::generate_interlocking_structure(this);
|
||||
m_print->throw_if_canceled();
|
||||
|
||||
|
||||
@@ -16,16 +16,19 @@ add_executable(${_TEST_NAME}_tests
|
||||
test_support_material.cpp
|
||||
test_trianglemesh.cpp
|
||||
)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r OpenSSL::Crypto)
|
||||
if (WIN32)
|
||||
target_link_libraries(${_TEST_NAME}_tests bcrypt.lib)
|
||||
endif()
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
if (WIN32)
|
||||
if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
|
||||
orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
|
||||
Snapmaker_Orca_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
|
||||
elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
|
||||
orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
|
||||
Snapmaker_Orca_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
|
||||
else()
|
||||
orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
|
||||
Snapmaker_Orca_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
@@ -6,14 +6,14 @@
|
||||
#include "test_data.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <boost/regex.hpp>
|
||||
#include <regex>
|
||||
|
||||
using namespace Slic3r;
|
||||
using namespace Slic3r::Test;
|
||||
|
||||
boost::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter");
|
||||
boost::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill");
|
||||
boost::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt");
|
||||
std::regex perimeters_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; perimeter");
|
||||
std::regex infill_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; infill");
|
||||
std::regex skirt_regex("G1 X[-0-9.]* Y[-0-9.]* E[-0-9.]* ; skirt");
|
||||
|
||||
SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
|
||||
GIVEN("A default configuration and a print test object") {
|
||||
@@ -61,16 +61,16 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
|
||||
REQUIRE(gcode.find("; fill_density") != std::string::npos);
|
||||
}
|
||||
THEN("Infill is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, infill_regex));
|
||||
std::smatch has_match;
|
||||
REQUIRE(std::regex_search(gcode, has_match, infill_regex));
|
||||
}
|
||||
THEN("Perimeters are emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, perimeters_regex));
|
||||
std::smatch has_match;
|
||||
REQUIRE(std::regex_search(gcode, has_match, perimeters_regex));
|
||||
}
|
||||
THEN("Skirt is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, skirt_regex));
|
||||
std::smatch has_match;
|
||||
REQUIRE(std::regex_search(gcode, has_match, skirt_regex));
|
||||
}
|
||||
THEN("final Z height is 20mm") {
|
||||
double final_z = 0.0;
|
||||
@@ -100,16 +100,16 @@ SCENARIO( "PrintGCode basic functionality", "[PrintGCode]") {
|
||||
REQUIRE(gcode.size() > 0);
|
||||
}
|
||||
THEN("Infill is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, infill_regex));
|
||||
std::smatch has_match;
|
||||
REQUIRE(std::regex_search(gcode, has_match, infill_regex));
|
||||
}
|
||||
THEN("Perimeters are emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, perimeters_regex));
|
||||
std::smatch has_match;
|
||||
REQUIRE(std::regex_search(gcode, has_match, perimeters_regex));
|
||||
}
|
||||
THEN("Skirt is emitted.") {
|
||||
boost::smatch has_match;
|
||||
REQUIRE(boost::regex_search(gcode, has_match, skirt_regex));
|
||||
std::smatch has_match;
|
||||
REQUIRE(std::regex_search(gcode, has_match, skirt_regex));
|
||||
}
|
||||
THEN("Between-object-gcode is emitted.") {
|
||||
REQUIRE(gcode.find("; between-object-gcode") != std::string::npos);
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
get_filename_component(_TEST_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
|
||||
add_executable(${_TEST_NAME}_tests ${_TEST_NAME}_tests_main.cpp printer_parts.cpp printer_parts.hpp)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libnest2d )
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libnest2d OpenSSL::Crypto)
|
||||
if (WIN32)
|
||||
target_link_libraries(${_TEST_NAME}_tests bcrypt.lib)
|
||||
endif()
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
catch_discover_tests(${_TEST_NAME}_tests TEST_PREFIX "${_TEST_NAME}: ")
|
||||
|
||||
@@ -32,7 +32,10 @@ if (TARGET OpenVDB::openvdb)
|
||||
target_sources(${_TEST_NAME}_tests PRIVATE test_hollowing.cpp)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r)
|
||||
target_link_libraries(${_TEST_NAME}_tests test_common libslic3r OpenSSL::Crypto)
|
||||
if (WIN32)
|
||||
target_link_libraries(${_TEST_NAME}_tests bcrypt.lib)
|
||||
endif()
|
||||
set_property(TARGET ${_TEST_NAME}_tests PROPERTY FOLDER "tests")
|
||||
|
||||
if (WIN32)
|
||||
|
||||
@@ -66,7 +66,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
|
||||
GIVEN("polyline") {
|
||||
Polyline polyline { { 50, 150 }, { 300, 150 } };
|
||||
WHEN("intersection_pl") {
|
||||
Polylines result = Slic3r::intersection_pl({ polyline }, { square, hole_in_square });
|
||||
Polylines result = Slic3r::intersection_pl(Polylines { polyline }, Polygons { square, hole_in_square });
|
||||
THEN("correct number of result lines") {
|
||||
REQUIRE(result.size() == 2);
|
||||
}
|
||||
@@ -99,7 +99,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
|
||||
{ 74730000, 74730000 }, { 55270000, 74730000 }, { 55270000, 68063296 }, { 44730000, 68063296 }, { 44730000, 74730000 }, { 25270000, 74730000 }, { 25270000, 55270000 }, { 31936670, 55270000 },
|
||||
{ 31936670, 44730000 }, { 25270000, 44730000 }, { 25270000, 25270000 }, { 44730000, 25270000 }, { 44730000, 31936670 } };
|
||||
Slic3r::Polygon clip { {75200000, 45200000}, {54800000, 45200000}, {54800000, 24800000}, {75200000, 24800000} };
|
||||
Slic3r::Polylines result = Slic3r::intersection_pl(subject, { clip });
|
||||
Slic3r::Polylines result = Slic3r::intersection_pl(subject, Polygons { clip });
|
||||
THEN("intersection_pl - result is not empty") {
|
||||
REQUIRE(result.size() == 1);
|
||||
}
|
||||
@@ -117,7 +117,7 @@ SCENARIO("Various Clipper operations - xs/t/11_clipper.t", "[ClipperUtils]") {
|
||||
GIVEN("Clipper bug #126") {
|
||||
Slic3r::Polyline subject { { 200000, 19799999 }, { 200000, 200000 }, { 24304692, 200000 }, { 15102879, 17506106 }, { 13883200, 19799999 }, { 200000, 19799999 } };
|
||||
Slic3r::Polygon clip { { 15257205, 18493894 }, { 14350057, 20200000 }, { -200000, 20200000 }, { -200000, -200000 }, { 25196917, -200000 } };
|
||||
Slic3r::Polylines result = Slic3r::intersection_pl(subject, { clip });
|
||||
Slic3r::Polylines result = Slic3r::intersection_pl(subject, Polygons { clip });
|
||||
THEN("intersection_pl - result is not empty") {
|
||||
REQUIRE(result.size() == 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user