diff --git a/src/libslic3r/Config.cpp b/src/libslic3r/Config.cpp index 7c8c30babe..7c286264e4 100644 --- a/src/libslic3r/Config.cpp +++ b/src/libslic3r/Config.cpp @@ -904,8 +904,33 @@ int ConfigBase::load_from_json(const std::string &file, ConfigSubstitutionContex if (optdef && optdef->type == coStrings) { use_comma = false; } + std::vector 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 "<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 "<set_deserialize(opt_key, value_str, substitution_context); diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index 88bced4642..032a671411 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -842,7 +842,8 @@ std::vector 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 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 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); diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index c5fe201d10..568ba677bd 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -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; + +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 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 slices; // extra_perimeters => [ surface, surface... ] + // Keep surface typing intact when compatible regions are merged for perimeter generation. + std::map 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 &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); } } } diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 937d5ccabd..b0adc22f4a 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -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, diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index f44d127d7c..055ad37796 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1830,15 +1830,14 @@ static std::vector> merge_segmented_layers(const std::ve { const size_t num_layers = segmented_regions.size(); std::vector> segmented_regions_merged(num_layers); - segmented_regions_merged.assign(num_layers, std::vector(num_facets_states - 1)); + segmented_regions_merged.assign(num_layers, std::vector(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(0, num_layers), [&segmented_regions, &top_and_bottom_layers, &segmented_regions_merged, &num_facets_states, &throw_on_cancel_callback](const tbb::blocked_range &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> 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)); } } } diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 2d7c23b0a9..383343a8d4 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -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 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))); diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 07dfc925f1..435acf9614 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -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 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. diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index c6fb321ecf..ce9e266237 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -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& prefered_filament_profiles = preferred_printer->config.option("default_filament_profile")->values; - if ((!initial_filament_profile_name.compare("Default Filament")) && (prefered_filament_profiles.size() > 0)) + const std::vector& prefered_filament_profiles = selected_printer->config.option("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 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>& subfile_map, + bool recursive) { + if (!fs::exists(section_dir)) + return; + + std::vector 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 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 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 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()) { diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index d53b359929..a1befe1f89 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -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(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) { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index c2a7130701..ed5259fd01 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -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("")) + << " 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> &segmentation) { const Print *print = print_object.print(); @@ -2290,11 +2308,37 @@ static void build_local_z_plan(PrintObject &print_object, const std::vectormixed_filament_manager(); const auto &mixed_rows = mixed_mgr.mixed_filaments(); - size_t pointillism_rows = 0; - for (const MixedFilament &mf : mixed_rows) { - const std::vector sequence = pointillism_sequence_for_row(mf, num_physical); + std::vector 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 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 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::vectorlayer_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 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 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(10. * EPSILON)); + src.append(std::move(typed), SurfaceType(surface_type)); + } + }; + std::vector by_extruder; std::vector 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 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 assigned_extruder(by_extruder.size(), false); std::vector 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(5. * EPSILON), scaled(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(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("")) + << "\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 collect_surface_emboss_mixed_debug_candidates( + const Layer &layer, + const PrintObjectRegions::LayerRangeRegions &layer_range, + const MixedFilamentManager &mixed_mgr, + size_t num_physical) +{ + std::vector out; + std::vector 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 &candidates, + const ExPolygons *overlay, + const std::string &overlay_legend) +{ + std::vector> 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 *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 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(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(shell_scaled)); + const float delta_mm = std::max(0.f, shell_depth_mm - depth_mm); + return delta_mm <= EPSILON ? 0.f : scaled(delta_mm); +} + +template +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 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 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(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(5. * EPSILON), scaled(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(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("")) + << " changed_layers=" << changed_layers + << " changed_regions=" << changed_regions + << " stolen_regions=" << stolen_regions; + return true; +} + template 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(); diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 0083742696..5e282b08a5 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -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() diff --git a/tests/fff_print/test_printgcode.cpp b/tests/fff_print/test_printgcode.cpp index 2a45bd2003..0cd7423ab8 100644 --- a/tests/fff_print/test_printgcode.cpp +++ b/tests/fff_print/test_printgcode.cpp @@ -6,14 +6,14 @@ #include "test_data.hpp" #include -#include +#include 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); diff --git a/tests/libnest2d/CMakeLists.txt b/tests/libnest2d/CMakeLists.txt index 07bc8292b3..26af377e27 100644 --- a/tests/libnest2d/CMakeLists.txt +++ b/tests/libnest2d/CMakeLists.txt @@ -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}: ") diff --git a/tests/libslic3r/CMakeLists.txt b/tests/libslic3r/CMakeLists.txt index 33485d352a..4fc56d8875 100644 --- a/tests/libslic3r/CMakeLists.txt +++ b/tests/libslic3r/CMakeLists.txt @@ -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) diff --git a/tests/libslic3r/test_clipper_utils.cpp b/tests/libslic3r/test_clipper_utils.cpp index 86afdcbcc4..c6de5dd40b 100644 --- a/tests/libslic3r/test_clipper_utils.cpp +++ b/tests/libslic3r/test_clipper_utils.cpp @@ -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); }