From 9c4258d43e0a0e41c0263525e83cad9fce35ab6a Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:58:30 +0200 Subject: [PATCH 01/43] This commit adds support for multi nozzle printing. Printing with different nozzle sizes is now possible and each extruder can now print at a different semi fixed / semi adaptive layer height depending on user choice. Includes: - Fix Filament for Features (OrcaSlicer#13607) - Fix + Support 'Default' filament option, index 0 (OrcaSlicer#13887) - Filament for features for SEMM (OrcaSlicer#13937) - Divided filament for features (OrcaSlicer#14042) - Port of LixNix/OrcaSlicer-multi-nozzle-size-printing commit 944da13 --- src/libslic3r/Brim.cpp | 5 +- src/libslic3r/Fill/Fill.cpp | 23 +- src/libslic3r/Flow.cpp | 19 +- src/libslic3r/Flow.hpp | 2 + src/libslic3r/GCode.cpp | 398 ++++++---- src/libslic3r/GCode/ToolOrdering.cpp | 317 ++++++-- src/libslic3r/GCode/ToolOrdering.hpp | 9 +- src/libslic3r/GCode/WipeTower2.cpp | 87 ++- src/libslic3r/GCode/WipeTower2.hpp | 11 +- src/libslic3r/Layer.cpp | 22 +- src/libslic3r/Layer.hpp | 28 +- src/libslic3r/LayerRegion.cpp | 64 +- src/libslic3r/MultiMaterialSegmentation.cpp | 2 +- src/libslic3r/PerimeterGenerator.cpp | 37 +- src/libslic3r/PerimeterGenerator.hpp | 10 +- src/libslic3r/Preset.cpp | 6 +- src/libslic3r/PresetBundle.cpp | 13 +- src/libslic3r/Print.cpp | 287 +++++++- src/libslic3r/Print.hpp | 23 +- src/libslic3r/PrintApply.cpp | 36 +- src/libslic3r/PrintConfig.cpp | 238 +++++- src/libslic3r/PrintConfig.hpp | 22 +- src/libslic3r/PrintObject.cpp | 472 +++++++++++- src/libslic3r/PrintObjectSlice.cpp | 218 +++++- src/libslic3r/PrintRegion.cpp | 54 +- src/libslic3r/Slicing.cpp | 39 +- src/libslic3r/Support/SupportCommon.cpp | 2 +- src/libslic3r/Support/SupportMaterial.cpp | 4 +- src/libslic3r/Support/SupportParameters.hpp | 3 +- src/libslic3r/Support/TreeSupport.cpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 22 +- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 12 +- src/slic3r/GUI/PartPlate.cpp | 156 +++- src/slic3r/GUI/Plater.cpp | 119 +-- src/slic3r/GUI/PresetHints.cpp | 9 +- src/slic3r/GUI/Tab.cpp | 41 +- tests/fff_print/CMakeLists.txt | 1 + .../test_multi_nozzle_layer_height.cpp | 684 ++++++++++++++++++ tests/libslic3r/test_mixed_filament.cpp | 31 +- 40 files changed, 2915 insertions(+), 615 deletions(-) create mode 100644 tests/fff_print/test_multi_nozzle_layer_height.cpp diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 5ee502acb89..30368cce989 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -1040,7 +1040,10 @@ static ExPolygons outer_inner_brim_area(const Print& print, } support_material_extruder = object->config().support_filament; if (support_material_extruder == 0 && object->has_support_material()) { - if (print.config().print_sequence == PrintSequence::ByObject) + // ORCA: under the support nozzle diameter restriction the brim uses the support's resolved filament. + if (unsigned int resolved = object->resolved_default_support_filament(); resolved > 0) + support_material_extruder = resolved; + else if (print.config().print_sequence == PrintSequence::ByObject) support_material_extruder = objectWithExtruder.second; else support_material_extruder = printExtruders.front() + 1; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ea899aa3b39..7f1187c1d21 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -896,6 +896,14 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.extrusion_role = erSolidInfill; } } + // ORCA: per-feature filaments. Top and internal solid fills are already resolved + // by layerm.extruder(extrusion_role) above; bottom surfaces print with the bottom + // surface filament, routed through the same mixed-filament remapping as top surfaces. + // External bridges are bottom surfaces, so they print with the bottom surface filament + // too (internal bridges keep their pre-seeded filament). + if (params.extrusion_role == erBottomSurface || params.extrusion_role == erBridgeInfill) + params.extruder = effective_layer_filament_id(layer, + (unsigned int)std::max(0, region_config.bottom_surface_filament_id.value)); // Orca: apply fill multiline only for sparse infill params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1; @@ -919,9 +927,12 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); const bool is_thick_bridge = surface.is_bridge() && (surface.is_internal_bridge() ? object_config.thick_internal_bridges : object_config.thick_bridges); params.flow = params.bridge ? - //Orca: enable thick bridge based on config - layerm.bridging_flow(extrusion_role, is_thick_bridge) : - layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness); + //Orca: enable thick bridge based on config. Combined layers stamp their full thickness on the surface; the non-thick bridge flow must be based on it. + layerm.bridging_flow(extrusion_role, is_thick_bridge, params.extruder, + (surface.thickness == -1) ? 0. : surface.thickness) : + // Width resolves against the nozzle of the filament that actually prints + // (params.extruder), which may differ from the role's default filament mapping. + layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness, params.extruder); // record speed params if (!params.bridge) { if (params.extrusion_role == erInternalInfill) @@ -1530,12 +1541,12 @@ void Layer::make_ironing() ((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) && (config.ironing_type == IroningType::TopSurfaces || (config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) { - if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) { + if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) { // Iron the whole face. - ironing_params.extruder = config.solid_infill_filament; + ironing_params.extruder = config.top_surface_filament_id; } else { // Iron just the infill. - ironing_params.extruder = config.solid_infill_filament; + ironing_params.extruder = config.top_surface_filament_id; } } if (ironing_params.extruder != -1) { diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index dbe7e157ab1..0008c13e4d8 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -211,21 +211,29 @@ double Flow::mm3_per_mm() const return res; } +// Nozzle diameter driving support / raft flows. A "default" (0) filament prints with the active extruder; the support_nozzle_diameter restriction defines which nozzle that may be. +float support_material_nozzle_diameter(const PrintObject *object, int configured_filament) +{ + if (configured_filament == 0 && object->config().support_nozzle_diameter.value > 0.) + return float(object->config().support_nozzle_diameter.value); + // for configured_filament == 0 (use the current extruder), get_at returns the 0th component. + return float(object->print()->config().nozzle_diameter.get_at(configured_filament - 1)); +} + Flow support_material_flow(const PrintObject *object, float layer_height) { return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width, - // if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)), + support_material_nozzle_diameter(object, object->config().support_filament), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } //BBS Flow support_transition_flow(const PrintObject* object) { //BBS: support transition of tree support is bridge flow - float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1)); + float dmr = support_material_nozzle_diameter(object, object->config().support_filament); return Flow::bridging_flow(dmr, dmr); } @@ -237,7 +245,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().line_width, - float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)), + support_material_nozzle_diameter(object, object->config().support_filament), (layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value)); } @@ -247,8 +255,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig frSupportMaterialInterface, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width, - // if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)), + support_material_nozzle_diameter(object, object->config().support_interface_filament), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 79cb1b324d6..a8028a9c6fa 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -139,6 +139,8 @@ class Flow bool m_bridge { false }; }; +// ORCA: nozzle driving support / raft flows: the configured filament's, or the support_nozzle_diameter restriction when the filament is left at "default" (0). +extern float support_material_nozzle_diameter(const PrintObject *object, int configured_filament); extern Flow support_material_flow(const PrintObject* object, float layer_height = 0.f); extern Flow support_transition_flow(const PrintObject *object); //BBS extern Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height = 0.f); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index adfcc0dc651..038d8728437 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1513,7 +1513,12 @@ std::vector GCode::collect_layers_to_print(const PrintObjec } extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); } - double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height + + double layer_span = layer_to_print.layer()->height; + // ORCA: the top layer of a combined group prints the whole group at once, so tolerate a gap of combined_height() (equals the layer height when not combined). + if (layer_to_print.object_layer != nullptr) + for (const LayerRegion *layerm : layer_to_print.object_layer->regions()) + layer_span = std::max(layer_span, layerm->combined_height()); + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_span + std::max(0., extra_gap); // Negative support_contact_z is not taken into account, it can result in false positives in cases @@ -5003,6 +5008,7 @@ LayerResult GCode::process_layer(const Print& print, // Group extrusions by an extruder, then by an object, an island and a region. std::map> by_extruder; + std::vector> split_perimeter_storage; bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); const double nozzle_0_mm = m_config.nozzle_diameter.values.empty() ? 0.4 : m_config.nozzle_diameter.get_at(0); const double pointillism_pixel_size_cfg = std::max(0.0, double(m_config.mixed_filament_pointillism_pixel_size.value)); @@ -5024,28 +5030,60 @@ LayerResult GCode::process_layer(const Print& print, if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { if (layer_tools.extruder_override != 0) return layer_tools.extruder_override; - const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); + // gap fill inherits the filament of the surface it fills; derive the role from the + // first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + ExtrusionRole role = erNone; + for (const ExtrusionEntity *fill_entity : entities.entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } + if (role == erNone && ! entities.entities.empty()) + // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. + return unsigned(region.config().outer_wall_filament_id.value); if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) - return unsigned(region.config().sparse_infill_filament.value); + return unsigned(region.config().sparse_infill_filament_id.value); + if (role == erTopSolidInfill || role == erIroning) + return unsigned(region.config().top_surface_filament_id.value); + if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) + return unsigned(region.config().bottom_surface_filament_id.value); if (is_solid_infill(role)) - return unsigned(region.config().solid_infill_filament.value); - return unsigned(region.config().sparse_infill_filament.value); + return unsigned(region.config().internal_solid_filament_id.value); + return unsigned(region.config().sparse_infill_filament_id.value); } - return layer_tools.extruder_override == 0 ? unsigned(region.config().wall_filament.value) : layer_tools.extruder_override; + if (layer_tools.extruder_override != 0) + return layer_tools.extruder_override; + return entities.role() == erPerimeter ? unsigned(region.config().inner_wall_filament_id.value) + : unsigned(region.config().outer_wall_filament_id.value); }; auto configured_extruder_id = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type, const ExtrusionEntityCollection& entities, const PrintRegion& region) -> int { if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { - const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); + // gap fill inherits the filament of the surface it fills; derive the role from the + // first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + ExtrusionRole role = erNone; + for (const ExtrusionEntity *fill_entity : entities.entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } + if (role == erNone && ! entities.entities.empty()) + // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. + return int(layer_tools.wall_extruder_id(region)); if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) - return int(layer_tools.sparse_infill_filament(region)); + return int(layer_tools.sparse_infill_filament_id(region)); + if (role == erTopSolidInfill || role == erIroning) + return int(layer_tools.top_surface_filament_id(region)); + if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) + return int(layer_tools.bottom_surface_filament_id(region)); if (is_solid_infill(role)) - return int(layer_tools.solid_infill_filament(region)); - return int(layer_tools.sparse_infill_filament(region)); + return int(layer_tools.internal_solid_filament_id(region)); + return int(layer_tools.sparse_infill_filament_id(region)); } - return int(layer_tools.wall_filament(region)); + return entities.role() == erPerimeter ? int(layer_tools.inner_wall_extruder_id(region)) + : int(layer_tools.wall_extruder_id(region)); }; auto pointillism_sequence_for_filament = [&](unsigned int filament_id_1based) -> const std::vector* { @@ -5381,6 +5419,29 @@ LayerResult GCode::process_layer(const Print& print, if (interface_dontcare) interface_extruder = dontcare_extruder; } + // ORCA: with a support nozzle diameter restriction, ("don't care") support/interface may only use a nozzle-matching extruder; prefer one scheduled on this layer (as ToolOrdering did). + if (object.config().support_nozzle_diameter.value > 0.) { + auto restrict_to_support_nozzle = [&print, &object, &layer_tools](unsigned int extruder_id) -> unsigned int { + if (object.support_filament_allowed(extruder_id + 1)) + return extruder_id; + unsigned int fallback = extruder_id; + bool have_fallback = false; + for (unsigned int candidate : layer_tools.extruders) // 0 based at this point + if (object.support_filament_allowed(candidate + 1)) { + if (! print.config().filament_soluble.get_at(candidate)) + return candidate; + if (! have_fallback) { + fallback = candidate; + have_fallback = true; + } + } + return fallback; + }; + if (support_dontcare) + support_extruder = restrict_to_support_nozzle(support_extruder); + if (interface_dontcare) + interface_extruder = restrict_to_support_nozzle(interface_extruder); + } // Both the support and the support interface are printed with the same extruder, therefore // the interface may be interleaved with the support base. bool single_extruder = !has_support || support_extruder == interface_extruder; @@ -5585,155 +5646,208 @@ LayerResult GCode::process_layer(const Print& print, local_z_clipped_collections.emplace_back(std::move(clipped_base)); } - const unsigned int configured_filament_id = configured_filament_id_1based(entity_type, *filtered_extrusions, region); - const std::vector* pointillism_sequence = - is_anything_overridden ? nullptr : pointillism_sequence_for_filament(configured_filament_id); - if (pointillism_sequence != nullptr) { - std::vector> split_by_extruder; - PointillismPathSplitStats split_stats; - const size_t sequence_phase = pointillism_sequence->empty() ? - 0 : size_t(std::max(0, layer_tools.layer_index)) % pointillism_sequence->size(); - if (split_extrusion_collection_for_pointillism_paths(*filtered_extrusions, - *pointillism_sequence, - layer_tools.num_physical, - pointillism_segment_len_scaled, - pointillism_line_gap_scaled, - sequence_phase, - split_by_extruder, - split_stats) && - split_stats.bucket_count >= 2) { - ++pointillism_path_split_entities; - pointillism_path_split_segments += split_stats.segment_count; - for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { - std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; - if (!split_collection || split_collection->entities.empty()) - continue; - const ExtrusionEntityCollection* split_ptr = split_collection.get(); - local_z_clipped_collections.emplace_back(std::move(split_collection)); - std::vector& islands = - object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); - for (size_t i = 0; i <= n_slices; ++i) { - const bool last = i == n_slices; - const size_t island_idx = last ? n_slices : slices_test_order[i]; - if (last || entity_matches_surface(island_idx, *split_ptr)) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); - break; + auto process_extrusions = [&](const ExtrusionEntityCollection* current_extrusions, + const ExtrusionEntityCollection* overrides_key, + bool use_overrides) { + const unsigned int configured_filament_id = configured_filament_id_1based(entity_type, *current_extrusions, region); + const std::vector* pointillism_sequence = + is_anything_overridden ? nullptr : pointillism_sequence_for_filament(configured_filament_id); + if (pointillism_sequence != nullptr) { + std::vector> split_by_extruder; + PointillismPathSplitStats split_stats; + const size_t sequence_phase = pointillism_sequence->empty() ? + 0 : size_t(std::max(0, layer_tools.layer_index)) % pointillism_sequence->size(); + if (split_extrusion_collection_for_pointillism_paths(*current_extrusions, + *pointillism_sequence, + layer_tools.num_physical, + pointillism_segment_len_scaled, + pointillism_line_gap_scaled, + sequence_phase, + split_by_extruder, + split_stats) && + split_stats.bucket_count >= 2) { + ++pointillism_path_split_entities; + pointillism_path_split_segments += split_stats.segment_count; + for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { + std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; + if (!split_collection || split_collection->entities.empty()) + continue; + const ExtrusionEntityCollection* split_ptr = split_collection.get(); + local_z_clipped_collections.emplace_back(std::move(split_collection)); + std::vector& islands = + object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + const bool last = i == n_slices; + const size_t island_idx = last ? n_slices : slices_test_order[i]; + if (last || entity_matches_surface(island_idx, *split_ptr)) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); + break; + } } } + return; } - continue; + ++pointillism_path_split_fallbacks; } - ++pointillism_path_split_fallbacks; - } - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = configured_extruder_id(entity_type, *filtered_extrusions, region); - if (!is_anything_overridden && - entity_type == ObjectByExtruder::Island::Region::PERIMETERS && - layer_tools.mixed_mgr != nullptr && - layer_tools.num_physical > 0 && - correct_extruder_id >= 0) { - const unsigned int mixed_filament_id = - grouped_manual_pattern_mixed_filament_id(entity_type, *filtered_extrusions, region); - if (mixed_filament_id != 0) { - std::vector> split_by_extruder; - size_t bucket_count = 0; - const PrintObject* current_object_for_gradient = - layer_to_print.original_object != nullptr ? layer_to_print.original_object : layer_to_print.object(); - if (split_extrusion_collection_for_multi_perimeter_pattern(*filtered_extrusions, - *layer_tools.mixed_mgr, - mixed_filament_id, - layer_tools.num_physical, - layer_tools.layer_index, - split_by_extruder, - bucket_count, - current_object_for_gradient)) { - if (bucket_count >= 2) { - for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { - std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; - if (!split_collection || split_collection->entities.empty()) - continue; - const ExtrusionEntityCollection* split_ptr = split_collection.get(); - local_z_clipped_collections.emplace_back(std::move(split_collection)); - std::vector& islands = - object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); - for (size_t i = 0; i <= n_slices; ++i) { - const bool last = i == n_slices; - const size_t island_idx = last ? n_slices : slices_test_order[i]; - if (last || entity_matches_surface(island_idx, *split_ptr)) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); - break; + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + int correct_extruder_id = configured_extruder_id(entity_type, *current_extrusions, region); + if (!is_anything_overridden && + entity_type == ObjectByExtruder::Island::Region::PERIMETERS && + layer_tools.mixed_mgr != nullptr && + layer_tools.num_physical > 0 && + correct_extruder_id >= 0) { + const unsigned int mixed_filament_id = + grouped_manual_pattern_mixed_filament_id(entity_type, *current_extrusions, region); + if (mixed_filament_id != 0) { + std::vector> split_by_extruder; + size_t bucket_count = 0; + const PrintObject* current_object_for_gradient = + layer_to_print.original_object != nullptr ? layer_to_print.original_object : layer_to_print.object(); + if (split_extrusion_collection_for_multi_perimeter_pattern(*current_extrusions, + *layer_tools.mixed_mgr, + mixed_filament_id, + layer_tools.num_physical, + layer_tools.layer_index, + split_by_extruder, + bucket_count, + current_object_for_gradient)) { + if (bucket_count >= 2) { + for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { + std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; + if (!split_collection || split_collection->entities.empty()) + continue; + const ExtrusionEntityCollection* split_ptr = split_collection.get(); + local_z_clipped_collections.emplace_back(std::move(split_collection)); + std::vector& islands = + object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + const bool last = i == n_slices; + const size_t island_idx = last ? n_slices : slices_test_order[i]; + if (last || entity_matches_surface(island_idx, *split_ptr)) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); + break; + } } } + return; } - continue; - } - if (bucket_count == 1) { - for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { - const std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; - if (split_collection && !split_collection->entities.empty()) { - // by_extruder keys and LayerTools runtime extruder IDs are zero-based here. - correct_extruder_id = int(extruder_idx); - break; + if (bucket_count == 1) { + for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { + const std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; + if (split_collection && !split_collection->entities.empty()) { + // by_extruder keys and LayerTools runtime extruder IDs are zero-based here. + correct_extruder_id = int(extruder_idx); + break; + } } } } } } - } - // Let's recover vector of extruder overrides: - const WipingExtrusions::ExtruderPerCopy* entity_overrides = nullptr; - if (!layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare - // extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - printing_extruders.clear(); - if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools) - .wiping_extrusions() - .get_extruder_overrides(filtered_extrusions, layer_to_print.original_object, correct_extruder_id, - layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { + // Let's recover vector of extruder overrides: + const WipingExtrusions::ExtruderPerCopy* entity_overrides = nullptr; + if (!layer_tools.has_extruder(correct_extruder_id)) { + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare + // extruders are eradicated from layer_tools) + correct_extruder_id = layer_tools.extruders.back(); + } + printing_extruders.clear(); + if (is_anything_overridden && use_overrides) { + entity_overrides = const_cast(layer_tools) + .wiping_extrusions() + .get_extruder_overrides(overrides_key, layer_to_print.original_object, correct_extruder_id, + layer_to_print.object()->instances().size()); + if (entity_overrides == nullptr) { + printing_extruders.emplace_back(correct_extruder_id); + } else { + printing_extruders.reserve(entity_overrides->size()); + for (int extruder : *entity_overrides) + printing_extruders.emplace_back(extruder >= 0 ? + // at least one copy is overridden to use this extruder + extruder : + // at least one copy would normally be printed with this extruder + // (see get_extruder_overrides function for explanation) + static_cast(-extruder - 1)); + Slic3r::sort_remove_duplicates(printing_extruders); + } + } else printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder - // (see get_extruder_overrides function for explanation) - static_cast(-extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); + + // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: + for (unsigned int extruder : printing_extruders) { + std::vector& islands = + object_islands_by_extruder(by_extruder, extruder, layer_to_print_idx, layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; + if ( // extrusions->first_point does not fit inside any slice + last || + // extrusions->first_point fits inside ith slice + entity_matches_surface(island_idx, *current_extrusions)) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, current_extrusions, + entity_overrides); + break; + } + } } - } else - printing_extruders.emplace_back(correct_extruder_id); - - // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: - for (unsigned int extruder : printing_extruders) { - std::vector& islands = - object_islands_by_extruder(by_extruder, extruder, layer_to_print_idx, layers.size(), n_slices + 1); - for (size_t i = 0; i <= n_slices; ++i) { - bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; - if ( // extrusions->first_point does not fit inside any slice - last || - // extrusions->first_point fits inside ith slice - entity_matches_surface(island_idx, *filtered_extrusions)) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, filtered_extrusions, - entity_overrides); - break; + }; + + bool split_mixed_perimeters = + entity_type == ObjectByExtruder::Island::Region::PERIMETERS && + region.config().outer_wall_filament_id.value != region.config().inner_wall_filament_id.value && + filtered_extrusions->role() == erMixed; + + if (split_mixed_perimeters) { + auto outer_perimeters = std::make_unique(); + auto inner_perimeters = std::make_unique(); + for (const ExtrusionEntity* entity : filtered_extrusions->entities) { + // ORCA: chaining may put an overhang path first and fully overhanging loops + // have no plain perimeter path: classify by scanning every path; anything without + // an inner perimeter path uses the outer wall filament (matches PerimeterGenerator's + // overhang flows). + bool has_external = false, has_internal = false; + auto classify = [&](const ExtrusionPaths& paths) { + for (const ExtrusionPath& path : paths) { + if (path.role() == erExternalPerimeter) + has_external = true; + else if (path.role() == erPerimeter) + has_internal = true; + } + }; + if (const auto* loop = dynamic_cast(entity)) + classify(loop->paths); + else if (const auto* multi_path = dynamic_cast(entity)) + classify(multi_path->paths); + else { + const ExtrusionRole role = entity->role(); + has_external = role == erExternalPerimeter; + has_internal = role == erPerimeter; } + if (has_external || !has_internal) + outer_perimeters->append(*entity); + else + inner_perimeters->append(*entity); + } + + if (!outer_perimeters->entities.empty()) { + split_perimeter_storage.emplace_back(std::move(outer_perimeters)); + process_extrusions(split_perimeter_storage.back().get(), nullptr, false); } + if (!inner_perimeters->entities.empty()) { + split_perimeter_storage.emplace_back(std::move(inner_perimeters)); + process_extrusions(split_perimeter_storage.back().get(), nullptr, false); + } + } else { + process_extrusions(filtered_extrusions, filtered_extrusions, true); } } } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 811923daaed..46b9f931eaa 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -94,14 +94,18 @@ bool internal_solid_infill_uses_sparse_filament(const PrintRegion ®ion, Extru unsigned int sparse_infill_filament_id_1based(const PrintRegion ®ion) { - return region.config().sparse_infill_filament.value; + return region.config().sparse_infill_filament_id.value; } unsigned int infill_filament_id_1based(const LayerTools &layer_tools, const PrintRegion ®ion, ExtrusionRole role) { if (internal_solid_infill_uses_sparse_filament(region, role)) return sparse_infill_filament_id_1based(region); - return is_solid_infill(role) ? region.config().solid_infill_filament.value : sparse_infill_filament_id_1based(region); + if (role == erTopSolidInfill || role == erIroning) + return region.config().top_surface_filament_id.value; + if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) + return region.config().bottom_surface_filament_id.value; + return is_solid_infill(role) ? region.config().internal_solid_filament_id.value : sparse_infill_filament_id_1based(region); } unsigned int grouped_manual_pattern_mixed_filament_id_for_layer(const LayerTools& layer_tools, @@ -284,25 +288,48 @@ unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id) const } // Return a zero based extruder from the region, or extruder_override if overriden. -unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const +unsigned int LayerTools::wall_extruder_id(const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); - unsigned int id = (this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override; + assert(region.config().outer_wall_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().outer_wall_filament_id.value : this->extruder_override; return resolve_mixed_1based(id) - 1; } -unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const +unsigned int LayerTools::inner_wall_extruder_id(const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); + assert(region.config().inner_wall_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().inner_wall_filament_id.value : this->extruder_override; + return resolve_mixed_1based(id) - 1; +} + +unsigned int LayerTools::sparse_infill_filament_id(const PrintRegion ®ion) const +{ + assert(region.config().sparse_infill_filament_id.value > 0); unsigned int id = (this->extruder_override == 0) ? sparse_infill_filament_id_1based(region) : this->extruder_override; const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; } -unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const +unsigned int LayerTools::internal_solid_filament_id(const PrintRegion ®ion) const +{ + assert(region.config().internal_solid_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().internal_solid_filament_id.value : this->extruder_override; + const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); + return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; +} + +unsigned int LayerTools::top_surface_filament_id(const PrintRegion ®ion) const { - assert(region.config().solid_infill_filament.value > 0); - unsigned int id = (this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override; + assert(region.config().top_surface_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().top_surface_filament_id.value : this->extruder_override; + const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); + return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; +} + +unsigned int LayerTools::bottom_surface_filament_id(const PrintRegion ®ion) const +{ + assert(region.config().bottom_surface_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().bottom_surface_filament_id.value : this->extruder_override; const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; } @@ -310,16 +337,41 @@ unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); - assert(region.config().sparse_infill_filament.value > 0); - assert(region.config().solid_infill_filament.value > 0); + assert(region.config().outer_wall_filament_id.value > 0); + assert(region.config().inner_wall_filament_id.value > 0); + assert(region.config().sparse_infill_filament_id.value > 0); + assert(region.config().internal_solid_filament_id.value > 0); + assert(region.config().top_surface_filament_id.value > 0); + assert(region.config().bottom_surface_filament_id.value > 0); if (extrusions.has_infill()) { - const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); + ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); + // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + if (role == erGapFill) + for (const ExtrusionEntity *ee : extrusions.entities) + if (ee->role() != erGapFill) { + role = ee->role(); + break; + } if (internal_solid_infill_uses_sparse_filament(region, role)) - return sparse_infill_filament(region); - return is_solid_infill(role) ? solid_infill_filament(region) : sparse_infill_filament(region); + return sparse_infill_filament_id(region); + if (extrusions.has_solid_infill()) { + ExtrusionRole solid_role = extrusions.role(); + // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + if (solid_role == erMixed) + for (const ExtrusionEntity *ee : extrusions.entities) + if (ee->role() != erGapFill) { + solid_role = ee->role(); + break; + } + if (solid_role == erTopSolidInfill || solid_role == erIroning) + return top_surface_filament_id(region); + if (solid_role == erBottomSurface || solid_role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) + return bottom_surface_filament_id(region); + return internal_solid_filament_id(region); + } + return sparse_infill_filament_id(region); } - return wall_filament(region); + return extrusions.role() == erPerimeter ? inner_wall_extruder_id(region) : wall_extruder_id(region); } static double calc_max_layer_height(const PrintConfig &config, double max_object_layer_height) @@ -564,8 +616,8 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr for (auto object : print.objects()) { auto first_layer = object->get_layer(0); for (auto layerm : first_layer->regions()) { - int extruder_id = layerm->region().config().option("wall_filament")->getInt(); - + int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt(); + for (auto expoly : layerm->raw_slices) { const double nozzle_diameter = print.config().nozzle_diameter.get_at(0); const coordf_t initial_layer_line_width = print.config().get_abs_value("initial_layer_line_width", nozzle_diameter); @@ -610,7 +662,7 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr std::map min_areas_per_extruder; auto first_layer = object.get_layer(0); for (auto layerm : first_layer->regions()) { - int extruder_id = layerm->region().config().option("wall_filament")->getInt(); + int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt(); for (auto expoly : layerm->raw_slices) { const double nozzle_diameter = object.print()->config().nozzle_diameter.get_at(0); const coordf_t line_width = object.config().get_abs_value("line_width", nozzle_diameter); @@ -693,6 +745,26 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto float(support_layer->print_z), float(support_layer->height), &object); + // support nozzle diameter restriction, resolve a "default" (0) support filament to a nozzle-matching one here, preferring a filament this layer already prints with to save toolchanges. + // Note: this fork collects support before the object's own layers, so the preference scan + // only sees filaments of previously collected objects and otherwise settles on the + // deterministic resolved_default_support_filament(); GCode::process_layer() re-prefers + // layer-scheduled extruders for "don't care" support when the G-code is emitted. + if (object.config().support_nozzle_diameter.value > 0.) { + auto restrict_default_filament = [&object, &layer_tools](unsigned int configured) -> unsigned int { + if (configured != 0) + return configured; + const PrintConfig &print_config = object.print()->config(); + for (unsigned int filament : layer_tools.extruders) // 1 based at this point + if (filament > 0 && object.support_filament_allowed(filament) && + ! print_config.filament_soluble.get_at(filament - 1)) + return filament; + unsigned int resolved = object.resolved_default_support_filament(); + return resolved > 0 ? resolved : configured; + }; + extruder_support = restrict_default_filament(extruder_support); + extruder_interface = restrict_default_filament(extruder_interface); + } if (has_support) layer_tools.extruders.push_back(extruder_support); if (has_interface) @@ -743,52 +815,79 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } if (something_nonoverriddable){ - const unsigned int configured_wall = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override; - unsigned int wall_ext = resolve_mixed(configured_wall, layerCount, float(layer->print_z), float(layer->height), &object); - const unsigned int grouped_id = - grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall); - if (grouped_id != 0) { - const std::vector ordered = - m_mixed_mgr->ordered_perimeter_extruders(grouped_id, - m_num_physical, - layerCount, - float(layer->print_z), - float(layer->height)); - if (!ordered.empty()) { - if (ordered.size() >= 2) - layer_tools.preserve_extruder_order = true; - for (unsigned int extruder_id : ordered) { - layer_tools.extruders.emplace_back(extruder_id); - if (layerCount == 0 && - std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end()) - firstLayerExtruders.emplace_back(int(extruder_id)); + // Emplace a wall filament, expanding a grouped manual-pattern mixed filament + // into its per-layer ordered physical extruders (both walls need this so the + // wipe tower planner sees every extruder the grouped split will emit). + auto emplace_wall_filament = [&](unsigned int configured_wall, bool first_layer_candidate) { + unsigned int wall_ext = resolve_mixed(configured_wall, layerCount, float(layer->print_z), float(layer->height), &object); + const unsigned int grouped_id = + grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall); + if (grouped_id != 0) { + const std::vector ordered = + m_mixed_mgr->ordered_perimeter_extruders(grouped_id, + m_num_physical, + layerCount, + float(layer->print_z), + float(layer->height)); + if (!ordered.empty()) { + if (ordered.size() >= 2) + layer_tools.preserve_extruder_order = true; + for (unsigned int extruder_id : ordered) { + layer_tools.extruders.emplace_back(extruder_id); + if (first_layer_candidate && layerCount == 0 && + std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end()) + firstLayerExtruders.emplace_back(int(extruder_id)); + } + return; } - } else { - layer_tools.extruders.emplace_back(wall_ext); - if (layerCount == 0) - firstLayerExtruders.emplace_back(wall_ext); } - } else { layer_tools.extruders.emplace_back(wall_ext); - if (layerCount == 0) + if (first_layer_candidate && layerCount == 0) firstLayerExtruders.emplace_back(wall_ext); - } + }; + emplace_wall_filament((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override, true); + // alternate_extra_wall should add an inner loop on odd layers (mirrors PerimeterGenerator's loop_number and the spiral gate of LayerRegion::make_perimeters()); layers dropping the loop again (only_one_wall_top / only_one_wall_first_layer) may reserve the filament unused, which merely costs a toolchange. + const bool spiral_vase_layer = object.print()->config().spiral_mode.value && + layer->id() >= size_t(region.config().bottom_shell_layers.value) && + layer->print_z >= region.config().bottom_shell_thickness - EPSILON; + if (extruder_override == 0 && + (region.config().wall_loops.value > 1 || + (region.config().alternate_extra_wall.value && layer->id() % 2 == 1 && + region.config().sparse_infill_density.value > 0 && !spiral_vase_layer))) + emplace_wall_filament(region.config().inner_wall_filament_id.value, false); } layer_tools.has_object = true; } - bool has_sparse_infill = false; - bool has_solid_infill = false; + bool has_sparse_infill = false; + bool has_internal_solid = false; + bool has_top_solid_surface = false; + bool has_bottom_surface = false; + bool has_pure_gap_fill = false; bool something_nonoverriddable = false; for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); + // gap fill inherits its surface's filament; classify by the first non-gap-fill entity (must match LayerTools::extruder()). + if (role == erGapFill) + for (const ExtrusionEntity *fill_entity : fill->entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } if (internal_solid_infill_uses_sparse_filament(region, role)) has_sparse_infill = true; + else if (role == erTopSolidInfill || role == erIroning) + has_top_solid_surface = true; + else if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces + has_bottom_surface = true; else if (is_solid_infill(role)) - has_solid_infill = true; + has_internal_solid = true; + else if (role == erGapFill) + // perimeter-generated gap fill with no sibling surface dispatches to the outer wall filament. + has_pure_gap_fill = true; else if (role != erNone) has_sparse_infill = true; @@ -800,13 +899,23 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (something_nonoverriddable || !m_print_config_ptr) { if (extruder_override == 0) { - if (has_solid_infill) { - layer_tools.extruders.emplace_back(layer_tools.solid_infill_filament(region) + 1); + if (has_internal_solid) { + layer_tools.extruders.emplace_back(layer_tools.internal_solid_filament_id(region) + 1); + } + if (has_top_solid_surface) { + layer_tools.extruders.emplace_back(layer_tools.top_surface_filament_id(region) + 1); + } + if (has_bottom_surface) { + layer_tools.extruders.emplace_back(layer_tools.bottom_surface_filament_id(region) + 1); } if (has_sparse_infill) { - layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament(region) + 1); + layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament_id(region) + 1); } - } else if (has_solid_infill || has_sparse_infill) { + if (has_pure_gap_fill) { + // perimeter-generated gap fill with no sibling surface uses the outer wall filament. + layer_tools.extruders.emplace_back(layer_tools.wall_extruder_id(region) + 1); + } + } else if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill || has_pure_gap_fill) { layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount, float(layer->print_z), @@ -814,7 +923,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto &object)); } } - if (has_solid_infill || has_sparse_infill) + if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill || has_pure_gap_fill) layer_tools.has_object = true; } layerCount++; @@ -1060,12 +1169,23 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. + // Per-extruder layer height combines layers away, leaving many LayerTools empty; skip them and compare against the last printing layer. + // Without the feature keep the historic behavior of stopping at the first empty layer. + const bool skip_empty_layer_tools = std::any_of(config.extruder_layer_height.values.begin(), + config.extruder_layer_height.values.end(), [](double h) { return h > 0.; }); + const LayerTools *lt_last_printing = nullptr; for (unsigned int i=0; i+1 1)) + if (lt.extruders.empty() || lt_next.extruders.empty()) { + if (! skip_empty_layer_tools) + break; + if (lt_last_printing == nullptr || lt_next.extruders.empty()) + continue; + } + if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt_last_printing->extruders.back() || lt_next.extruders.size() > 1)) lt_next.has_wipe_tower = true; // We should also check that the next wipe tower layer is no further than max_layer_height: unsigned int j = i+1; @@ -1405,12 +1525,37 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print return (-1); } +// diameter of the nozzle a 0-based filament prints through. +// On classic multi-tool printers the filament index is the extruder index. +static double filament_nozzle_diameter(const Print &print, unsigned int filament_id) +{ + return print.config().nozzle_diameter.get_at(filament_id); +} + // Decides whether this entity could be overridden bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const { - if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region))) + const unsigned int intended_filament = m_layer_tools->extruder(eec, region); + if (print_config.filament_soluble.get_at(intended_filament)) return false; + // Entity widths/heights were computed for its filament's nozzle, so only a filament with the same nozzle diameter may take it over; with no candidate it must keep its own filament. + if (print_config.nozzle_diameter.values.size() > 1) { + const Print &print = *object.print(); + const double intended_nozzle = filament_nozzle_diameter(print, intended_filament); + bool has_candidate = false; + for (size_t filament = 0; filament < print_config.filament_soluble.values.size(); ++ filament) + if (filament != intended_filament && + !print_config.filament_soluble.get_at(filament) && + !print_config.filament_is_support.get_at(filament) && + std::abs(filament_nozzle_diameter(print, (unsigned int)filament) - intended_nozzle) < EPSILON) { + has_candidate = true; + break; + } + if (!has_candidate) + return false; + } + if (object.config().flush_into_objects) return true; @@ -1504,10 +1649,15 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (!is_overriddable(*fill, print.config(), *object, region)) continue; + // Only wipe into entities computed for this extruder's nozzle diameter. + if (std::abs(filament_nozzle_diameter(print, lt.extruder(*fill, region)) - + filament_nozzle_diameter(print, new_extruder)) > EPSILON) + continue; + if (wipe_into_infill_only && ! is_infill_first) // In this case we must check that the original extruder is used on this layer before the one we are overridding // (and the perimeters will be finished before the infill is printed): - if (!lt.is_extruder_order(lt.wall_filament(region), new_extruder)) + if (!lt.is_extruder_order(lt.wall_extruder_id(region), new_extruder)) continue; if ((!is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume)) @@ -1525,7 +1675,10 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int { for (const ExtrusionEntity* ee : layerm->perimeters.entities) { auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume) { + if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume && + // nozzle diameter must match + std::abs(filament_nozzle_diameter(print, lt.extruder(*fill, region)) - + filament_nozzle_diameter(print, new_extruder)) < EPSILON) { set_extruder_override(fill, object, copy, new_extruder, num_of_copies); if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f) // More material was purged already than asked for. @@ -1544,8 +1697,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (this_support_layer == nullptr) break; - bool support_overriddable = object_config.support_filament == 0; - bool support_intf_overriddable = object_config.support_interface_filament == 0; + // Only a nozzle matching the object's support nozzle diameter restriction may flush into its support. + bool support_overriddable = object_config.support_filament == 0 && object->support_filament_allowed(new_extruder + 1); + bool support_intf_overriddable = object_config.support_interface_filament == 0 && object->support_filament_allowed(new_extruder + 1); if (!support_overriddable && !support_intf_overriddable) break; @@ -1595,6 +1749,25 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); + // Prefer filaments printing through the nozzle diameter the entity's widths were computed for; returns -1 when the layer offers none. + auto nonsoluble_matching_extruder = [this, &print](bool first, unsigned int intended_filament) -> int { + const PrintConfig &config = print.config(); + const double intended_nozzle = filament_nozzle_diameter(print, intended_filament); + auto matches = [&](unsigned int filament) { + return !config.filament_soluble.get_at(filament) && !config.filament_is_support.get_at(filament) && + std::abs(filament_nozzle_diameter(print, filament) - intended_nozzle) < EPSILON; + }; + const std::vector &extruders = m_layer_tools->extruders; + if (first) { + for (auto it = extruders.begin(); it != extruders.end(); ++ it) + if (matches(*it)) return int(*it); + } else { + for (auto it = extruders.rbegin(); it != extruders.rend(); ++ it) + if (matches(*it)) return int(*it); + } + return -1; + }; + for (const PrintObject* object : print.objects()) { // Finds this layer: const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON); @@ -1625,9 +1798,14 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) if (is_infill_first //BBS //|| object->config().flush_into_objects // in this case the perimeter is overridden, so we can override by the last one safely - || lt.is_extruder_order(lt.wall_filament(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || ! lt.has_extruder(lt.sparse_infill_filament(region)))) // we have to force override - this could violate infill_first (FIXME) - set_extruder_override(fill, object, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); + || lt.is_extruder_order(lt.wall_extruder_id(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints + || ! lt.has_extruder(lt.sparse_infill_filament_id(region)))) { // we have to force override - this could violate infill_first (FIXME) + // Prefer a nozzle-matching filament; fall back so the entity still prints. + int flush_extruder = nonsoluble_matching_extruder(is_infill_first, lt.extruder(*fill, region)); + if (flush_extruder < 0) + flush_extruder = is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder; + set_extruder_override(fill, object, copy, flush_extruder, num_of_copies); + } else { // In this case we can (and should) leave it to be printed normally. // Force overriding would mean it gets printed before its perimeter. @@ -1637,8 +1815,13 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Now the same for perimeters - see comments above for explanation: for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, object, copy)) - set_extruder_override(fill, object, copy, (is_infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); + if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, object, copy)) { + // Same nozzle-matching preference as above. + int flush_extruder = nonsoluble_matching_extruder(!is_infill_first, lt.extruder(*fill, region)); + if (flush_extruder < 0) + flush_extruder = is_infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder; + set_extruder_override(fill, object, copy, flush_extruder, num_of_copies); + } } } } diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 070b4c4d210..996d4cdca58 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -106,9 +106,12 @@ class LayerTools bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); } // Return a zero based extruder from the region, or extruder_override if overriden. - unsigned int wall_filament(const PrintRegion ®ion) const; - unsigned int sparse_infill_filament(const PrintRegion ®ion) const; - unsigned int solid_infill_filament(const PrintRegion ®ion) const; + unsigned int wall_extruder_id(const PrintRegion ®ion) const; + unsigned int inner_wall_extruder_id(const PrintRegion ®ion) const; + unsigned int sparse_infill_filament_id(const PrintRegion ®ion) const; + unsigned int internal_solid_filament_id(const PrintRegion ®ion) const; + unsigned int top_surface_filament_id(const PrintRegion ®ion) const; + unsigned int bottom_surface_filament_id(const PrintRegion ®ion) const; // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const; diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 6a06416fd37..bf222eca77a 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -1422,7 +1422,10 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config) if (max_vol_speed != 0.f) m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); - m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + // ORCA: every tool purges with a width matching its own nozzle; the tower's geometric layout + // (m_perimeter_width) uses the widest configured nozzle, not whichever was configured last. + m_filpar[idx].perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; + m_perimeter_width = idx == 0 ? m_filpar[idx].perimeter_width : std::max(m_perimeter_width, m_filpar[idx].perimeter_width); if (m_semm) { std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; @@ -1494,7 +1497,7 @@ std::vector WipeTower2::prime( for (size_t idx_tool = 0; idx_tool < tools.size(); ++idx_tool) { size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow).set_z(m_z_pos).set_initial_tool(m_current_tool); writer.set_is_prime(true); @@ -1588,7 +1591,7 @@ WipeTower::ToolChangeResult WipeTower2::emit_planned_tool_change(const WipeTower (!is_no_tool_sentinel(tool) ? wipe_area + m_depth_traversed - 0.5f * m_perimeter_width : m_wipe_tower_depth - m_perimeter_width)); - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1701,7 +1704,7 @@ WipeTower::ToolChangeResult WipeTower2::local_z_tool_change(size_t new_tool, { const size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1758,7 +1761,8 @@ WipeTower::ToolChangeResult WipeTower2::local_z_tool_change(size_t new_tool, float WipeTower2::predict_ramming_end_x(int old_tool, float layer_height) const { - float line_width = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // Must match the per-tool ramming line width used by toolchange_Unload(). + float line_width = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; // Same xl/xr as Unload, using actual ramming line_width for the offset float xl = m_perimeter_width / 2.f + 1.f * line_width; @@ -1798,7 +1802,7 @@ void WipeTower2::toolchange_Unload(WipeTowerWriter2& writer, float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; - const float line_width = m_perimeter_width * + const float line_width = tool_perimeter_width(m_current_tool) * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm @@ -1886,7 +1890,7 @@ void WipeTower2::toolchange_Unload(WipeTowerWriter2& writer, } } Vec2f end_of_ramming(writer.x(), writer.y()); - writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier + writer.change_analyzer_line_width(tool_perimeter_width(m_current_tool)); // so the next lines are not affected by ramming_line_width_multiplier // Retraction: if (m_enable_filament_ramming) @@ -2034,6 +2038,8 @@ void WipeTower2::toolchange_Change(WipeTowerWriter2& writer, const size_t new_to writer.flush_planner_queue(); m_current_tool = new_tool; + // The new toolhead may have a different nozzle, so the extrusion flow must follow the tool change. + m_extrusion_flow = extrusion_flow(m_layer_height); } void WipeTower2::toolchange_Load(WipeTowerWriter2& writer, const WipeTower::box_coordinates& cleaning_box) @@ -2071,14 +2077,15 @@ void WipeTower2::toolchange_Wipe(WipeTowerWriter2& writer, const WipeTower::box_ const float& xr = cleaning_box.rd.x(); writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow); - const float line_width = m_perimeter_width * m_extra_flow; + // Purge lines use the tool's own width; row spacing dy keeps m_perimeter_width so consumed depth matches the plan. + const float line_width = tool_perimeter_width(m_current_tool) * m_extra_flow; writer.change_analyzer_line_width(line_width); // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. - float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow; + float x_to_wipe = volume_to_length(wipe_volume, tool_perimeter_width(m_current_tool), m_layer_height) / m_extra_flow; float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow. // All the calculations in all other places take the spacing into account for all the layers. @@ -2192,7 +2199,7 @@ void WipeTower2::toolchange_Wipe(WipeTowerWriter2& writer, const WipeTower::box_ } writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. - writer.change_analyzer_line_width(m_perimeter_width); + writer.change_analyzer_line_width(tool_perimeter_width(m_current_tool)); } WipeTower::ToolChangeResult WipeTower2::finish_layer() @@ -2202,7 +2209,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -2424,12 +2431,13 @@ std::vector> WipeTower2::extract_wipe_volumes(const PrintConf return wipe_volumes; } -static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width) +// purge_line_width is what the purging tool extrudes, row_spacing_width the tower's geometric row pitch - they differ on printers with mixed nozzle diameters. +static float get_wipe_depth(float volume, float layer_height, float purge_line_width, float row_spacing_width, float extra_flow, float extra_spacing, float width) { - float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow; + float length_to_extrude = (volume_to_length(volume, purge_line_width, layer_height)) / extra_flow; length_to_extrude = std::max(length_to_extrude, 0.f); - return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing; + return (int(length_to_extrude / width) + 1) * row_spacing_width * extra_spacing; } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box @@ -2447,7 +2455,8 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i return; // this is an actual toolchange - let's calculate depth to reserve on the wipe tower - float ramming_lw = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // ramming uses the OLD tool's own line width and the wipe the NEW tool's; the planned depths must match the widths toolchange_Unload() / toolchange_Wipe() will actually use. + float ramming_lw = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; float width = m_wipe_tower_width - m_perimeter_width - 2 * ramming_lw; // Orca: For non-SEMM multi-toolhead, ramming_speed contains only flow (not speed), so 0.25f * flow is meaningless. // Use the actual multitool_ramming_volume instead. @@ -2461,9 +2470,9 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i m_extra_spacing_ramming; float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); - float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par); - float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, width); + float first_wipe_volume = length_to_volume(first_wipe_line, tool_perimeter_width(new_tool) * m_extra_flow, layer_height_par); + float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, tool_perimeter_width(new_tool), + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); m_plan.back().tool_changes.push_back( WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); @@ -2482,7 +2491,8 @@ void WipeTower2::plan_local_z_toolchange(float z_par, float layer_height_par, un if (old_tool == new_tool) return; - float ramming_lw = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // ramming uses the OLD tool's own line width and the wipe the NEW tool's (see plan_toolchange()). + float ramming_lw = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; float width = m_wipe_tower_width - m_perimeter_width - 2 * ramming_lw; float ramming_volume = m_semm ? 0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f) @@ -2494,9 +2504,9 @@ void WipeTower2::plan_local_z_toolchange(float z_par, float layer_height_par, un m_extra_spacing_ramming; float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); - float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par); - float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, width); + float first_wipe_volume = length_to_volume(first_wipe_line, tool_perimeter_width(new_tool) * m_extra_flow, layer_height_par); + float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, tool_perimeter_width(new_tool), + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); m_plan.back().local_z_tool_changes.push_back( WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); @@ -2514,9 +2524,15 @@ void WipeTower2::plan_local_z_reserve(float z_par, float layer_height_par, size_ const float mini_wipe_depth = m_local_z_wipe_tower_purge_lines * m_perimeter_width * m_extra_spacing_wipe; const float wipe_width = std::max(0.f, m_wipe_tower_width - 3.f * m_perimeter_width); + // The reserving slot does not know which tool will purge into it; plan the wipe length with the + // narrowest tool line width (worst case) while keeping the geometric row pitch (m_perimeter_width). + float min_tool_perimeter_width = m_perimeter_width; + for (const FilamentParameters& filament : m_filpar) + if (filament.perimeter_width > 0.f) + min_tool_perimeter_width = std::min(min_tool_perimeter_width, filament.perimeter_width); const float wiping_depth = wipe_width > WT_EPSILON ? - get_wipe_depth(std::max(0.f, wipe_volume), layer_height_par, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, wipe_width) : + get_wipe_depth(std::max(0.f, wipe_volume), layer_height_par, min_tool_perimeter_width, + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, wipe_width) : 0.f; float max_ramming_depth = 0.f; @@ -2526,9 +2542,11 @@ void WipeTower2::plan_local_z_reserve(float z_par, float layer_height_par, size_ if (!do_ramming || filament.ramming_speed.empty()) continue; - const float line_width = m_perimeter_width * filament.ramming_line_width_multiplicator; + // Each tool rams with its own line width (see toolchange_Unload()). + const float tool_width = filament.perimeter_width > 0.f ? filament.perimeter_width : m_perimeter_width; + const float line_width = tool_width * filament.ramming_line_width_multiplicator; const float line_step = - (m_perimeter_width * filament.ramming_line_width_multiplicator * filament.ramming_step_multiplicator) * + (tool_width * filament.ramming_line_width_multiplicator * filament.ramming_step_multiplicator) * m_extra_spacing_ramming; if (line_width <= WT_EPSILON || line_step <= WT_EPSILON) continue; @@ -2597,15 +2615,17 @@ void WipeTower2::save_on_last_wipe() if (i == idx) { float width = m_wipe_tower_width - 3 * m_perimeter_width; // width we draw into - float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, + // ORCA: the finish layer and the wipe are extruded by the new tool with its own line width. + float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), + tool_perimeter_width(toolchange.new_tool), m_layer_info->height); float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save); float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, - m_perimeter_width * m_extra_flow, + tool_perimeter_width(toolchange.new_tool) * m_extra_flow, m_layer_info->height)); - float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, width); + float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, tool_perimeter_width(toolchange.new_tool), + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; toolchange.wipe_volume = volume_left_to_wipe; @@ -2930,7 +2950,8 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(int old_tool, i float ramming_volume = m_semm ? 0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f) : m_filpar[old_tool].multitool_ramming_volume; - float line_width = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // ramming uses the OLD tool's own line width and the wipe the NEW tool's (see plan_toolchange()). + float line_width = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; float width = m_wipe_tower_width - m_perimeter_width - 2 * line_width; if (std::fabs(width) < EPSILON) { assert(false); @@ -2943,9 +2964,9 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(int old_tool, i float ramming_depth = num_lines * line_width * m_filpar[old_tool].ramming_step_multiplicator * m_extra_spacing_ramming; float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); - float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height); + float first_wipe_volume = length_to_volume(first_wipe_line, tool_perimeter_width(new_tool) * m_extra_flow, layer_height); float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, - m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); + tool_perimeter_width(new_tool), m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume); } diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp index 459beddb057..aa23c553cd8 100644 --- a/src/libslic3r/GCode/WipeTower2.hpp +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -159,6 +159,8 @@ class WipeTower2 float max_e_speed = std::numeric_limits::max(); std::vector ramming_speed; float nozzle_diameter; + // ORCA: tower line width this tool extrudes (nozzle_diameter * Width_To_Nozzle_Ratio). + float perimeter_width = 0.f; float filament_area; bool multitool_ramming; float multitool_ramming_time = 0.f; @@ -185,6 +187,10 @@ class WipeTower2 float filament_area() const { return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point } + // Line width the given tool extrudes on the tower; m_perimeter_width (widest nozzle) keeps the geometric layout: line spacing, box sizes, depth planning. + float tool_perimeter_width(size_t tool) const { + return tool < m_filpar.size() && m_filpar[tool].perimeter_width > 0.f ? m_filpar[tool].perimeter_width : m_perimeter_width; + } bool m_change_pressure = true; float m_change_pressure_value = 0.0; @@ -272,12 +278,13 @@ class WipeTower2 bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } - // Calculates extrusion flow needed to produce required line width for given layer height + // Calculates extrusion flow needed to produce required line width for given layer height. + // ORCA: uses the current tool's own line width, matching its nozzle. float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow { if ( layer_height < 0 ) return m_extrusion_flow; - return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); + return layer_height * ( tool_perimeter_width(m_current_tool) - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); } diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index e428a1673bd..33665a525db 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -74,6 +74,21 @@ LayerRegion* Layer::add_region(const PrintRegion *print_region) return m_regions.back(); } +// ORCA: per-extruder layer height, see PrintObject::apply_extruder_layer_heights(). +double LayerRegion::combined_height() const +{ + return m_combined_height > 0. ? m_combined_height : m_layer->height; +} + +const Layer* LayerRegion::combined_lower_layer() const +{ + const Layer *layer = m_layer; + // count == 0 (layer combined away into a group top) is treated as 1; it produces no extrusions anyway. + for (unsigned short i = 0, count = std::max(m_combined_layer_count, 1); layer != nullptr && i < count; ++ i) + layer = layer->lower_layer; + return layer; +} + // merge all regions' slices to get islands void Layer::make_slices() { @@ -181,7 +196,8 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b) const PrintRegionConfig& config = a.config(); const PrintRegionConfig& other_config = b.config(); - return config.wall_filament == other_config.wall_filament + return config.outer_wall_filament_id == other_config.outer_wall_filament_id + && config.inner_wall_filament_id == other_config.inner_wall_filament_id && config.wall_loops == other_config.wall_loops && config.wall_sequence == other_config.wall_sequence && config.is_infill_first == other_config.is_infill_first @@ -242,7 +258,9 @@ void Layer::make_perimeters() if (! (*it)->slices.empty()) { LayerRegion* other_layerm = *it; const PrintRegion &other_region = other_layerm->region(); - if (is_perimeter_compatible(this_region, other_region)) + // Regions combined to different extruder layer heights extrude with different heights and must not share a make_perimeters() call. + if ((*layerm)->combined_layer_count() == other_layerm->combined_layer_count() && + is_perimeter_compatible(this_region, other_region)) { other_layerm->perimeters.clear(); other_layerm->fills.clear(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index b62144a2fea..cc3021f7dd5 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -16,8 +16,15 @@ using LayerPtrs = std::vector; class LayerRegion; using LayerRegionPtrs = std::vector; class PrintRegion; +class PrintRegionConfig; class PrintObject; +// Snapmaker mixed filament: resolve a configured 1-based filament id to the physical filament +// actually printing on this layer (0 "Default" passes through unchanged). The infill variant +// additionally resolves grouped manual patterns like the innermost perimeter. +unsigned int effective_layer_filament_id(const Layer &layer, unsigned int filament_id); +unsigned int effective_infill_filament_id(const Layer &layer, const PrintRegionConfig &config, unsigned int filament_id); + namespace FillAdaptive { struct Octree; }; @@ -74,8 +81,11 @@ class LayerRegion unsigned int extruder(FlowRole role) const; Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; - Flow flow(FlowRole role, double layer_height, bool use_initial_layer_width) const; - Flow bridging_flow(FlowRole role, bool thick_bridge = false) const; + // filament_id: 1-based filament actually printing this flow when it differs from the role's default mapping (e.g. top/bottom surface fills, external bridges), 0 to resolve from the role. + Flow flow(FlowRole role, double layer_height, unsigned int filament_id) const; + Flow flow(FlowRole role, double layer_height, bool use_initial_layer_width, unsigned int filament_id = 0) const; + // layer_height overrides m_layer->height for the non-thick flow (combined layer groups print thicker), 0 to use m_layer->height. + Flow bridging_flow(FlowRole role, bool thick_bridge = false, unsigned int filament_id = 0, double layer_height = 0.) const; void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); @@ -100,6 +110,17 @@ class LayerRegion //BBS void simplify_infill_extrusion_entity() { simplify_entity_collection(&fills); } void simplify_wall_extrusion_entity() { simplify_entity_collection(&perimeters); } + + // ORCA: per-extruder layer height. Number of object layers this region's extrusions cover here: + // > 1 on the top layer of a group combined by PrintObject::apply_extruder_layer_heights(), 0 on + // the combined-away layers below such a top (their slices are empty and print nothing; the 0 + // tells them apart from layers where the region's geometry is genuinely absent), 1 otherwise. + unsigned short combined_layer_count() const { return m_combined_layer_count; } + // Extrusion height at this layer (sum of the covered layer heights when combined, else layer height). + double combined_height() const; + // Layer right below the covered group; replaces Layer::lower_layer for overhang / bridge + // detection of combined regions. May be nullptr. + const Layer* combined_lower_layer() const; private: void simplify_entity_collection(ExtrusionEntityCollection* entity_collection); void simplify_path(ExtrusionPath* path); @@ -116,6 +137,9 @@ class LayerRegion private: Layer *m_layer; const PrintRegion *m_region; + // ORCA: set by PrintObject::apply_extruder_layer_heights(), see combined_layer_count() / combined_height(). + unsigned short m_combined_layer_count { 1 }; + double m_combined_height { 0. }; }; class Layer diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 0cb8b681871..4aa45311918 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -20,8 +20,6 @@ namespace Slic3r { -namespace { - unsigned int effective_layer_filament_id(const Layer &layer, unsigned int filament_id) { if (filament_id == 0) @@ -81,16 +79,14 @@ unsigned int effective_infill_filament_id(const Layer &layer, const PrintRegionC object); } -} // namespace - unsigned int LayerRegion::extruder(FlowRole role) const { const PrintRegionConfig &config = this->region().config(); unsigned int filament_id = 0; if (role == frInfill) - filament_id = config.sparse_infill_filament.value; + filament_id = config.sparse_infill_filament_id.value; else if (role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON) - filament_id = config.sparse_infill_filament.value; + filament_id = config.sparse_infill_filament_id.value; else filament_id = this->region().extruder(role); @@ -109,7 +105,13 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const return this->flow(role, layer_height, m_layer->id() == 0); } -Flow LayerRegion::flow(FlowRole role, double layer_height, bool use_initial_layer_width) const +// filament_id: 1-based filament actually printing this flow when it differs from the role's default mapping (e.g. top/bottom surface fills, external bridges), 0 to resolve from the role. +Flow LayerRegion::flow(FlowRole role, double layer_height, unsigned int filament_id) const +{ + return this->flow(role, layer_height, m_layer->id() == 0, filament_id); +} + +Flow LayerRegion::flow(FlowRole role, double layer_height, bool use_initial_layer_width, unsigned int filament_id) const { const PrintConfig &print_config = m_layer->object()->print()->config(); ConfigOptionFloatOrPercent config_width; @@ -134,17 +136,19 @@ Flow LayerRegion::flow(FlowRole role, double layer_height, bool use_initial_laye if (config_width.value == 0) config_width = m_layer->object()->config().line_width; - const auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1)); + // Width resolves against the nozzle of the filament that actually prints (filament_id, when given), + // which may differ from the role's default filament mapping. + const auto nozzle_diameter = float(print_config.nozzle_diameter.get_at((filament_id > 0 ? filament_id : this->extruder(role)) - 1)); return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } -Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const +Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge, unsigned int filament_id, double layer_height) const { const PrintRegion ®ion = this->region(); const PrintRegionConfig ®ion_config = region.config(); const PrintObject &print_object = *this->layer()->object(); Flow bridge_flow; - auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(this->extruder(role) - 1)); + auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at((filament_id > 0 ? filament_id : this->extruder(role)) - 1)); if (thick_bridge) { // The old Slic3r way (different from all other slicers): Use rounded extrusions. // Get the configured nozzle_diameter for the extruder associated to the flow role requested. @@ -153,7 +157,8 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const bridge_flow = Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter); } else { // The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing. - bridge_flow = this->flow(role).with_flow_ratio(region_config.bridge_flow); + // Combined layer groups stamp their full thickness on the surface; the base flow must be resolved at that height. + bridge_flow = this->flow(role, layer_height > 0. ? layer_height : m_layer->height, filament_id).with_flow_ratio(region_config.bridge_flow); } return bridge_flow; @@ -188,27 +193,33 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe 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)))); + perimeter_config.outer_wall_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.outer_wall_filament_id.value)))); + perimeter_config.inner_wall_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.inner_wall_filament_id.value)))); + perimeter_config.sparse_infill_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.sparse_infill_filament_id.value)))); + perimeter_config.internal_solid_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.internal_solid_filament_id.value)))); + perimeter_config.top_surface_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.top_surface_filament_id.value)))); + perimeter_config.bottom_surface_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.bottom_surface_filament_id.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. (this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) && this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON); + // ORCA: on the top layer of a combined group all perimeters extrude with the whole group's height. + const double perimeter_height = this->combined_height(); + PerimeterGenerator g( // input: &slices, &compatible_regions, - this->layer()->height, + perimeter_height, this->layer()->slice_z, - this->flow(frPerimeter), + this->flow(frPerimeter, perimeter_height), &perimeter_config, &this->layer()->object()->config(), &print_config, spiral_mode, - + // output: &this->perimeters, &this->thin_fills, @@ -216,10 +227,11 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe //BBS fill_no_overlap ); - - if (this->layer()->lower_layer != nullptr) + + // Detect overhangs / bridges against the layer below the whole combined group (combined_lower_layer() == lower_layer for regular regions). + if (const Layer *lower_layer = this->combined_lower_layer(); lower_layer != nullptr) // Cummulative sum of polygons over all the regions. - g.lower_slices = &this->layer()->lower_layer->lslices; + g.lower_slices = &lower_layer->lslices; if (this->layer()->upper_layer != NULL) g.upper_slices = &this->layer()->upper_layer->lslices; @@ -228,9 +240,15 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe g.upper_slices_same_region = &this->layer()->upper_layer->get_region(region_id)->slices; g.layer_id = (int)this->layer()->id(); - g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges); - g.solid_infill_flow = this->flow(frSolidInfill); + g.ext_perimeter_flow = this->flow(frExternalPerimeter, perimeter_height); + g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, 0, perimeter_height); + // Overhangs of external / fully overhanging loops dispatch to the outer wall filament (GCode::process_layer() splits mixed perimeters); resolve their width against its nozzle. + // The filament id comes from the resolved perimeter_config (mixed-filament aware), not the raw region config. + g.ext_overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, + (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value), perimeter_height); + g.solid_infill_flow = this->flow(frSolidInfill, perimeter_height); + // Gap fill dispatches to the outer wall filament (LayerTools::extruder()); resolve its width against its nozzle. + g.gap_fill_flow = this->flow(frSolidInfill, perimeter_height, (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value)); if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) g.process_arachne(); diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 055ad377966..dee98e67e1f 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1346,7 +1346,7 @@ static inline std::vector> segmentation_top_and_bottom_l if (const PrintRegionConfig &config = region->region().config(); // color_idx == 0 means "don't know" extruder aka the underlying extruder. // As this region may split existing regions, we collect statistics over all regions for color_idx == 0. - color_idx == 0 || config.wall_filament == int(color_idx)) { + color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) { //BBS: the extrusion line width is outer wall rather than inner wall const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0); double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 4e0ddd0f3fd..3bf94f0b2ac 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -197,10 +197,14 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter if (remain_polines.size() != 0) { - extrusion_paths_append(paths, std::move(remain_polines), - erOverhangPerimeter, perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width(), - perimeter_generator.overhang_flow.height()); + // External / fully overhanging loops (no supported path at all) dispatch to the outer wall filament (GCode::process_layer()); their overhangs use that filament's flow. + const bool overhang_external = is_external || paths.empty(); + const Flow &loop_overhang_flow = overhang_external ? perimeter_generator.ext_overhang_flow : + perimeter_generator.overhang_flow; + extrusion_paths_append(paths, std::move(remain_polines), erOverhangPerimeter, + overhang_external ? perimeter_generator.ext_mm3_per_mm_overhang() : + perimeter_generator.mm3_per_mm_overhang(), + loop_overhang_flow.width(), loop_overhang_flow.height()); } // Reapply the nearest point search for starting point. @@ -444,8 +448,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter + // External / fully overhanging extrusions (no supported path at all) dispatch to the outer + // wall filament (GCode::process_layer()); their overhangs use that filament's flow. Known + // corner: a fully overhanging fragment of the ExtrusionMultiPath split below keeps the inner + // flow, but widths come from the Arachne junctions either way; only thick-bridge height differs. + const bool overhang_external = is_external || paths.empty(); extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference), erOverhangPerimeter, - perimeter_generator.overhang_flow); + overhang_external ? perimeter_generator.ext_overhang_flow : perimeter_generator.overhang_flow); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -605,7 +614,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP // get the real top surface ExPolygons grown_lower_slices; ExPolygons bridge_checker; - auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1); // Check whether surface be bridge or not if (this->lower_slices != NULL) { // BBS: get the Polygons below the polygon this layer @@ -1068,8 +1077,9 @@ void PerimeterGenerator::apply_extra_perimeters(ExPolygons &infill_area) if (!m_spiral_vase && this->lower_slices != nullptr && this->config->detect_overhang_wall && this->config->extra_perimeters_on_overhangs && this->config->wall_loops > 0 && this->layer_id > this->object_config->raft_layers) { // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material + // Pure erOverhangPerimeter entities dispatch to the outer wall filament, so use its flow. auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_area, this->lower_slices_polygons(), - this->config->wall_loops, this->overhang_flow, + this->config->wall_loops, this->ext_overhang_flow, this->m_scaled_resolution, *this->object_config, *this->print_config); if (!extra_perimeters.empty()) { @@ -1143,7 +1153,8 @@ void PerimeterGenerator::process_classic() // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + m_ext_mm3_per_mm_overhang = this->ext_overhang_flow.mm3_per_mm(); + // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); @@ -1152,7 +1163,7 @@ void PerimeterGenerator::process_classic() // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1); m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2))); } @@ -1589,7 +1600,8 @@ void PerimeterGenerator::process_classic() if (! polylines.empty()) { ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); + // Gap fill prints with the outer wall filament (LayerTools::extruder()). + variable_width(polylines, erGapFill, this->gap_fill_flow, gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). Growing actual extrusions ensures that gaps not filled by medial axis @@ -2092,6 +2104,7 @@ void PerimeterGenerator::process_arachne() coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + m_ext_mm3_per_mm_overhang = this->ext_overhang_flow.mm3_per_mm(); // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); @@ -2101,7 +2114,7 @@ void PerimeterGenerator::process_arachne() // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1); m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2))); } @@ -2537,7 +2550,7 @@ bool PerimeterGeneratorLoop::is_internal_contour() const std::vector PerimeterGenerator::generate_lower_polygons_series(float width) { - float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1); + float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1); float start_offset = -0.5 * width; float end_offset = 0.5 * nozzle_diameter; diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 74be04294eb..93346c33f3b 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -73,7 +73,11 @@ class PerimeterGenerator { Flow perimeter_flow; Flow ext_perimeter_flow; Flow overhang_flow; + // Overhangs of external / fully overhanging loops print with the outer wall filament (GCode::process_layer() splits mixed perimeters by filament), whose nozzle may differ. + Flow ext_overhang_flow; Flow solid_infill_flow; + // Gap fill prints with the outer wall filament (LayerTools::extruder()), whose nozzle may differ from the internal solid infill filament's. + Flow gap_fill_flow; const PrintRegionConfig *config; const PrintObjectConfig *object_config; const PrintConfig *print_config; @@ -116,12 +120,12 @@ class PerimeterGenerator { ExPolygons* fill_no_overlap) : slices(slices), compatible_regions(compatible_regions), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height), slice_z(slice_z), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), - overhang_flow(flow), solid_infill_flow(flow), + overhang_flow(flow), ext_overhang_flow(flow), solid_infill_flow(flow), gap_fill_flow(flow), config(config), object_config(object_config), print_config(print_config), m_spiral_vase(spiral_mode), m_scaled_resolution(scaled(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap), - m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1) + m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1) {} void process_classic(); @@ -132,6 +136,7 @@ class PerimeterGenerator { double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; } double mm3_per_mm() const { return m_mm3_per_mm; } double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; } + double ext_mm3_per_mm_overhang() const { return m_ext_mm3_per_mm_overhang; } //BBS double smaller_width_ext_mm3_per_mm() const { return m_ext_mm3_per_mm_smaller_width; } Polygons lower_slices_polygons() const { return m_lower_slices_polygons; } @@ -148,6 +153,7 @@ class PerimeterGenerator { double m_ext_mm3_per_mm; double m_mm3_per_mm; double m_mm3_per_mm_overhang; + double m_ext_mm3_per_mm_overhang; //BBS double m_ext_mm3_per_mm_smaller_width; Polygons m_lower_slices_polygons; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 8882ce9a3af..a56fd6a435f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -892,8 +892,8 @@ static std::vector s_Preset_print_options { "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", - "filename_format", "wall_filament", "support_bottom_z_distance", - "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", + "filename_format", "outer_wall_filament_id", "inner_wall_filament_id", "support_bottom_z_distance", + "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_nozzle_diameter", "support_interface_filament","support_interface_not_for_body", "ooze_prevention", "standby_temperature_delta", "preheat_time","delta_temperature","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "skin_infill_line_width","skeleton_infill_line_width", @@ -906,6 +906,8 @@ static std::vector s_Preset_print_options { "tree_support_branch_diameter", "tree_support_branch_diameter_angle", "detect_narrow_internal_solid_infill", "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/ + // ORCA: per-extruder layer height ("extruder_layer_height"). + "extruder_layer_height_mode", "extruder_layer_height_tolerance", "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "initial_layer_infill_speed", "only_one_wall_top", "timelapse_type", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f97f468fe47..0d50076e0e9 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2641,13 +2641,16 @@ DynamicPrintConfig PresetBundle::full_fff_config() const opt->value = boost::algorithm::clamp(opt->value, 0, int(num_filaments)); } - static const char* keys_1based[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament"}; - for (size_t i = 0; i < sizeof(keys_1based) / sizeof(keys_1based[0]); ++ i) { - std::string key = std::string(keys_1based[i]); + static const char* keys_with_default[] = { + "outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id" + }; + for (size_t i = 0; i < sizeof(keys_with_default) / sizeof(keys_with_default[0]); ++ i) { + std::string key = std::string(keys_with_default[i]); auto *opt = dynamic_cast(out.option(key, false)); assert(opt != nullptr); - if(opt->value < 1 || opt->value > int(num_filaments)) - opt->value = 1; + if(opt->value < 0 || opt->value > int(num_filaments)) + opt->value = 0; } out.option("print_settings_id", true)->value = this->prints.get_selected_preset_name(); out.option("filament_settings_id", true)->values = this->filament_presets; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 703e8ed32db..aef944ca1f1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -697,6 +697,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_shrinkage_compensation_z" || opt_key == "resolution" || opt_key == "precise_z_height" + // ORCA: layer combining for thicker extruder layer heights happens at the slicing step. + || opt_key == "extruder_layer_height" || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_whole_objects" @@ -807,6 +809,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "enable_arc_fitting" || opt_key == "print_order" || opt_key == "wall_sequence") { + // The min / max layer height limits gate layer combining at the slicing step (see region_layer_height_multiplier()), so re-slice while per-extruder layer heights are active. + if ((opt_key == "min_layer_height" || opt_key == "max_layer_height") && + std::any_of(m_config.extruder_layer_height.values.begin(), m_config.extruder_layer_height.values.end(), + [](double h) { return h > 0.; })) + osteps.emplace_back(posSlice); osteps.emplace_back(posPerimeters); osteps.emplace_back(posEstimateCurledExtrusions); osteps.emplace_back(posInfill); @@ -911,17 +918,27 @@ std::vector Print::support_material_extruders() const for (PrintObject *object : m_objects) { if (object->has_support_material()) { + // Under a support nozzle diameter restriction a "default" (0) support filament resolves to a matching-nozzle filament: one of the object's own or the deterministic fallback. + const unsigned int resolved_default = object->resolved_default_support_filament(); assert(object->config().support_filament >= 0); - if (object->config().support_filament == 0) - support_uses_current_extruder = true; - else { + if (object->config().support_filament == 0) { + if (resolved_default > 0) { + unsigned int i = resolved_default - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } else + support_uses_current_extruder = true; + } else { unsigned int i = (unsigned int)object->config().support_filament - 1; extruders.emplace_back((i >= num_extruders) ? 0 : i); } assert(object->config().support_interface_filament >= 0); - if (object->config().support_interface_filament == 0) - support_uses_current_extruder = true; - else { + if (object->config().support_interface_filament == 0) { + if (resolved_default > 0) { + unsigned int i = resolved_default - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } else + support_uses_current_extruder = true; + } else { unsigned int i = (unsigned int)object->config().support_interface_filament - 1; extruders.emplace_back((i >= num_extruders) ? 0 : i); } @@ -1803,6 +1820,19 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } return true; }; + // ORCA: append per-extruder layer height / support nozzle diagnostics to the single warning out-param. + auto warn = [warning](const std::string &msg, const std::string &opt_key, const PrintObject *object) { + if (warning) { + if (! warning->string.empty()) + warning->string += "\n"; + warning->string += msg; + warning->opt_key = opt_key; + warning->object = object; + } + }; + // Flags for the per-extruder layer height / support nozzle warnings below. + bool warned_unhonored_heights = false, warned_support_mixed_nozzles = false, + warned_below_min_heights = false, warned_pitch_fallbacks = false; for (PrintObject *object : m_objects) { if (object->has_support_material()) { // BBS: remove useless logics and L() @@ -1824,6 +1854,26 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } #endif + // ORCA: multi-nozzle support restriction ("support_nozzle_diameter"). + if (const double support_nozzle = object->config().support_nozzle_diameter.value; support_nozzle > 0.) { + if (std::none_of(m_config.nozzle_diameter.values.begin(), m_config.nozzle_diameter.values.end(), + [support_nozzle](double d) { return std::abs(d - support_nozzle) < EPSILON; })) + return {L("No extruder has a nozzle matching the support nozzle diameter."), object, "support_nozzle_diameter"}; + if (object->config().support_filament.value > 0 && + std::abs(m_config.nozzle_diameter.get_at(object->config().support_filament.value - 1) - support_nozzle) > EPSILON) + return {L("The support/raft base filament prints with a nozzle that does not match the support nozzle diameter."), object, "support_filament"}; + if (object->config().support_interface_filament.value > 0 && + std::abs(m_config.nozzle_diameter.get_at(object->config().support_interface_filament.value - 1) - support_nozzle) > EPSILON) + return {L("The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter."), object, "support_interface_filament"}; + } else if (! warned_support_mixed_nozzles && max_nozzle_diameter - min_nozzle_diameter > EPSILON && + (object->config().support_filament.value == 0 || object->config().support_interface_filament.value == 0)) { + // Supports left on the "default" filament follow the active extruder, mixing nozzle sizes. + warn(L("Support may print with extruders of differing nozzle diameters. Set the support " + "nozzle diameter (or explicit support and interface filaments) to keep the support " + "on one nozzle size."), "support_nozzle_diameter", object); + warned_support_mixed_nozzles = true; + } + // Prusa: Fixing crashes with invalid tip diameter or branch diameter // https://github.com/prusa3d/PrusaSlicer/commit/96b3ae85013ac363cd1c3e98ec6b7938aeacf46d if (is_tree(object->config().support_type.value) && (object->config().support_style == smsTreeOrganic || @@ -1862,9 +1912,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* size_t first_layer_extruder = object->config().raft_layers == 1 ? object->config().support_interface_filament-1 : object->config().support_filament-1; - first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? - min_nozzle_diameter : - m_config.nozzle_diameter.get_at(first_layer_extruder); + if (first_layer_extruder == size_t(-1) && object->config().support_nozzle_diameter.value > 0.) + // ORCA: a "default" support filament under the restriction prints the raft with the restricted nozzle. + first_layer_min_nozzle_diameter = object->config().support_nozzle_diameter.value; + else + first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? + min_nozzle_diameter : + m_config.nozzle_diameter.get_at(first_layer_extruder); } else { // if we don't have raft layers, any nozzle diameter is potentially used in first layer first_layer_min_nozzle_diameter = min_nozzle_diameter; @@ -1889,6 +1943,219 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* for (const PrintRegion ®ion : object->all_regions()) if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) return {err_msg, object, opt_key}; + + // ORCA: per-extruder layer height ("extruder_layer_height"). + if (layer_height > EPSILON) { + // Gates diagnostics that would otherwise fire for configurations not using the feature. + const bool heights_feature_active = std::any_of(m_config.extruder_layer_height.values.begin(), + m_config.extruder_layer_height.values.end(), [](double v) { return v > 0.; }); + bool object_has_combined_regions = false; + for (const PrintRegion ®ion : object->all_regions()) { + const PrintRegionConfig ®ion_config = region.config(); + // The wall filaments define the part's layer pitch and must satisfy the strict lattice constraints below; must match the gating of PrintObject::region_layer_height_multiplier(). + std::vector lattice_filaments; // 0-based + bool has_mixed_wall = false; + if (region_config.wall_loops.value > 0) { + const int num_filaments = (int)m_config.filament_diameter.size(); + auto emplace_filament = [num_filaments, &lattice_filaments, &has_mixed_wall](int filament_id) { + int i = std::max(0, filament_id - 1); + if (i >= num_filaments) { + // Snapmaker mixed filament: virtual mixed ids resolve to a different + // physical extruder per layer; such walls never combine + // (matches PrintObject::region_layer_height_multiplier()). + has_mixed_wall = true; + return; + } + lattice_filaments.emplace_back(i); + }; + emplace_filament(region_config.outer_wall_filament_id.value); + if (region_config.wall_loops.value > 1) + emplace_filament(region_config.inner_wall_filament_id.value); + } else + PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, lattice_filaments); + unsigned int region_multiplier = 0; + for (const unsigned int filament : lattice_filaments) { + // The filament index is the extruder index on classic multi-tool printers. + const size_t extruder_idx = filament; + const double extruder_height = m_config.extruder_layer_height.get_at(extruder_idx); + unsigned int multiplier = 1; + if (extruder_height > 0.) { + if (extruder_height < layer_height - EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). " + "Lower the object layer height to the finest extruder layer height."), + extruder_idx + 1, extruder_height, layer_height), object, "extruder_layer_height" }; + const int n = int(std::lround(extruder_height / layer_height)); + if (std::abs(extruder_height - n * layer_height) > EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)."), + extruder_idx + 1, extruder_height, layer_height), object, "extruder_layer_height" }; + if (extruder_height > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter."), + extruder_idx + 1, extruder_height), object, "extruder_layer_height" }; + // 0 means "auto" (0.75 of the nozzle diameter for adaptive layer heights), not enforced here. + if (const double max_lh = m_config.max_layer_height.get_at(extruder_idx); max_lh > EPSILON && extruder_height > max_lh + EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its maximum layer height (%3% mm)."), + extruder_idx + 1, extruder_height, max_lh), object, "extruder_layer_height" }; + if (const double min_lh = m_config.min_layer_height.get_at(extruder_idx); min_lh > EPSILON && extruder_height < min_lh - EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is below its minimum layer height (%3% mm)."), + extruder_idx + 1, extruder_height, min_lh), object, "extruder_layer_height" }; + multiplier = (unsigned int)n; + } + if (region_multiplier == 0) + region_multiplier = multiplier; + else if (region_multiplier != multiplier) { + // Walls print together; differing preferences fall back to the object layer height (warned below). + region_multiplier = 1; + break; + } + } + if (region_multiplier == 0 || has_mixed_wall) + region_multiplier = 1; + // 0-based indices of all filaments printing this region's features. + std::vector used_filaments; + PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, used_filaments); + if (region_multiplier > 1) + // A feature filament that cannot print the walls' pitch makes the part fall back to the object layer height (mirrors PrintObject::region_layer_height_multiplier()). + for (const unsigned int filament : used_filaments) { + const size_t extruder_idx = filament; // filament index == extruder index here + const double pitch = region_multiplier * layer_height; + const double max_lh = m_config.max_layer_height.get_at(extruder_idx); + const double min_lh = m_config.min_layer_height.get_at(extruder_idx); + if (pitch > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON || + (max_lh > EPSILON && pitch > max_lh + EPSILON) || + (min_lh > EPSILON && pitch < min_lh - EPSILON)) { + if (! warned_pitch_fallbacks) { + warned_pitch_fallbacks = true; + warn(Slic3r::format(_u8L("The walls of some object parts prefer %1% mm layers, but filament %2% printing " + "other features of the part cannot print that height (nozzle diameter or layer " + "height limits). These parts print with the object layer height instead."), + pitch, filament + 1), + "extruder_layer_height", object); + } + region_multiplier = 1; + break; + } + } + const double region_pitch = region_multiplier * layer_height; + // Infill combines up to the preferred height of its printing filament (sparse, or internal solid at 100% density; mirrors PrintObject::combine_infill()); its width must cover the tallest group. + const bool solid_combined_infill = std::fabs(region_config.sparse_infill_density.value - 100.) < EPSILON; + double combined_infill_height = 0.; + unsigned int combined_infill_filament = 0; // 0-based, only valid while combining + if (region_multiplier == 1 && region_config.sparse_infill_density.value > 0) { + // This fork prints internal solid infill at 100% density with the sparse + // filament (internal_solid_infill_uses_sparse_filament), so the sparse + // filament decides in both cases (mirrors PrintObject::combine_infill()). + const int filament_id = region_config.sparse_infill_filament_id.value; + const FlowRole role = solid_combined_infill ? frSolidInfill : frInfill; + const char *width_key = solid_combined_infill ? "internal_solid_infill_line_width" : "sparse_infill_line_width"; + const double preferred = object->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)); + if (preferred > layer_height + EPSILON) { + const double infill_nozzle = m_config.nozzle_diameter.get_at(region.extruder(role) - 1); + // Mirrors PrintObject::combine_infill(): groups capped by both feature nozzles and the printing extruder's max layer height (0 = unset). + const unsigned int combine_filament0 = (unsigned int)std::max(1, filament_id) - 1; + double combine_cap = std::min({preferred, + m_config.nozzle_diameter.get_at(std::max(1, region_config.sparse_infill_filament_id.value) - 1), + m_config.nozzle_diameter.get_at(std::max(1, region_config.internal_solid_filament_id.value) - 1)}); + if (const double max_lh = m_config.max_layer_height.get_at(combine_filament0); max_lh > EPSILON) + combine_cap = std::min(combine_cap, max_lh); + combined_infill_height = std::floor(combine_cap / layer_height + EPSILON) * layer_height; + combined_infill_filament = combine_filament0; + auto width_opt = *region_config.option(width_key); + if (width_opt.value == 0.) + width_opt = object->config().line_width; + const double width = width_opt.value == 0. ? + double(Flow::auto_extrusion_width(role, float(infill_nozzle))) : + width_opt.get_abs_value(infill_nozzle); + if (combined_infill_height > layer_height + EPSILON && width <= combined_infill_height + EPSILON) + return { Slic3r::format(_u8L("The %1% mm infill line width is too small for infill combined to %2% mm high layers. " + "Increase the line width or lower the preferred layer height of the infill filament."), + width, combined_infill_height), object, width_key }; + } + } + // Warn once when a filament's minimum layer height is above the height its features print at (the part's pitch; the combined infill prints above the minimum by construction). + if (! warned_below_min_heights && heights_feature_active) + for (const unsigned int filament : used_filaments) { + const double min_lh = m_config.min_layer_height.get_at(filament); + if (min_lh <= EPSILON) + continue; + const bool pitch_below_min = region_pitch < min_lh - EPSILON; + // Areas of a combined region that cannot reach the pitch fall back to the object layer height (see apply_extruder_layer_heights()), possibly below the minimum. + const bool fallback_below_min = region_multiplier > 1 && layer_height < min_lh - EPSILON; + if (! pitch_below_min && ! fallback_below_min) + continue; + if (combined_infill_height > 0. && filament == combined_infill_filament && + combined_infill_height >= min_lh - EPSILON && ! fallback_below_min) + continue; + warned_below_min_heights = true; + warn(Slic3r::format(_u8L("Some object parts print %1% mm layers with filament %2% whose minimum layer " + "height is %3% mm. Raise the object layer height, use a filament with a finer " + "nozzle for these features, or accept printing below the extruder's minimum."), + pitch_below_min ? region_pitch : layer_height, filament + 1, min_lh), + "extruder_layer_height", object); + break; + } + // Warn once about preferred heights the part cannot honor: walls set the pitch, infill combines separately, everything else prints with the pitch. + if (! warned_unhonored_heights) + for (const unsigned int filament : used_filaments) { + const double preferred = object->extruder_preferred_layer_height(filament + 1); + if (preferred <= 0. || std::abs(preferred - region_pitch) < EPSILON) + continue; + if (combined_infill_height > 0. && filament == combined_infill_filament) + continue; + // At 100% density no sparse surfaces exist, the sparse filament's preference is moot. + if (solid_combined_infill && + filament == (unsigned int)std::max(1, region_config.sparse_infill_filament_id.value) - 1) + continue; + warned_unhonored_heights = true; + warn(_u8L("Some object parts use filaments whose preferred layer heights cannot all be honored: " + "the wall filaments set a part's layer pitch, the infill can combine to a thicker height, " + "and the remaining features print with the part's pitch."), + "extruder_layer_height", object); + break; + } + if (region_multiplier > 1) { + object_has_combined_regions = true; + // The combined extrusions are thicker, so line widths must stay above the combined + // height, resolved per role against the region's own nozzle (mirrors PrintRegion::flow()), + // unlike validate_extrusion_width() above which uses the object's smallest nozzle. + // (role, 1-based filament printing it, width key). Bottom surfaces print + // the solid infill width with the bottom surface filament (Fill.cpp). + const std::tuple width_roles[] = { + { frExternalPerimeter, region_config.outer_wall_filament_id.value, "outer_wall_line_width" }, + { frPerimeter, region_config.inner_wall_filament_id.value, "inner_wall_line_width" }, + { frInfill, region_config.sparse_infill_filament_id.value, "sparse_infill_line_width" }, + { frSolidInfill, region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width" }, + { frTopSolidInfill, region_config.top_surface_filament_id.value, "top_surface_line_width" }, + { frSolidInfill, region_config.bottom_surface_filament_id.value, "internal_solid_infill_line_width" }, + }; + for (const auto &[role, filament_id, width_key] : width_roles) { + const double nozzle_diameter = m_config.nozzle_diameter.get_at(std::max(1, filament_id) - 1); + auto width_opt = *region_config.option(width_key); + if (width_opt.value == 0.) + width_opt = object->config().line_width; + const double width = width_opt.value == 0. ? + double(Flow::auto_extrusion_width(role, float(nozzle_diameter))) : + width_opt.get_abs_value(nozzle_diameter); + if (width <= region_pitch + EPSILON) + return { Slic3r::format(_u8L("The %1% mm line width is too small for the %2% mm layer height of its extruder. " + "Increase the line width or lower the extruder layer height."), + width, region_pitch), object, width_key }; + } + } + } + if (object_has_combined_regions) { + if (m_config.spiral_mode) + return { _u8L("Per-extruder layer heights are not supported in spiral vase mode."), object, "extruder_layer_height" }; + if (object->config().interface_shells) + return { _u8L("Per-extruder layer heights are not supported together with interface shells."), object, "extruder_layer_height" }; + if (object->model_object()->has_custom_layering()) { + std::vector profile; + PrintObject::update_layer_height_profile(*object->model_object(), object->slicing_parameters(), profile, object); + if (! check_object_layers_fixed(object->slicing_parameters(), profile)) + return { _u8L("Per-extruder layer heights are not supported together with variable layer height."), + object, "extruder_layer_height" }; + } + } + } } } @@ -2186,7 +2453,7 @@ Flow Print::brim_flow() const frPerimeter, // Flow::new_from_config_width takes care of the percent to value substitution width, - (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1), + (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().outer_wall_filament_id-1), (float)this->skirt_first_layer_height()); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b618641c35e..6632506b945 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -154,7 +154,9 @@ class PrintRegion int print_object_region_id() const throw() { return m_print_object_region_id; } // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; - Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; + // filament_id: 1-based filament actually printing this flow when it differs from the role's default + // mapping (top / bottom surface fills), 0 to resolve the filament from the role. + Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false, unsigned int filament_id = 0) const; // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. @@ -486,6 +488,23 @@ class PrintObject : public PrintObjectBaseWithState object_extruders() const; + // ORCA: per-extruder layer height ("extruder_layer_height" printer option). + // Preferred layer height (mm) of the extruder printing the given 1-based filament id; 0 when unset or not reliably resolvable. + double extruder_preferred_layer_height(unsigned int filament_id) const; + // Object layers a region printing with the given 1-based filament id combines into one extrusion where its geometry allows it (1 = no combining). + unsigned int layer_height_multiplier_for_filament(unsigned int filament_id) const; + // Layer pitch multiplier of a region, driven by its wall filaments; 1 when no combining is requested or possible. + unsigned int region_layer_height_multiplier(const PrintRegion ®ion) const; + // Any region of this object printing with a layer height multiplier > 1? + bool has_combined_layer_regions() const; + // Multi-nozzle support restriction ("support_nozzle_diameter" print option): may the given 1-based filament print this object's support / raft? + // Always true when the restriction is disabled or for the "default" filament 0. + bool support_filament_allowed(unsigned int filament_id) const; + // 1-based filament a "default" (0) support filament resolves to under the support nozzle + // diameter restriction when none of a layer's own filaments match: the first non-soluble + // matching-nozzle filament, else the first matching one; 0 when the restriction is disabled or nothing matches. + unsigned int resolved_default_support_filament() const; + // Called by make_perimeters() void slice(); @@ -550,6 +569,8 @@ class PrintObject : public PrintObjectBaseWithStateconfig(); - cfg.wall_filament.value = region.extruder_id; - cfg.solid_infill_filament.value = region.extruder_id; - cfg.sparse_infill_filament.value = region.extruder_id; + cfg.outer_wall_filament_id.value = region.extruder_id; + cfg.inner_wall_filament_id.value = region.extruder_id; + cfg.internal_solid_filament_id.value = region.extruder_id; + cfg.top_surface_filament_id.value = region.extruder_id; + cfg.bottom_surface_filament_id.value = region.extruder_id; + cfg.sparse_infill_filament_id.value = region.extruder_id; if (cfg != region.region->config()) { // Region configuration changed. if (print_region_ref_cnt(*region.region) == 0) { @@ -1073,20 +1076,29 @@ static PrintObjectRegions* generate_print_object_regions( (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; + cfg.outer_wall_filament_id.value = painted_extruder_id; + cfg.inner_wall_filament_id.value = painted_extruder_id; + cfg.internal_solid_filament_id.value = painted_extruder_id; + cfg.top_surface_filament_id.value = painted_extruder_id; + cfg.bottom_surface_filament_id.value = painted_extruder_id; + cfg.sparse_infill_filament_id.value = painted_extruder_id; // Keep PrintRegion config-interned. If a painted target resolves to the same // config as its parent, alias it instead of creating a duplicate PrintRegion. PrintRegion *painted_region = get_create_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) { + if (painted_region->config().outer_wall_filament_id.value != painted_extruder_id || + painted_region->config().inner_wall_filament_id.value != painted_extruder_id || + painted_region->config().internal_solid_filament_id.value != painted_extruder_id || + painted_region->config().top_surface_filament_id.value != painted_extruder_id || + painted_region->config().bottom_surface_filament_id.value != painted_extruder_id || + painted_region->config().sparse_infill_filament_id.value != painted_extruder_id) { BOOST_LOG_TRIVIAL(warning) << "Painted region filament mismatch" << " requested_extruder_id=" << painted_extruder_id - << " wall_filament=" << painted_region->config().wall_filament.value - << " solid_infill_filament=" << painted_region->config().solid_infill_filament.value - << " sparse_infill_filament=" << painted_region->config().sparse_infill_filament.value + << " outer_wall_filament_id=" << painted_region->config().outer_wall_filament_id.value + << " inner_wall_filament_id=" << painted_region->config().inner_wall_filament_id.value + << " internal_solid_filament_id=" << painted_region->config().internal_solid_filament_id.value + << " top_surface_filament_id=" << painted_region->config().top_surface_filament_id.value + << " bottom_surface_filament_id=" << painted_region->config().bottom_surface_filament_id.value + << " sparse_infill_filament_id=" << painted_region->config().sparse_infill_filament_id.value << " parent_region_id=" << parent_region_id << " parent_print_region_id=" << parent_region.region->print_object_region_id() << " painted_print_region_id=" << painted_region->print_object_region_id(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c4881c23c9a..e5a1b28e4aa 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -294,6 +294,13 @@ static t_config_enum_values s_keys_map_EnsureVerticalShellThickness{ }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsureVerticalShellThickness) +// ORCA: per-extruder layer height ("extruder_layer_height"). +static t_config_enum_values s_keys_map_ExtruderLayerHeightMode{ + { "consistent", int(ExtruderLayerHeightMode::elhmConsistent) }, + { "adaptive", int(ExtruderLayerHeightMode::elhmAdaptive) }, +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderLayerHeightMode) + // Orca static t_config_enum_values s_keys_map_InternalBridgeFilter { { "disabled", ibfDisabled }, @@ -3400,14 +3407,14 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(100., true)); - def = this->add("sparse_infill_filament", coInt); + def = this->add("sparse_infill_filament_id", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Infill"); def->category = L("Extruders"); - def->tooltip = L("Filament to print internal sparse infill."); - def->min = 1; + def->tooltip = L("Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament."); + def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("sparse_infill_line_width", coFloatOrPercent); def->label = L("Sparse infill"); @@ -3952,12 +3959,61 @@ void PrintConfigDef::init_fff_params() def = this->add("min_layer_height", coFloats); def->label = L("Min"); def->tooltip = L("The lowest printable layer height for the extruder. " - "Used to limit the minimum layer height when enable adaptive layer height."); + "Used to limit the minimum layer height when enable adaptive layer height. " + "Parts printed with a thicker preferred extruder layer height never fall back " + "below this height either (the first layer excepted)."); def->sidetext = "mm"; // milimeters, don't need translation def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0.07 }); + def = this->add("extruder_layer_height", coFloats); + def->label = L("Preferred layer height"); + def->tooltip = L("Layer height this extruder should print with, used for printers whose extruders have " + "different nozzle sizes. Object parts whose walls are assigned to this extruder are " + "printed only on every Nth layer with correspondingly thicker extrusions, wherever " + "their geometry allows it (near-vertical walls); elsewhere they fall back to the " + "object layer height. It must then be an integer multiple of the object layer height. " + "When only the sparse infill of a part uses this extruder, the infill is combined up " + "to this height instead. 0 means to use the object layer height."); + def->sidetext = "mm"; // milimeters, don't need translation + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("extruder_layer_height_mode", coEnum); + def->label = L("Thick layer regions"); + def->category = L("Quality"); + def->tooltip = L("How aggressively object parts assigned to an extruder with a thicker preferred layer " + "height are combined into thick layers.\n" + "Consistent: parts print with at most two layer heights, the extruder layer height " + "wherever whole runs of layers fit and the object layer height everywhere else. This " + "gives the most uniform walls.\n" + "Adaptive: runs may also be combined at intermediate multiples of the object layer " + "height, so more of the part prints with thicker layers, at the price of bands of " + "varying layer heights on curved part boundaries."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("consistent"); + def->enum_values.push_back("adaptive"); + def->enum_labels.push_back(L("Consistent")); + def->enum_labels.push_back(L("Adaptive")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(elhmConsistent)); + + def = this->add("extruder_layer_height_tolerance", coPercent); + def->label = L("Thick layer tolerance"); + def->category = L("Quality"); + def->tooltip = L("How far the outline of an object part assigned to an extruder with a thicker preferred " + "layer height may drift sideways across the layers of one thick run and still be combined, " + "as a percentage of that extruder's nozzle diameter. Higher values combine more of curved " + "part boundaries into thick layers, at the price of rougher boundary walls: deviations up " + "to this fraction of the nozzle diameter are swallowed by the thick extrusions."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(10)); + def = this->add("slow_down_min_speed", coFloats); def->label = L("Min print speed"); def->tooltip = L("The minimum print speed to which the printer slows down to maintain the minimum layer time defined above " @@ -4139,14 +4195,23 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); - def = this->add("wall_filament", coInt); + def = this->add("outer_wall_filament_id", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Walls"); + def->label = L("Outer walls"); def->category = L("Extruders"); - def->tooltip = L("Filament to print walls."); - def->min = 1; + def->tooltip = L("Filament to print outer walls.\n\"Default\" uses the active object/part filament."); + def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("inner_wall_filament_id", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Inner walls"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print inner walls.\n\"Default\" uses the active object/part filament."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); def = this->add("inner_wall_line_width", coFloatOrPercent); def->label = L("Inner wall"); @@ -4936,14 +5001,32 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(15)); - def = this->add("solid_infill_filament", coInt); + def = this->add("internal_solid_filament_id", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Solid infill"); + def->label = L("Internal solid infill"); def->category = L("Extruders"); - def->tooltip = L("Filament to print solid infill."); - def->min = 1; + def->tooltip = L("Filament to print internal solid infill.\n\"Default\" uses the active object/part filament."); + def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("top_surface_filament_id", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Top surface"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print top surface.\n\"Default\" uses the active object/part filament."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("bottom_surface_filament_id", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Bottom surface"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print bottom surface.\n\"Default\" uses the active object/part filament."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); def = this->add("internal_solid_infill_line_width", coFloatOrPercent); def->label = L("Internal solid infill"); @@ -5314,7 +5397,7 @@ void PrintConfigDef::init_fff_params() def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Support/raft base"); def->category = L("Support"); - def->tooltip = L("Filament to print support base and raft. \"Default\" means no specific filament for support and current filament is used."); + def->tooltip = L("Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used."); def->min = 0; def->mode = comSimple; def->set_default_value(new ConfigOptionInt(0)); @@ -5326,6 +5409,19 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("support_nozzle_diameter", coFloat); + def->label = L("Support nozzle diameter"); + def->category = L("Support"); + def->tooltip = L("On printers whose extruders have different nozzle diameters, only filaments of this " + "nozzle diameter are used to print support, raft and support interface. This keeps " + "filaments of other nozzle sizes - with their different line widths and layer height " + "limits - out of the support. Support filaments set to a non-default value must match " + "this diameter. Value 0 allows any filament to print support."); + def->sidetext = "mm"; // milimeters, don't need translation + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("support_line_width", coFloatOrPercent); def->label = L("Support"); def->category = L("Quality"); @@ -5349,7 +5445,7 @@ void PrintConfigDef::init_fff_params() def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Support/raft interface"); def->category = L("Support"); - def->tooltip = L("Filament to print support interface. \"Default\" means no specific filament for support interface and current filament is used."); + def->tooltip = L("Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used."); def->min = 0; // BBS def->mode = comSimple; @@ -6426,7 +6522,7 @@ void PrintConfigDef::init_extruder_option_keys() { // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings m_extruder_option_keys = { - "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", + "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_layer_height", "extruder_offset", "retraction_length", "z_hop", "z_hop_types", "z_hop_when_prime", "travel_slope", "retract_lift_above", "retract_lift_below", "retract_lift_enforce", "retraction_speed", "deretraction_speed", "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", @@ -7145,12 +7241,34 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = "change_filament_gcode"; } else if (opt_key == "bridge_fan_speed") { opt_key = "overhang_fan_speed"; - } else if (opt_key == "infill_extruder") { - opt_key = "sparse_infill_filament"; - }else if (opt_key == "solid_infill_extruder") { - opt_key = "solid_infill_filament"; - }else if (opt_key == "perimeter_extruder") { - opt_key = "wall_filament"; + } else if (opt_key == "infill_extruder" || opt_key == "sparse_infill_filament") { + // ORCA: legacy feature-filament selector. Pre-2.4.0-dev these keys were 1-based and the + // default value "1" meant "the first/active filament". The current scheme uses a dedicated + // key where 0 = "Default" (inherit the object/part filament) and 1..N = explicit filament. + // Renaming to the new *_id key here means every config (process presets, 3mf project + // settings, imported gcode) is translated uniformly on load - not just the version-gated + // 3mf paths the old bespoke migration covered - and a brand-new key can never be misread as + // a legacy default. Map the legacy default "1" to "0" (inherit); keep explicit values >1. + opt_key = "sparse_infill_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "solid_infill_extruder" || opt_key == "solid_infill_filament") { + opt_key = "internal_solid_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "top_solid_infill_filament") { + opt_key = "top_surface_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "bottom_solid_infill_filament") { + opt_key = "bottom_surface_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "perimeter_extruder" || opt_key == "wall_filament" || opt_key == "wall_filament_id") { + opt_key = "outer_wall_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "inner_wall_filament") { + opt_key = "inner_wall_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "outer_wall_filament") { + opt_key = "outer_wall_filament_id"; + if (value == "1") value = "0"; }else if(opt_key == "wipe_tower_extruder") { opt_key = "wipe_tower_filament"; }else if (opt_key == "support_material_extruder") { @@ -7398,10 +7516,18 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) int extruder = this->option("extruder")->getInt(); this->erase("extruder"); if (extruder != 0) { - if (!this->has("sparse_infill_filament")) - this->option("sparse_infill_filament", true)->setInt(extruder); - if (!this->has("wall_filament")) - this->option("wall_filament", true)->setInt(extruder); + if (!this->has("sparse_infill_filament_id") || this->option("sparse_infill_filament_id")->getInt() == 0) + this->option("sparse_infill_filament_id", true)->setInt(extruder); + if (!this->has("outer_wall_filament_id") || this->option("outer_wall_filament_id")->getInt() == 0) + this->option("outer_wall_filament_id", true)->setInt(extruder); + if (!this->has("inner_wall_filament_id") || this->option("inner_wall_filament_id")->getInt() == 0) + this->option("inner_wall_filament_id", true)->setInt(extruder); + if (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0) + this->option("internal_solid_filament_id", true)->setInt(extruder); + if (!this->has("top_surface_filament_id") || this->option("top_surface_filament_id")->getInt() == 0) + this->option("top_surface_filament_id", true)->setInt(extruder); + if (!this->has("bottom_surface_filament_id") || this->option("bottom_surface_filament_id")->getInt() == 0) + this->option("bottom_surface_filament_id", true)->setInt(extruder); // Don't propagate the current extruder to support. // For non-soluble supports, the default "0" extruder means to use the active extruder, // for soluble supports one certainly does not want to set the extruder to non-soluble. @@ -7420,8 +7546,24 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) this->option("wipe_tower_filament")->setInt(0); } - if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) - this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); + if (this->has("sparse_infill_filament_id")) { + int sparse_infill_filament_id = this->option("sparse_infill_filament_id")->getInt(); + if (sparse_infill_filament_id > 0 && (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0)) + this->option("internal_solid_filament_id", true)->setInt(sparse_infill_filament_id); + } + + const int internal_solid = this->has("internal_solid_filament_id") ? this->option("internal_solid_filament_id")->getInt() : 0; + const int top_surface = this->has("top_surface_filament_id") ? this->option("top_surface_filament_id")->getInt() : 0; + const int bottom_surface = this->has("bottom_surface_filament_id") ? this->option("bottom_surface_filament_id")->getInt() : 0; + + if (internal_solid == 0 && top_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(top_surface); + if (internal_solid == 0 && bottom_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(bottom_surface); + if (top_surface == 0 && internal_solid > 0) + this->option("top_surface_filament_id", true)->setInt(internal_solid); + if (bottom_surface == 0 && internal_solid > 0) + this->option("bottom_surface_filament_id", true)->setInt(internal_solid); if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { @@ -7479,10 +7621,18 @@ void DynamicPrintConfig::normalize_fdm_1() int extruder = this->option("extruder")->getInt(); this->erase("extruder"); if (extruder != 0) { - if (!this->has("sparse_infill_filament")) - this->option("sparse_infill_filament", true)->setInt(extruder); - if (!this->has("wall_filament")) - this->option("wall_filament", true)->setInt(extruder); + if (!this->has("sparse_infill_filament_id") || this->option("sparse_infill_filament_id")->getInt() == 0) + this->option("sparse_infill_filament_id", true)->setInt(extruder); + if (!this->has("outer_wall_filament_id") || this->option("outer_wall_filament_id")->getInt() == 0) + this->option("outer_wall_filament_id", true)->setInt(extruder); + if (!this->has("inner_wall_filament_id") || this->option("inner_wall_filament_id")->getInt() == 0) + this->option("inner_wall_filament_id", true)->setInt(extruder); + if (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0) + this->option("internal_solid_filament_id", true)->setInt(extruder); + if (!this->has("top_surface_filament_id") || this->option("top_surface_filament_id")->getInt() == 0) + this->option("top_surface_filament_id", true)->setInt(extruder); + if (!this->has("bottom_surface_filament_id") || this->option("bottom_surface_filament_id")->getInt() == 0) + this->option("bottom_surface_filament_id", true)->setInt(extruder); // Don't propagate the current extruder to support. // For non-soluble supports, the default "0" extruder means to use the active extruder, // for soluble supports one certainly does not want to set the extruder to non-soluble. @@ -7493,8 +7643,24 @@ void DynamicPrintConfig::normalize_fdm_1() } } - if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) - this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); + if (this->has("sparse_infill_filament_id")) { + int sparse_infill_filament_id = this->option("sparse_infill_filament_id")->getInt(); + if (sparse_infill_filament_id > 0 && (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0)) + this->option("internal_solid_filament_id", true)->setInt(sparse_infill_filament_id); + } + + const int internal_solid = this->has("internal_solid_filament_id") ? this->option("internal_solid_filament_id")->getInt() : 0; + const int top_surface = this->has("top_surface_filament_id") ? this->option("top_surface_filament_id")->getInt() : 0; + const int bottom_surface = this->has("bottom_surface_filament_id") ? this->option("bottom_surface_filament_id")->getInt() : 0; + + if (internal_solid == 0 && top_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(top_surface); + if (internal_solid == 0 && bottom_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(bottom_surface); + if (top_surface == 0 && internal_solid > 0) + this->option("top_surface_filament_id", true)->setInt(internal_solid); + if (bottom_surface == 0 && internal_solid > 0) + this->option("bottom_surface_filament_id", true)->setInt(internal_solid); if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 68d18bccf2c..b7aa97ea316 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -195,6 +195,12 @@ enum EnsureVerticalShellThickness { evstAll, }; +// ORCA: per-extruder layer height ("extruder_layer_height"). +enum ExtruderLayerHeightMode { + elhmConsistent, + elhmAdaptive, +}; + //Orca enum InternalBridgeFilter { ibfDisabled, ibfLimited, ibfNofilter @@ -460,6 +466,7 @@ static std::string get_bed_temp_1st_layer_key(const BedType type) template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ExtruderLayerHeightMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinMode) @@ -837,6 +844,9 @@ PRINT_CONFIG_CLASS_DEFINE( // Force the generation of solid shells between adjacent materials/volumes. ((ConfigOptionBool, interface_shells)) ((ConfigOptionFloat, layer_height)) + // ORCA: per-extruder layer height ("extruder_layer_height"). + ((ConfigOptionEnum, extruder_layer_height_mode)) + ((ConfigOptionPercent, extruder_layer_height_tolerance)) ((ConfigOptionFloat, mmu_segmented_region_max_width)) ((ConfigOptionFloat, mmu_segmented_region_interlocking_depth)) ((ConfigOptionFloat, raft_contact_distance)) @@ -860,6 +870,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_bottom_z_distance)) ((ConfigOptionInt, enforce_support_layers)) ((ConfigOptionInt, support_filament)) + // ORCA: restrict support/raft/interface printing to filaments of this nozzle diameter (0 = no restriction). + ((ConfigOptionFloat, support_nozzle_diameter)) ((ConfigOptionFloatOrPercent, support_line_width)) ((ConfigOptionBool, support_interface_not_for_body)) ((ConfigOptionBool, support_interface_loop_pattern)) @@ -1010,7 +1022,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, fuzzy_skin_octaves)) ((ConfigOptionFloat, fuzzy_skin_persistence)) ((ConfigOptionFloat, gap_infill_speed)) - ((ConfigOptionInt, sparse_infill_filament)) + ((ConfigOptionInt, sparse_infill_filament_id)) ((ConfigOptionFloatOrPercent, sparse_infill_line_width)) ((ConfigOptionPercent, infill_wall_overlap)) ((ConfigOptionPercent, top_bottom_infill_wall_overlap)) @@ -1036,14 +1048,17 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, ironing_angle)) // Detect bridging perimeters ((ConfigOptionBool, detect_overhang_wall)) - ((ConfigOptionInt, wall_filament)) + ((ConfigOptionInt, outer_wall_filament_id)) + ((ConfigOptionInt, inner_wall_filament_id)) ((ConfigOptionFloatOrPercent, inner_wall_line_width)) ((ConfigOptionFloat, inner_wall_speed)) // Total number of perimeters. ((ConfigOptionInt, wall_loops)) ((ConfigOptionBool, alternate_extra_wall)) ((ConfigOptionFloat, minimum_sparse_infill_area)) - ((ConfigOptionInt, solid_infill_filament)) + ((ConfigOptionInt, internal_solid_filament_id)) + ((ConfigOptionInt, top_surface_filament_id)) + ((ConfigOptionInt, bottom_surface_filament_id)) ((ConfigOptionFloatOrPercent, internal_solid_infill_line_width)) ((ConfigOptionFloat, internal_solid_infill_speed)) // Detect thin walls. @@ -1355,6 +1370,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloats, max_layer_height)) ((ConfigOptionFloats, fan_min_speed)) ((ConfigOptionFloats, min_layer_height)) + ((ConfigOptionFloats, extruder_layer_height)) ((ConfigOptionFloat, printable_height)) ((ConfigOptionPoint, best_object_pos)) ((ConfigOptionFloats, slow_down_min_speed)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f649c0e2b1e..b0476125a64 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -223,7 +223,7 @@ void PrintObject::_transform_hole_to_polyholes() bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value; if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) { layerid2center[layer_idx].emplace_back( - std::tuple{center, diameter_max, layer->m_regions[region_idx]->region().config().wall_filament.value, max_variation, twist}, & hole); + std::tuple{center, diameter_max, layer->m_regions[region_idx]->region().config().outer_wall_filament_id.value, max_variation, twist}, & hole); } } } @@ -519,6 +519,18 @@ void PrintObject::prepare_infill() this->combine_infill(); m_print->throw_if_canceled(); + // Per-extruder layer height. Stamp the group height onto fill surfaces of combined LayerRegions (Fill uses Surface::thickness when set); must run last, earlier steps re-create surfaces. + if (this->has_combined_layer_regions()) { + for (Layer *layer : m_layers) + for (LayerRegion *layerm : layer->m_regions) + if (layerm->combined_layer_count() > 1) + for (Surface &surface : layerm->fill_surfaces.surfaces) { + surface.thickness = layerm->combined_height(); + surface.thickness_layers = layerm->combined_layer_count(); + } + m_print->throw_if_canceled(); + } + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { @@ -881,6 +893,18 @@ bool PrintObject::invalidate_state_by_config_options( std::vector steps; bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { + // Per-extruder layer height: these options feed region_layer_height_multiplier(), which decides layer combination at the slicing step. + if (opt_key == "outer_wall_filament_id" || opt_key == "inner_wall_filament_id" + || opt_key == "sparse_infill_filament_id" || opt_key == "internal_solid_filament_id" + || opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id" + || opt_key == "wall_loops" || opt_key == "sparse_infill_density" + || opt_key == "top_shell_layers" || opt_key == "bottom_shell_layers") { + for (double extruder_layer_height : m_print->config().extruder_layer_height.values) + if (extruder_layer_height > 0.) { + steps.emplace_back(posSlice); + break; + } + } if ( opt_key == "brim_width" || opt_key == "brim_object_gap" || opt_key == "brim_type" @@ -947,6 +971,9 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPerimeters); } else if ( opt_key == "layer_height" + // ORCA: per-extruder layer height: the layer combining happens at the slicing step. + || opt_key == "extruder_layer_height_mode" + || opt_key == "extruder_layer_height_tolerance" || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_whole_objects" @@ -1019,6 +1046,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_remove_small_overhang" || opt_key == "enforce_support_layers" || opt_key == "support_filament" + || opt_key == "support_nozzle_diameter" || opt_key == "support_line_width" || opt_key == "support_interface_top_layers" || opt_key == "support_interface_bottom_layers" @@ -1092,8 +1120,10 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "bottom_shell_thickness" || opt_key == "top_shell_thickness" || opt_key == "minimum_sparse_infill_area" - || opt_key == "sparse_infill_filament" - || opt_key == "solid_infill_filament" + || opt_key == "sparse_infill_filament_id" + || opt_key == "internal_solid_filament_id" + || opt_key == "top_surface_filament_id" + || opt_key == "bottom_surface_filament_id" || opt_key == "sparse_infill_line_width" || opt_key == "skin_infill_line_width" || opt_key == "skeleton_infill_line_width" @@ -1151,7 +1181,8 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPrepareInfill); } else if ( opt_key == "outer_wall_line_width" - || opt_key == "wall_filament" + || opt_key == "outer_wall_filament_id" + || opt_key == "inner_wall_filament_id" || opt_key == "fuzzy_skin" || opt_key == "fuzzy_skin_thickness" || opt_key == "fuzzy_skin_point_distance" @@ -1704,11 +1735,17 @@ void PrintObject::process_external_surfaces() for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z; - m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces( + LayerRegion *layerm = m_layers[layer_idx]->get_region(int(region_id)); + // Per-extruder layer height: a combined group's external surfaces relate to the layer below the whole group (lower_idx == layer_idx - 1 for regular regions; + // count == 0 on combined-away layers, hence the max(..., 1)). + const size_t combined_layers = std::max(layerm->combined_layer_count(), 1); + const bool has_lower = layer_idx >= combined_layers; + const size_t lower_idx = has_lower ? layer_idx - combined_layers : 0; + layerm->process_external_surfaces( // lower layer - (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], + has_lower ? m_layers[lower_idx] : nullptr, // lower layer polygons with density > 0% - (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); + (! has_lower || surfaces_covered.empty() || surfaces_covered[lower_idx].empty()) ? nullptr : &surfaces_covered[lower_idx]); } } ); @@ -1825,6 +1862,9 @@ void PrintObject::discover_vertical_shells() if (region.config().ensure_vertical_shell_thickness.value != evstAll ) // This region will be handled by discover_horizontal_shells(). continue; + if (this->region_layer_height_multiplier(region) > 1) + // Per-extruder layer height. Combined regions extrude on group tops only; their shells are ensured by discover_horizontal_shells() stepping over the groups instead. + continue; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -3190,6 +3230,15 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_total_fil opt.value = 1; } +// ORCA: feature filament selectors use 0 = "Default" (inherit the active object/part filament). +// Once the object/part/layer-range overrides are resolved, any remaining "Default" or invalid +// value falls back to filament 1. +static void clamp_feature_filament_to_valid(ConfigOptionInt &opt, size_t num_total_filaments) +{ + if (opt.value <= 0 || opt.value > (int)num_total_filaments) + opt.value = 1; +} + PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders) { PrintObjectConfig config = default_object_config; @@ -3205,55 +3254,124 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject } const std::string key_extruder { "extruder" }; -static constexpr const std::initializer_list keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv }; +static constexpr const std::initializer_list keys_extruders { + "sparse_infill_filament_id"sv, + "internal_solid_filament_id"sv, + "top_surface_filament_id"sv, + "bottom_surface_filament_id"sv, + "outer_wall_filament_id"sv, + "inner_wall_filament_id"sv +}; + +struct FeatureFilamentOverrideMask +{ + bool sparse_infill_filament_id = false; + bool internal_solid_filament_id = false; + bool top_surface_filament_id = false; + bool bottom_surface_filament_id = false; + bool outer_wall_filament_id = false; + bool inner_wall_filament_id = false; +}; -static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in) +static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides) { - // 1) Copy the "extruder key to sparse_infill_filament and wall_filament. + // 1) Explicit feature filament values take precedence over base extruder fallback. auto *opt_extruder = in.opt(key_extruder); - if (opt_extruder) - if (int extruder = opt_extruder->value; extruder != 0) { - // Not a default extruder. - out.sparse_infill_filament.value = extruder; - out.solid_infill_filament.value = extruder; - out.wall_filament.value = extruder; - } + int base_extruder = (opt_extruder != nullptr) ? opt_extruder->value : 0; + // 2) Copy the rest of the values. for (auto it = in.cbegin(); it != in.cend(); ++ it) if (it->first != key_extruder) if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) { if (one_of(it->first, keys_extruders)) { - // Ignore "default" extruders. + // "Default" (0) clears explicit override for this scope and lets fallback apply. int extruder = static_cast(it->second.get())->value; - if (extruder > 0) + if (extruder > 0) { my_opt->setInt(extruder); + if (it->first == "sparse_infill_filament_id") + feature_overrides.sparse_infill_filament_id = true; + else if (it->first == "internal_solid_filament_id") + feature_overrides.internal_solid_filament_id = true; + else if (it->first == "top_surface_filament_id") + feature_overrides.top_surface_filament_id = true; + else if (it->first == "bottom_surface_filament_id") + feature_overrides.bottom_surface_filament_id = true; + else if (it->first == "outer_wall_filament_id") + feature_overrides.outer_wall_filament_id = true; + else if (it->first == "inner_wall_filament_id") + feature_overrides.inner_wall_filament_id = true; + } else { + if (it->first == "sparse_infill_filament_id") + feature_overrides.sparse_infill_filament_id = false; + else if (it->first == "internal_solid_filament_id") + feature_overrides.internal_solid_filament_id = false; + else if (it->first == "top_surface_filament_id") + feature_overrides.top_surface_filament_id = false; + else if (it->first == "bottom_surface_filament_id") + feature_overrides.bottom_surface_filament_id = false; + else if (it->first == "outer_wall_filament_id") + feature_overrides.outer_wall_filament_id = false; + else if (it->first == "inner_wall_filament_id") + feature_overrides.inner_wall_filament_id = false; + } } else my_opt->set(it->second.get()); } + + // 3) Apply base extruder only to features that were not explicitly overridden. + if (base_extruder > 0) { + if (!feature_overrides.sparse_infill_filament_id) + out.sparse_infill_filament_id.value = base_extruder; + if (!feature_overrides.internal_solid_filament_id) + out.internal_solid_filament_id.value = base_extruder; + if (!feature_overrides.top_surface_filament_id) + out.top_surface_filament_id.value = base_extruder; + if (!feature_overrides.bottom_surface_filament_id) + out.bottom_surface_filament_id.value = base_extruder; + if (!feature_overrides.outer_wall_filament_id) + out.outer_wall_filament_id.value = base_extruder; + if (!feature_overrides.inner_wall_filament_id) + out.inner_wall_filament_id.value = base_extruder; + } } 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) { PrintRegionConfig config = default_or_parent_region_config; + FeatureFilamentOverrideMask feature_overrides; + + // For model parts, non-zero values coming from the print defaults should stay explicit. + if (volume.is_model_part()) { + feature_overrides.sparse_infill_filament_id = (config.sparse_infill_filament_id.value > 0); + feature_overrides.internal_solid_filament_id = (config.internal_solid_filament_id.value > 0); + feature_overrides.top_surface_filament_id = (config.top_surface_filament_id.value > 0); + feature_overrides.bottom_surface_filament_id = (config.bottom_surface_filament_id.value > 0); + feature_overrides.outer_wall_filament_id = (config.outer_wall_filament_id.value > 0); + feature_overrides.inner_wall_filament_id = (config.inner_wall_filament_id.value > 0); + } + if (volume.is_model_part()) { // default_or_parent_region_config contains the Print's PrintRegionConfig. // Override with ModelObject's PrintRegionConfig values. - apply_to_print_region_config(config, volume.get_object()->config.get()); + apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides); } else { // default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config. } - apply_to_print_region_config(config, volume.config.get()); + apply_to_print_region_config(config, volume.config.get(), feature_overrides); if (! volume.material_id().empty()) - apply_to_print_region_config(config, volume.material()->config.get()); + apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides); if (layer_range_config != nullptr) { // Not applicable to modifiers. assert(volume.is_model_part()); - apply_to_print_region_config(config, *layer_range_config); + apply_to_print_region_config(config, *layer_range_config, feature_overrides); } - // Clamp invalid extruders to the default extruder (with index 1). - clamp_exturder_to_default(config.sparse_infill_filament, num_extruders); - clamp_exturder_to_default(config.wall_filament, num_extruders); - clamp_exturder_to_default(config.solid_infill_filament, num_extruders); + // Resolve feature defaults and clamp invalid extruders to index 1. + clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.outer_wall_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.inner_wall_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.internal_solid_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.top_surface_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.bottom_surface_filament_id, num_extruders); if (config.sparse_infill_density.value < 0.00011f) // Switch of infill for very low infill rates, also avoid division by zero in infill generator for these very low rates. // See GH issue #5910. @@ -3316,9 +3434,12 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full object_config.brim_type != btNoBrim && object_config.brim_width > 0., object_extruders); for (const std::pair &range_and_config : model_object.layer_config_ranges) - if (range_and_config.second.has("wall_filament") || - range_and_config.second.has("sparse_infill_filament") || - range_and_config.second.has("solid_infill_filament")) + if (range_and_config.second.has("outer_wall_filament_id") || + range_and_config.second.has("inner_wall_filament_id") || + range_and_config.second.has("sparse_infill_filament_id") || + range_and_config.second.has("internal_solid_filament_id") || + range_and_config.second.has("top_surface_filament_id") || + range_and_config.second.has("bottom_surface_filament_id")) PrintRegion::collect_object_printing_extruders( print_config, region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders), @@ -3355,6 +3476,138 @@ std::vector PrintObject::object_extruders() const return extruders; } +// Per-extruder layer height ("extruder_layer_height" printer option). +// Raw preferred layer height (mm) of the extruder printing the given 1-based filament id, 0 when unset or not reliably resolvable. +double PrintObject::extruder_preferred_layer_height(unsigned int filament_id) const +{ + const PrintConfig &print_config = m_print->config(); + if (print_config.extruder_layer_height.empty()) + return 0.; + // Region filament ids are 1-based, 0 ("Default") falls back to the first filament. + if (filament_id == 0) + filament_id = 1; + // Snapmaker mixed filament: virtual mixed ids (beyond the physical filaments) resolve to a + // different physical extruder on every layer, so no stable preferred height exists for them. + if (filament_id > print_config.filament_diameter.values.size()) + return 0.; + // Classic multi-tool printers index per-extruder vectors by the filament id directly (like the min/max_layer_height and nozzle_diameter lookups elsewhere). + const size_t extruder_idx = filament_id - 1; + return std::max(0., print_config.extruder_layer_height.get_at(extruder_idx)); +} + +// Returns the number of object layers a region printing with the given 1-based filament id combines into one extrusion (1 = no combining). +// The configured height must be a (near) integer multiple of the object layer height, otherwise it is ignored here and rejected by Print::validate(). +unsigned int PrintObject::layer_height_multiplier_for_filament(unsigned int filament_id) const +{ + const double extruder_height = this->extruder_preferred_layer_height(filament_id); + if (extruder_height <= 0.) + return 1; + const double layer_height = m_config.layer_height.value; + if (layer_height <= EPSILON) + return 1; + const int multiplier = int(std::lround(extruder_height / layer_height)); + if (multiplier <= 1 || std::abs(extruder_height - multiplier * layer_height) > EPSILON) + // Not combining, or not an integer multiple (the latter is rejected by Print::validate()). + return 1; + // Same filament -> extruder mapping as extruder_preferred_layer_height() and Print::validate(). + const PrintConfig &print_config = m_print->config(); + const size_t extruder_idx = size_t((filament_id == 0 ? 1 : filament_id) - 1); + if (extruder_height > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + // Taller than the nozzle can print, rejected by Print::validate(). + return 1; + return (unsigned int)multiplier; +} + +unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®ion) const +{ + const PrintRegionConfig &config = region.config(); + // The wall filaments define a region's layer pitch (sparse infill preferring taller layers is combined separately in combine_infill()); wall-less regions need agreement of all their feature filaments. + // Disagreeing filaments keep the object layer height (Print::validate() warns). + std::vector filaments; // 0-based filament indices + if (config.wall_loops.value > 0) { + const int num_filaments = (int)m_print->config().filament_diameter.size(); + bool has_mixed_wall = false; + auto emplace_filament = [num_filaments, &filaments, &has_mixed_wall](int filament_id) { + int i = std::max(0, filament_id - 1); + if (i >= num_filaments) { + // Snapmaker mixed filament: virtual mixed ids resolve to a different physical + // extruder per layer, so such walls need every layer and must never combine + // (matches Print::validate() and extruder_preferred_layer_height()). + has_mixed_wall = true; + return; + } + filaments.emplace_back(i); + }; + emplace_filament(config.outer_wall_filament_id.value); + if (config.wall_loops.value > 1) + emplace_filament(config.inner_wall_filament_id.value); + if (has_mixed_wall) + return 1; + } else + // The brim is not considered, it prints on the first layer which is never combined. + PrintRegion::collect_object_printing_extruders(m_print->config(), config, false /* has_brim */, filaments); + unsigned int multiplier = 0; + for (unsigned int filament : filaments) { + unsigned int m = this->layer_height_multiplier_for_filament(filament + 1); + if (multiplier == 0) + multiplier = m; + else if (m != multiplier) + return 1; + } + if (multiplier <= 1) + return 1; + // A feature filament whose nozzle or layer height limits cannot follow the walls' pitch makes the region fall back to the object layer height (Print::validate() warns about it). + const PrintConfig &print_config = m_print->config(); + const double pitch = multiplier * m_config.layer_height.value; + std::vector used_filaments; // 0-based filament indices + PrintRegion::collect_object_printing_extruders(print_config, config, false /* has_brim */, used_filaments); + for (unsigned int filament : used_filaments) { + // The filament index is the extruder index on classic multi-tool printers. + const size_t extruder_idx = size_t(filament); + if (pitch > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + return 1; + if (const double max_lh = print_config.max_layer_height.get_at(extruder_idx); max_lh > EPSILON && pitch > max_lh + EPSILON) + return 1; + if (const double min_lh = print_config.min_layer_height.get_at(extruder_idx); min_lh > EPSILON && pitch < min_lh - EPSILON) + return 1; + } + return multiplier; +} + +bool PrintObject::has_combined_layer_regions() const +{ + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) + if (this->region_layer_height_multiplier(this->printing_region(region_id)) > 1) + return true; + return false; +} + +bool PrintObject::support_filament_allowed(unsigned int filament_id) const +{ + const double restriction = m_config.support_nozzle_diameter.value; + if (restriction <= 0. || filament_id == 0) + return true; + return std::abs(m_print->config().nozzle_diameter.get_at(filament_id - 1) - restriction) < EPSILON; +} + +unsigned int PrintObject::resolved_default_support_filament() const +{ + if (m_config.support_nozzle_diameter.value <= 0.) + return 0; + const PrintConfig &print_config = m_print->config(); + const size_t num_filaments = std::max(print_config.nozzle_diameter.values.size(), + print_config.filament_diameter.values.size()); + unsigned int soluble_fallback = 0; + for (size_t i = 0; i < num_filaments; ++ i) + if (this->support_filament_allowed((unsigned int)(i + 1))) { + if (! print_config.filament_soluble.get_at(i)) + return (unsigned int)(i + 1); + if (soluble_fallback == 0) + soluble_fallback = (unsigned int)(i + 1); + } + return soluble_fallback; +} + namespace { struct LayerHeightRangeOverride { @@ -4038,6 +4291,9 @@ void PrintObject::clip_fill_surfaces() for (LayerRegion *layerm : lower_layer->m_regions) { if (layerm->region().config().sparse_infill_density.value == 0) continue; + if (layerm->combined_layer_count() > 1) + // Per-extruder layer height: a combined group's infill supports the whole group, don't clip it against the single layer above. + continue; Polygons internal; for (Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) @@ -4061,6 +4317,9 @@ void PrintObject::discover_horizontal_shells() BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + // ORCA: per-extruder layer height. Combined regions extrude only on the top layers of their + // groups; the empty layers in between must neither receive shells nor count as shell layers. + const bool region_combined = this->region_layer_height_multiplier(this->printing_region(region_id)) > 1; for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); Layer *layer = m_layers[i]; @@ -4076,7 +4335,8 @@ void PrintObject::discover_horizontal_shells() } // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). - if (region_config.ensure_vertical_shell_thickness.value == evstAll) + // ORCA: combined regions are skipped by discover_vertical_shells() and handled here instead. + if (region_config.ensure_vertical_shell_thickness.value == evstAll && ! region_combined) continue; coordf_t print_z = layer->print_z; @@ -4113,17 +4373,26 @@ void PrintObject::discover_horizontal_shells() // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom'; // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. + // ORCA: per-extruder layer height. n_region_layers counts only layers the region + // prints on (equals the int(i) - n index distance when nothing is skipped), so + // shells of combined regions propagate across group tops. + int n_region_layers = 0; for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1; (type == stTop) ? - (n >= 0 && (int(i) - n < num_solid_layers || + (n >= 0 && (n_region_layers + 1 < num_solid_layers || print_z - m_layers[n]->print_z < region_config.top_shell_thickness.value - EPSILON)) : - (n < int(m_layers.size()) && (n - int(i) < num_solid_layers || + (n < int(m_layers.size()) && (n_region_layers + 1 < num_solid_layers || m_layers[n]->bottom_z() - bottom_z < region_config.bottom_shell_thickness.value - EPSILON)); (type == stTop) ? -- n : ++ n) { // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id]; + // ORCA: combined-away layers (count == 0) print nothing here: no shells, not + // counted. Genuinely absent geometry (count == 1, empty slices) still counts. + if (region_combined && neighbor_layerm->combined_layer_count() == 0) + continue; + ++ n_region_layers; // find intersection between neighbor and current layer's surfaces // intersections have contours and holes @@ -4274,7 +4543,21 @@ void PrintObject::combine_infill() const PrintRegion ®ion = this->printing_region(region_id); //BBS const bool enable_combine_infill = region.config().infill_combination.value; - if (enable_combine_infill == false || region.config().sparse_infill_density == 0.) + // Per-extruder layer height: infill whose filament prefers a taller layer height is combined up to it, with or without the infill_combination option. + // Only for regions printing at the object layer height; combined-wall regions already print everything over their runs. + if (this->region_layer_height_multiplier(region) > 1) + continue; + // At 100% density the combined surfaces are internal solid infill (see use_solid_infill + // below), but this fork prints them with the sparse infill filament + // (internal_solid_infill_uses_sparse_filament in PrintRegion::extruder()), so the sparse + // filament's preference and limits decide in both cases. + const int combine_filament_id = std::max(1, region.config().sparse_infill_filament_id.value); + // Per-extruder vector index: the filament index is the extruder index on classic multi-tool printers (like extruder_preferred_layer_height()). + const size_t combine_extruder_idx = size_t(combine_filament_id - 1); + double preferred_infill_height = this->extruder_preferred_layer_height((unsigned int)combine_filament_id); + if (preferred_infill_height <= m_config.layer_height.value + EPSILON) + preferred_infill_height = 0.; + if ((enable_combine_infill == false && preferred_infill_height == 0.) || region.config().sparse_infill_density == 0.) continue; // Support internal solid infill when sparse_infill_density is 100% @@ -4285,14 +4568,26 @@ void PrintObject::combine_infill() // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. //FIXME limit the layer height to max_layer_height - double nozzle_diameter = this->print()->config().nozzle_diameter.get_at(region.config().wall_filament.value - 1); - nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1)); - nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1)); + double nozzle_diameter = this->print()->config().nozzle_diameter.get_at(region.config().outer_wall_filament_id.value - 1); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().inner_wall_filament_id.value - 1)); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1)); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1)); //Orca: Limit combination of infill to up to infill_combination_max_layer_height const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter); nozzle_diameter = infill_combination_max_layer_height > 0 ? std::min(infill_combination_max_layer_height, nozzle_diameter) : nozzle_diameter; - + + // Per-extruder layer height: the filament's preferred height is an explicit target that overrides the caps above, limited only by the physical nozzle diameters and the extruder's max layer height. + // Plain infill_combination keeps its historical cap untouched. + if (preferred_infill_height > 0.) { + nozzle_diameter = std::min({preferred_infill_height, + this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1), + this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1)}); + const double combine_max_layer_height = this->print()->config().max_layer_height.get_at(combine_extruder_idx); + if (combine_max_layer_height > EPSILON) + nozzle_diameter = std::min(nozzle_diameter, combine_max_layer_height); + } + // define the combinations std::vector combine(m_layers.size(), 0); { @@ -4387,6 +4682,107 @@ void PrintObject::combine_infill() } } } + + // Per-extruder layer height: leftovers the pass above could not combine to the full group height print at the object layer height. + // Where that is below the infill extruder's min layer height, re-group them over the smallest layer count reaching the minimum (isolated leftovers with no matching neighbors stay as-is; Print::validate() warns). + const double combine_min_layer_height = this->print()->config().min_layer_height.get_at(combine_extruder_idx); + const double min_window = combine_min_layer_height > 0. ? + std::ceil(combine_min_layer_height / m_config.layer_height.value - EPSILON) * m_config.layer_height.value : 0.; + // Re-grouped windows must stay within the main pass' caps (a min_layer_height above them is + // a misconfiguration). + if (preferred_infill_height > 0. && combine_min_layer_height > m_config.layer_height.value + EPSILON && + min_window <= nozzle_diameter + EPSILON) { + // Assign leftover windows exactly like the pass above, just with the shorter target. + std::vector recombine(m_layers.size(), 0); + { + double current_height = 0.; + size_t num_layers = 0; + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + m_print->throw_if_canceled(); + const Layer *layer = m_layers[layer_idx]; + if (layer->id() == 0) + // Skip first print layer (which may not be first layer in array because of raft). + continue; + if (current_height + layer->height >= min_window + EPSILON) { + recombine[layer_idx - 1] = num_layers; + current_height = 0.; + num_layers = 0; + } + current_height += layer->height; + ++ num_layers; + } + recombine[m_layers.size() - 1] = num_layers; + } + // Leftovers are surfaces of the combined type whose thickness is still unset. + auto leftover_expolygons = [surface_type](const LayerRegion *layerm) { + ExPolygons out; + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == surface_type && surface.thickness < 0.) + out.emplace_back(surface.expolygon); + return out; + }; + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + m_print->throw_if_canceled(); + size_t num_layers = recombine[layer_idx]; + if (num_layers <= 1) + continue; + std::vector layerms; + layerms.reserve(num_layers); + for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i) + layerms.emplace_back(m_layers[i]->regions()[region_id]); + ExPolygons intersection = leftover_expolygons(layerms.front()); + for (size_t i = 1; i < layerms.size() && ! intersection.empty(); ++ i) + intersection = intersection_ex(leftover_expolygons(layerms[i]), intersection); + double area_threshold = layerms.front()->infill_area_threshold(); + if (! intersection.empty() && area_threshold > 0.) + intersection.erase(std::remove_if(intersection.begin(), intersection.end(), + [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), + intersection.end()); + if (intersection.empty()) + continue; + // Same clearance as the pass above: the leftover fills also grow to overlap perimeters. + Polygons intersection_with_clearance; + intersection_with_clearance.reserve(intersection.size()); + float clearance_offset = + 0.5f * layerms.back()->flow(frPerimeter).scaled_width() + + ((infill_pattern == ipRectilinear || + infill_pattern == ipMonotonic || + infill_pattern == ipGrid || + infill_pattern == ipLateralLattice|| + infill_pattern == ipLine || + infill_pattern == ipHoneycomb || + infill_pattern == ipLateralHoneycomb) ? 1.5f : 0.5f) * + layerms.back()->flow(frSolidInfill).scaled_width(); + for (ExPolygon &expoly : intersection) + polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); + for (LayerRegion *layerm : layerms) { + // Take out only the leftover surfaces; surfaces combined above keep their heights. + Polygons leftover; + Surfaces kept; + kept.reserve(layerm->fill_surfaces.surfaces.size()); + for (Surface &surface : layerm->fill_surfaces.surfaces) { + if (surface.surface_type == surface_type && surface.thickness < 0.) + polygons_append(leftover, to_polygons(std::move(surface.expolygon))); + else + kept.emplace_back(std::move(surface)); + } + layerm->fill_surfaces.surfaces = std::move(kept); + layerm->fill_surfaces.append(diff_ex(leftover, intersection_with_clearance), surface_type); + if (layerm == layerms.back()) { + Surface templ(surface_type, ExPolygon()); + templ.thickness = 0.; + for (LayerRegion *layerm2 : layerms) + templ.thickness += layerm2->layer()->height; + templ.thickness_layers = (unsigned short)layerms.size(); + layerm->fill_surfaces.append(intersection, templ); + } else { + layerm->fill_surfaces.append( + intersection_ex(leftover, intersection_with_clearance), + stInternalVoid); + } + } + } + } } } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 688c0f2aee4..686916bae39 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -848,6 +848,11 @@ void PrintObject::slice() // BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim groupingVolumesForBrim(this, m_layers, firstLayerReplacedBy); + // Per-extruder layer height: combine region slices into every Nth layer where geometry allows. + // Must run before backup_untyped_slices() below so the combined slices survive the restore_untyped_slices*() calls in make_perimeters() / prepare_infill(). + this->apply_extruder_layer_heights(); + m_print->throw_if_canceled(); + // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -869,6 +874,191 @@ void PrintObject::slice() this->set_done(posSlice); } +// ORCA: per-extruder layer height ("extruder_layer_height" printer option). +// For every region whose extruder prefers an integer multiple N (> 1) of the object layer height, +// greedily combine bottom-up runs of up to N layers on which the region keeps a (nearly) identical +// shape, merging the run's slices into its top layer to be extruded once at the run's full height +// (see LayerRegion::combined_height()); combined-away layers carry no slices for the region and +// trigger no toolchange. A run requires near-identical slices, uniform layer heights and full support +// from below (no overhang / bridge hidden inside a run). Only full runs plus the clean cap of a +// column ending above are committed, so each region prints at two consistent heights rather than +// ever-changing intermediate bands. Region tops / bottoms and overhangs keep the finer base layers. +void PrintObject::apply_extruder_layer_heights() +{ + if (m_layers.size() < 2 || this->num_printing_regions() == 0) + return; + std::vector multipliers(this->num_printing_regions(), 1); + bool any_combined = false; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + multipliers[region_id] = this->region_layer_height_multiplier(this->printing_region(region_id)); + any_combined |= multipliers[region_id] > 1; + } + if (! any_combined) + return; + if (m_print->config().spiral_mode || m_config.interface_shells) + // These combinations are rejected by Print::validate(), fail safe here. + return; + + BOOST_LOG_TRIVIAL(debug) << "Combining region slices to extruder layer heights for " << this->model_object()->name; + + // Never combine the first printed layer: it keeps its own height for bed adhesion (mirrors the + // id() == 0 exclusion in PrintObject::combine_infill()), and with a raft detect_surfaces_type() + // needs its slices to seed the object's bottom surfaces. + const size_t first_idx = 1; + if (m_layers.size() <= first_idx + 1) + return; + + // Adaptive mode commits runs cut short by shape drift at intermediate heights instead of + // falling back to the object layer height. + const bool adaptive = m_config.extruder_layer_height_mode.value == elhmAdaptive; + // The filament index is the extruder index on classic multi-tool printers (same mapping as + // extruder_preferred_layer_height() and Print::validate()). + const PrintConfig &print_config = m_print->config(); + auto extruder_idx_of = [](int filament_id) { + return size_t((unsigned int)std::max(1, filament_id) - 1); + }; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const size_t mult = multipliers[region_id]; + if (mult <= 1) + continue; + // Shapes deviating by less than this fraction of the region's nozzle diameter are considered + // identical; the deviations swallowed stay below what printing N layers at once causes anyway. + const double nozzle_diameter = print_config.nozzle_diameter.get_at( + extruder_idx_of(this->printing_region(region_id).config().outer_wall_filament_id.value)); + const float tolerance = float(scale_(m_config.extruder_layer_height_tolerance.get_abs_value(nozzle_diameter))); + // Where geometry would fall back below the extruders' minimum layer height, runs of at least + // min_run layers are forced instead, ignoring the shape tolerance (the nozzle cannot print + // finer). Both wall filaments print fallback runs, so the coarser of their minimums decides. + const PrintRegionConfig ®ion_config = this->printing_region(region_id).config(); + double min_layer_height = print_config.min_layer_height.get_at( + extruder_idx_of(region_config.outer_wall_filament_id.value)); + if (region_config.wall_loops.value > 1) + min_layer_height = std::max(min_layer_height, print_config.min_layer_height.get_at( + extruder_idx_of(region_config.inner_wall_filament_id.value))); + size_t min_run = 1; + if (min_layer_height > m_config.layer_height.value + EPSILON) + min_run = std::min(mult, (size_t)std::ceil(min_layer_height / m_config.layer_height.value - EPSILON)); + size_t idx = first_idx; + while (idx < m_layers.size()) { + m_print->throw_if_canceled(); + ExPolygons merged = to_expolygons(m_layers[idx]->regions()[region_id]->slices.surfaces); + if (merged.empty()) { + // The region does not exist at this layer. + ++ idx; + continue; + } + // Grow the run upwards while nothing sticks out of the run's common shape by more than + // the tolerance. Uniform layer heights only (variable heights are rejected by Print::validate()). + Polygons unioned = to_polygons(merged); + size_t top_idx = idx; + bool shape_drifted = false; + while (top_idx + 1 < m_layers.size() && top_idx + 1 - idx < mult) { + const size_t next = top_idx + 1; + if (std::abs(m_layers[next]->height - m_layers[idx]->height) > EPSILON) + break; + const ExPolygons expolys = to_expolygons(m_layers[next]->regions()[region_id]->slices.surfaces); + if (expolys.empty()) + // The region ends above, the run is the clean cap of its column. + break; + ExPolygons next_merged = intersection_ex(expolys, merged); + if (next_merged.empty()) + // Laterally displaced (a painted-boundary step): this column ends, the next anchors above. + break; + Polygons next_unioned = unioned; + polygons_append(next_unioned, to_polygons(expolys)); + if (! opening(diff(union_(next_unioned), offset(next_merged, tolerance)), 0.5f * tolerance).empty()) { + shape_drifted = true; + break; + } + merged = std::move(next_merged); + unioned = std::move(next_unioned); + top_idx = next; + } + // Full runs and clean caps commit as grown; a run cut short by shape drift falls back to + // the object layer height (unless adaptive) to avoid bands of ever-changing heights on + // curved boundaries. The fallback is raised to min_run where required. + const size_t grown = top_idx + 1 - idx; + size_t commit_length; + if (grown == mult || ! shape_drifted) + commit_length = grown; + else if (adaptive) + commit_length = std::max(grown, min_run); + else + commit_length = min_run; + if (commit_length >= 2 && min_run > 1) { + // Don't leave a remainder shorter than min_run above: shorten so the next run can reach it. + size_t above = 0; + for (size_t i = idx + commit_length; i < m_layers.size() && above < min_run; ++ i) { + if (m_layers[i]->regions()[region_id]->slices.empty()) + break; + ++ above; + } + if (above > 0 && above < min_run) { + const size_t shift = min_run - above; + if (commit_length >= min_run + shift && commit_length - shift >= 2) + commit_length -= shift; + } + } + if (commit_length < 2) { + // Print this layer with the object layer height. + ++ idx; + continue; + } + size_t commit_top = std::min(idx + commit_length - 1, m_layers.size() - 1); + if (commit_top != top_idx) { + // Forced or shortened run: recompute its shape without the tolerance check, + // shrinking the range where the region ends or jumps. + merged = to_expolygons(m_layers[idx]->regions()[region_id]->slices.surfaces); + for (size_t i = idx + 1; i <= commit_top; ++ i) { + if (std::abs(m_layers[i]->height - m_layers[idx]->height) > EPSILON) { + commit_top = i - 1; + break; + } + const ExPolygons expolys = to_expolygons(m_layers[i]->regions()[region_id]->slices.surfaces); + ExPolygons next_merged = expolys.empty() ? ExPolygons() : intersection_ex(expolys, merged); + if (next_merged.empty()) { + commit_top = i - 1; + break; + } + merged = std::move(next_merged); + } + if (commit_top + 1 - idx < 2) { + // Nothing to force here, the object layer height is the last resort. + ++ idx; + continue; + } + } + // The run must rest on the object below, else the finer per-layer bridge / overhang path + // is needed. Skipped when min_run > 1: no finer path exists then, overhangs are detected + // against the layer below the whole run instead. + if (min_run <= 1) + if (const Layer *below = m_layers[idx]->lower_layer; below != nullptr && + ! opening_ex(diff_ex(merged, below->lslices, ApplySafetyOffset::Yes), 0.5f * tolerance).empty()) { + ++ idx; + continue; + } + // Commit: move the common shape to the top layer of the run, drop the layers below. + double combined_height = 0.; + for (size_t i = idx; i <= commit_top; ++ i) + combined_height += m_layers[i]->height; + LayerRegion *top_layerm = m_layers[commit_top]->regions()[region_id]; + top_layerm->slices.set(std::move(merged), stInternal); + top_layerm->m_combined_layer_count = (unsigned short)(commit_top - idx + 1); + top_layerm->m_combined_height = combined_height; + for (size_t i = idx; i < commit_top; ++ i) { + LayerRegion *combined_away = m_layers[i]->regions()[region_id]; + combined_away->slices.clear(); + // 0 marks "extrudes at the run top above", as opposed to genuinely absent geometry. + combined_away->m_combined_layer_count = 0; + } + // Do not touch Layer::lslices here: they describe the final object and keep driving + // top / bottom detection of the other regions, brim, supports and overhang handling. + idx = commit_top + 1; + } + m_print->throw_if_canceled(); + } +} + static bool bool_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, bool fallback) { if (!full_cfg.has(key)) @@ -2575,7 +2765,7 @@ static bool apply_mixed_region_surface_offsets(PrintObject &print_object) if (layerm == nullptr || layerm->slices.empty()) continue; - const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value)); + const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().outer_wall_filament_id.value)); if (!mixed_mgr.is_mixed(filament_id, num_physical)) continue; @@ -2769,7 +2959,7 @@ static std::vector> whole_object_local_z_segmentation_by if (layerm == nullptr || layerm->slices.empty()) continue; - const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value)); + const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().outer_wall_filament_id.value)); if (!mixed_mgr.is_mixed(filament_id, num_physical)) continue; if (filament_id >= segmentation[layer_id].size()) @@ -4469,7 +4659,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector< 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; + if (const int cfg_wall = parent_print_region.config().outer_wall_filament_id.value; cfg_wall >= 1 && cfg_wall <= int(by_extruder.size())) self_extruder_id = cfg_wall; if (clamp_parent_to_geometry && default_bbox.defined && parent_layer_region_bbox.overlap(default_bbox)) @@ -4732,7 +4922,7 @@ static std::vector collect_surface_emboss_mixe 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)) + if (!mixed_mgr.is_mixed(unsigned(std::max(0, volume_region.region->config().outer_wall_filament_id.value)), num_physical)) continue; out.push_back({ volume, region_id }); @@ -4767,7 +4957,7 @@ static void export_surface_emboss_mixed_layer_svg( }) != candidates.end(); SVG::ExPolygonAttributes attrs( - "region " + std::to_string(region_id) + " wall=" + std::to_string(layerm->region().config().wall_filament.value), + "region " + std::to_string(region_id) + " wall=" + std::to_string(layerm->region().config().outer_wall_filament_id.value), is_candidate ? "#3b82f6" : "#bfc5cc", is_candidate ? 0.35f : 0.14f); attrs.outline_width = scale_(0.05f); @@ -4829,9 +5019,9 @@ static void dump_surface_emboss_mixed_layer_state( 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 + << " wall=" << layerm->region().config().outer_wall_filament_id.value + << " sparse=" << layerm->region().config().sparse_infill_filament_id.value + << " solid=" << layerm->region().config().internal_solid_filament_id.value << " area=" << slice_area; append_surface_emboss_mixed_debug_line(print_object, line.str()); } @@ -4848,14 +5038,14 @@ static void dump_surface_emboss_mixed_layer_state( << " candidate region=" << candidate.region_id << " volume_name=" << candidate.volume->name << " volume_extruder=" << candidate.volume->extruder_id() - << " cfg_wall=" << layerm->region().config().wall_filament.value + << " cfg_wall=" << layerm->region().config().outer_wall_filament_id.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; + const int cfg_wall = layerm->region().config().outer_wall_filament_id.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; @@ -4949,7 +5139,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object continue; processed_region_ids.emplace_back(region_id); - const unsigned int filament_id = unsigned(std::max(0, volume_region.region->config().wall_filament.value)); + const unsigned int filament_id = unsigned(std::max(0, volume_region.region->config().outer_wall_filament_id.value)); if (!mixed_mgr.is_mixed(filament_id, num_physical)) continue; @@ -4982,7 +5172,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object << " region=" << region_id << " volume_name=" << volume->name << " volume_extruder=" << volume->extruder_id() - << " cfg_wall=" << volume_region.region->config().wall_filament.value + << " cfg_wall=" << volume_region.region->config().outer_wall_filament_id.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)); @@ -5007,7 +5197,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object 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)) + if (target_layerm->region().config().outer_wall_filament_id.value == int(filament_id)) continue; ExPolygons stolen = intersection_ex(target_layerm->slices.surfaces, override_mask); @@ -5024,7 +5214,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object << " layer=" << layer_id << " emboss_region=" << region_id << " from_region=" << target_region_id - << " from_wall=" << target_layerm->region().config().wall_filament.value + << " from_wall=" << target_layerm->region().config().outer_wall_filament_id.value << " stolen_area=" << std::abs(area(stolen)); append_surface_emboss_mixed_debug_line(print_object, line.str()); diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index f4b82faf4ab..a6d842ed74c 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -18,20 +18,22 @@ bool internal_solid_infill_uses_sparse_filament(const PrintRegionConfig &config, unsigned int PrintRegion::extruder(FlowRole role) const { size_t extruder = 0; - if (role == frPerimeter || role == frExternalPerimeter) - extruder = m_config.wall_filament; + if (role == frPerimeter) + extruder = m_config.inner_wall_filament_id; + else if (role == frExternalPerimeter) + extruder = m_config.outer_wall_filament_id; else if (role == frInfill) - extruder = m_config.sparse_infill_filament; + extruder = m_config.sparse_infill_filament_id; else if (role == frSolidInfill) - extruder = internal_solid_infill_uses_sparse_filament(m_config, role) ? m_config.sparse_infill_filament : m_config.solid_infill_filament; + extruder = internal_solid_infill_uses_sparse_filament(m_config, role) ? m_config.sparse_infill_filament_id : m_config.internal_solid_filament_id; else if (role == frTopSolidInfill) - extruder = m_config.solid_infill_filament; + extruder = m_config.top_surface_filament_id; else throw Slic3r::InvalidArgument("Unknown role"); return extruder; } -Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const +Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer, unsigned int filament_id) const { const PrintConfig &print_config = object.print()->config(); ConfigOptionFloatOrPercent config_width; @@ -56,17 +58,21 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he if (config_width.value == 0) config_width = object.config().line_width; - // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Get the configured nozzle_diameter for the extruder associated to the flow role requested, + // or for the explicitly given filament when it differs from the role's default mapping (top / bottom surface fills). // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1)); + auto nozzle_diameter = float(print_config.nozzle_diameter.get_at((filament_id > 0 ? filament_id : this->extruder(role)) - 1)); return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const { - return (print_config.nozzle_diameter.get_at(m_config.wall_filament.value - 1) + - print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament.value - 1) + - print_config.nozzle_diameter.get_at(m_config.solid_infill_filament.value - 1)) / 3.; + return (print_config.nozzle_diameter.get_at(m_config.outer_wall_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.inner_wall_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.internal_solid_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.top_surface_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.bottom_surface_filament_id.value - 1)) / 6.; } coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const @@ -83,12 +89,19 @@ void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_con int i = std::max(0, extruder_id - 1); object_extruders.emplace_back((i >= num_extruders) ? 0 : i); }; - if (region_config.wall_loops.value > 0 || has_brim) - emplace_extruder(region_config.wall_filament); + if (region_config.wall_loops.value > 0 || has_brim) { + emplace_extruder(region_config.outer_wall_filament_id); + if (region_config.wall_loops.value > 1) + emplace_extruder(region_config.inner_wall_filament_id); + } if (region_config.sparse_infill_density.value > 0) - emplace_extruder(region_config.sparse_infill_filament); - if (region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0) - emplace_extruder(region_config.solid_infill_filament); + emplace_extruder(region_config.sparse_infill_filament_id); + if (region_config.sparse_infill_density.value > 0 || region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0) + emplace_extruder(region_config.internal_solid_filament_id); + if (region_config.top_shell_layers.value > 0) + emplace_extruder(region_config.top_surface_filament_id); + if (region_config.bottom_shell_layers.value > 0) + emplace_extruder(region_config.bottom_surface_filament_id); } void PrintRegion::collect_object_printing_extruders(const Print &print, std::vector &object_extruders) const @@ -98,9 +111,12 @@ void PrintRegion::collect_object_printing_extruders(const Print &print, std::vec #ifndef NDEBUG // BBS auto num_extruders = int(print.config().filament_diameter.size()); - assert(this->config().wall_filament <= num_extruders); - assert(this->config().sparse_infill_filament <= num_extruders); - assert(this->config().solid_infill_filament <= num_extruders); + assert(this->config().outer_wall_filament_id <= num_extruders); + assert(this->config().inner_wall_filament_id <= num_extruders); + assert(this->config().sparse_infill_filament_id <= num_extruders); + assert(this->config().internal_solid_filament_id <= num_extruders); + assert(this->config().top_surface_filament_id <= num_extruders); + assert(this->config().bottom_surface_filament_id <= num_extruders); #endif collect_object_printing_extruders(print.config(), this->config(), print.has_brim(), object_extruders); } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index f3499b7d361..f8bc5600146 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -73,8 +73,16 @@ SlicingParameters SlicingParameters::create_from_config( // which is consistent with the requirement that if support_filament == 0 resp. support_interface_filament == 0, // support will not trigger tool change, but it will use the current nozzle instead. // In that case all the nozzles have to be of the same diameter. - coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_filament.value - 1); - coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_interface_filament.value - 1); + // Support_nozzle_diameter restricts which extruders / nozzle diameter may print support ("default" = no restriction) + auto restricted_default = [&object_config](int configured) { + return configured == 0 && object_config.support_nozzle_diameter.value > 0.; + }; + coordf_t support_material_extruder_dmr = restricted_default(object_config.support_filament.value) ? + object_config.support_nozzle_diameter.value : + print_config.nozzle_diameter.get_at(object_config.support_filament.value - 1); + coordf_t support_material_interface_extruder_dmr = restricted_default(object_config.support_interface_filament.value) ? + object_config.support_nozzle_diameter.value : + print_config.nozzle_diameter.get_at(object_config.support_interface_filament.value - 1); bool soluble_interface = object_config.support_top_z_distance.value == 0.; SlicingParameters params; @@ -94,12 +102,31 @@ SlicingParameters SlicingParameters::create_from_config( params.max_layer_height = std::numeric_limits::max(); if (object_config.enable_support.value || params.base_raft_layers > 0 || object_config.enforce_support_layers > 0) { // Has some form of support. Add the support layers to the minimum / maximum layer height limits. + auto support_layer_height_limit = [&print_config, &object_config, &restricted_default](int configured, bool min_limit) -> coordf_t { + auto from_nozzle = [&print_config, min_limit](int filament) { + return min_limit ? min_layer_height_from_nozzle(print_config, filament) : max_layer_height_from_nozzle(print_config, filament); + }; + if (restricted_default(configured)) { + // Combine the limits of all filaments the restriction allows. + coordf_t limit = min_limit ? 0. : std::numeric_limits::max(); + bool found = false; + for (size_t i = 0; i < print_config.nozzle_diameter.values.size(); ++ i) + if (std::abs(print_config.nozzle_diameter.values[i] - object_config.support_nozzle_diameter.value) < EPSILON) { + coordf_t l = from_nozzle(int(i + 1)); + limit = min_limit ? std::max(limit, l) : std::min(limit, l); + found = true; + } + if (found) + return limit; + } + return from_nozzle(configured); + }; params.min_layer_height = std::max( - min_layer_height_from_nozzle(print_config, object_config.support_filament), - min_layer_height_from_nozzle(print_config, object_config.support_interface_filament)); + support_layer_height_limit(object_config.support_filament.value, true), + support_layer_height_limit(object_config.support_interface_filament.value, true)); params.max_layer_height = std::min( - max_layer_height_from_nozzle(print_config, object_config.support_filament), - max_layer_height_from_nozzle(print_config, object_config.support_interface_filament)); + support_layer_height_limit(object_config.support_filament.value, false), + support_layer_height_limit(object_config.support_interface_filament.value, false)); params.max_suport_layer_height = params.max_layer_height; } if (object_extruders.empty()) { diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 0de4067770a..903e9a9614a 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -55,7 +55,7 @@ void remove_bridges_from_contacts( // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. Polygons lower_grown_slices = expand(lower_layer.lslices, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament - 1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id - 1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 7580976ac99..e83981f9ac0 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1224,7 +1224,7 @@ namespace SupportMaterialInternal { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. Polygons lower_grown_slices = expand(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way @@ -2149,7 +2149,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // check if the sharp tails should be extended higher bool detect_first_sharp_tail_only = false; - const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1)); + const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(support_material_nozzle_diameter(&object, object.config().support_interface_filament)); const coordf_t extrusion_width_scaled = scale_(extrusion_width); if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { for (size_t layer_nr = layer_id_start; layer_nr < num_layers; layer_nr++) { diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 655ee0d8060..8d34dee9413 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -158,7 +158,8 @@ struct SupportParameters { assert(slicing_params.raft_layers() == 0); } - const auto nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_interface_filament - 1); + // ORCA: honors the support nozzle diameter restriction for "default" support filaments. + const auto nozzle_diameter = support_material_nozzle_diameter(&object, object_config.support_interface_filament); const coordf_t extrusion_width = object_config.line_width.get_abs_value(nozzle_diameter); support_extrusion_width = object_config.support_line_width.get_abs_value(nozzle_diameter); support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 17e7fb044f0..352e46b44b3 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1319,7 +1319,7 @@ void TreeSupport::generate_toolpaths() { const PrintObjectConfig &object_config = m_object->config(); coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(object_config.support_filament - 1); + coordf_t nozzle_diameter = support_material_nozzle_diameter(m_object, object_config.support_filament); coordf_t layer_height = object_config.layer_height.value; const size_t wall_count = object_config.tree_support_wall_count.value; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 11c2bd16482..d9d106bea03 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -557,9 +557,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field(el, have_perimeters); bool have_infill = config->option("sparse_infill_density")->value > 0; - // sparse_infill_filament uses the same logic as in Print::extruders() + // sparse_infill_filament_id uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", - "minimum_sparse_infill_area", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) + "minimum_sparse_infill_area", "sparse_infill_filament_id", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) toggle_line(el, have_infill); bool have_combined_infill = config->opt_bool("infill_combination") && have_infill; @@ -614,7 +614,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : { "infill_direction", "sparse_infill_line_width", "fill_multiline","gap_fill_target","filter_out_gap_fill","infill_wall_overlap", "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle", - "solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "solid_infill_filament", + "solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", }) toggle_field(el, have_infill || has_solid_infill); @@ -653,8 +653,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim && config->opt_enum("brim_type") != btPainted; toggle_field("brim_width", have_brim_width); - // wall_filament uses the same logic as in Print::extruders() - toggle_field("wall_filament", have_perimeters || have_brim); + // Wall filament selectors use the same logic as in Print::extruders(). + toggle_field("outer_wall_filament_id", have_perimeters || have_brim); + toggle_field("inner_wall_filament_id", have_perimeters || have_brim); bool have_brim_ear = (config->opt_enum("brim_type") == btEar); const auto brim_width = config->opt_float("brim_width"); @@ -730,6 +731,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("inner_wall_line_width", have_perimeters || have_skirt || have_brim); toggle_field("support_filament", have_support_material || have_skirt); + // ORCA: support_nozzle_diameter only applies to printers whose extruders have differing nozzle diameters. + bool mixed_nozzle_sizes = false; + if (const auto *nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); + nozzle_diameters != nullptr && ! nozzle_diameters->values.empty()) + for (double d : nozzle_diameters->values) + mixed_nozzle_sizes |= std::abs(d - nozzle_diameters->values.front()) > EPSILON; + toggle_line("support_nozzle_diameter", have_support_material && mixed_nozzle_sizes); + toggle_line("raft_contact_distance", have_raft && !have_support_soluble); // Orca: Raft, grid, snug and organic supports use these two parameters to control the size & density of the "brim"/flange @@ -760,9 +769,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : { "prime_tower_width", "prime_tower_brim_width"}) toggle_line(el, have_prime_tower); - for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"}) - toggle_line(el, !bSEMM); - bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower"); for (auto el : {"wipe_tower_cone_angle", diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 87a6d909cb5..5ef5b1846b6 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -146,7 +146,7 @@ std::map> SettingsFactory::PART_CAT }}, { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},{"top_surface_density", L("Top Surface Density"),1}, {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},{"bottom_surface_density", L("Bottom Surface Density"),1}, - {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"sparse_infill_filament", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, + {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"sparse_infill_filament_id", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, {"align_infill_direction_to_model", "", 1}, {"extra_solid_infills", "", 1}, {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b1130ea33c1..be251b4a450 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -701,7 +701,8 @@ void ObjectList::update_filament_values_for_items(const size_t filaments_count) } m_objects_model->SetExtruder(extruder, item); - static const char *keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + static const char *keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament"}; for (auto key : keys) if (object->config.has(key) && object->config.opt_int(key) > filaments_count) @@ -877,7 +878,8 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz } m_objects_model->SetExtruder(extruder, item); - static const char* keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + static const char* keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament"}; for (auto key : keys) { if (object->config.has(key)) { @@ -5902,9 +5904,9 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) else config.set_key_value("extruder", new ConfigOptionInt(new_extruder)); - // config.set("sparse_infill_filament", new_extruder); - // config.set("solid_infill_filament", new_extruder); - // config.set("wall_filament", new_extruder); + // config.set("sparse_infill_filament_id", new_extruder); + // config.set("internal_solid_filament_id", new_extruder); + // config.set("outer_wall_filament_id", new_extruder); wxGetApp().obj_list()->update_selections(); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 435b72d0933..87142ca5a41 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -80,7 +80,7 @@ namespace { template int resolve_sparse_infill_filament(const ConfigLike &config, int inherited_sparse_infill_filament) { - const ConfigOption *sparse_opt = config.option("sparse_infill_filament"); + const ConfigOption *sparse_opt = config.option("sparse_infill_filament_id"); return sparse_opt != nullptr && sparse_opt->getInt() > 0 ? sparse_opt->getInt() : inherited_sparse_infill_filament; } @@ -1370,9 +1370,16 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const const DynamicPrintConfig& glb_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; int glb_support_intf_extr = glb_config.opt_int("support_interface_filament"); int glb_support_extr = glb_config.opt_int("support_filament"); - int glb_wall_extr = glb_config.opt_int("wall_filament"); - int glb_sparse_infill_extr = glb_config.opt_int("sparse_infill_filament"); - int glb_solid_infill_extr = glb_config.opt_int("solid_infill_filament"); + int glb_outer_wall_extr = glb_config.opt_int("outer_wall_filament_id"); + int glb_inner_wall_extr = glb_config.opt_int("inner_wall_filament_id"); + if (glb_outer_wall_extr == 0) glb_outer_wall_extr = glb_inner_wall_extr; + if (glb_inner_wall_extr == 0) glb_inner_wall_extr = glb_outer_wall_extr; + int glb_sparse_infill_extr = glb_config.opt_int("sparse_infill_filament_id"); + int glb_internal_solid_extr = glb_config.opt_int("internal_solid_filament_id"); + int glb_top_surface_extr = glb_config.opt_int("top_surface_filament_id"); + int glb_bottom_surface_extr = glb_config.opt_int("bottom_surface_filament_id"); + if (glb_top_surface_extr == 0) glb_top_surface_extr = glb_internal_solid_extr; + if (glb_bottom_surface_extr == 0) glb_bottom_surface_extr = glb_internal_solid_extr; bool glb_support = glb_config.opt_bool("enable_support"); glb_support |= glb_config.opt_int("raft_layers") > 0; @@ -1426,25 +1433,59 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const plate_extruders.push_back(glb_support_extr); } - int obj_wall_extr = glb_wall_extr; - const ConfigOption* wall_opt = mo->config.option("wall_filament"); - if (wall_opt != nullptr) - obj_wall_extr = wall_opt->getInt(); - if (obj_wall_extr != 1) - plate_extruders.push_back(obj_wall_extr); + int obj_outer_wall_extr = 0; + if (const ConfigOption* wall_opt = mo->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr == 0) + if (const ConfigOption* wall_opt = mo->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr != 0) + plate_extruders.push_back(obj_outer_wall_extr); + else if (glb_outer_wall_extr != 0) + plate_extruders.push_back(glb_outer_wall_extr); + + int obj_inner_wall_extr = 0; + if (const ConfigOption* wall_opt = mo->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr == 0) + if (const ConfigOption* wall_opt = mo->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr != 0) + plate_extruders.push_back(obj_inner_wall_extr); + else if (glb_inner_wall_extr != 0) + plate_extruders.push_back(glb_inner_wall_extr); const int object_sparse_infill_extr = resolve_sparse_infill_filament(mo->config, glb_sparse_infill_extr); - if (object_sparse_infill_extr != 1) + if (object_sparse_infill_extr != 0) plate_extruders.push_back(object_sparse_infill_extr); - int obj_solid_infill_extr = 1; - const ConfigOption* solid_infill_opt = mo->config.option("solid_infill_filament"); - if (solid_infill_opt != nullptr) - obj_solid_infill_extr = solid_infill_opt->getInt(); - if (obj_solid_infill_extr != 1) - plate_extruders.push_back(obj_solid_infill_extr); - else if (glb_solid_infill_extr != 1) - plate_extruders.push_back(glb_solid_infill_extr); + int obj_internal_solid_extr = 0; + if (const ConfigOption* solid_opt = mo->config.option("internal_solid_filament_id"); solid_opt != nullptr) + obj_internal_solid_extr = solid_opt->getInt(); + if (obj_internal_solid_extr != 0) + plate_extruders.push_back(obj_internal_solid_extr); + else if (glb_internal_solid_extr != 0) + plate_extruders.push_back(glb_internal_solid_extr); + + int obj_top_surface_extr = 0; + if (const ConfigOption* top_opt = mo->config.option("top_surface_filament_id"); top_opt != nullptr) + obj_top_surface_extr = top_opt->getInt(); + if (obj_top_surface_extr == 0) + obj_top_surface_extr = obj_internal_solid_extr; + if (obj_top_surface_extr != 0) + plate_extruders.push_back(obj_top_surface_extr); + else if (glb_top_surface_extr != 0) + plate_extruders.push_back(glb_top_surface_extr); + + int obj_bottom_surface_extr = 0; + if (const ConfigOption* bottom_opt = mo->config.option("bottom_surface_filament_id"); bottom_opt != nullptr) + obj_bottom_surface_extr = bottom_opt->getInt(); + if (obj_bottom_surface_extr == 0) + obj_bottom_surface_extr = obj_internal_solid_extr; + if (obj_bottom_surface_extr != 0) + plate_extruders.push_back(obj_bottom_surface_extr); + else if (glb_bottom_surface_extr != 0) + plate_extruders.push_back(glb_bottom_surface_extr); } @@ -1484,9 +1525,16 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D // if 3mf file int glb_support_intf_extr = full_config.opt_int("support_interface_filament"); int glb_support_extr = full_config.opt_int("support_filament"); - int glb_wall_extr = full_config.opt_int("wall_filament"); - int glb_sparse_infill_extr = full_config.opt_int("sparse_infill_filament"); - int glb_solid_infill_extr = full_config.opt_int("solid_infill_filament"); + int glb_outer_wall_extr = full_config.opt_int("outer_wall_filament_id"); + int glb_inner_wall_extr = full_config.opt_int("inner_wall_filament_id"); + if (glb_outer_wall_extr == 0) glb_outer_wall_extr = glb_inner_wall_extr; + if (glb_inner_wall_extr == 0) glb_inner_wall_extr = glb_outer_wall_extr; + int glb_sparse_infill_extr = full_config.opt_int("sparse_infill_filament_id"); + int glb_internal_solid_extr = full_config.opt_int("internal_solid_filament_id"); + int glb_top_surface_extr = full_config.opt_int("top_surface_filament_id"); + int glb_bottom_surface_extr = full_config.opt_int("bottom_surface_filament_id"); + if (glb_top_surface_extr == 0) glb_top_surface_extr = glb_internal_solid_extr; + if (glb_bottom_surface_extr == 0) glb_bottom_surface_extr = glb_internal_solid_extr; bool glb_support = full_config.opt_bool("enable_support"); glb_support |= full_config.opt_int("raft_layers") > 0; @@ -1570,25 +1618,59 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D else if (glb_support_extr != 0) plate_extruders.push_back(glb_support_extr); - int obj_wall_extr = glb_wall_extr; - const ConfigOption* wall_opt = object->config.option("wall_filament"); - if (wall_opt != nullptr) - obj_wall_extr = wall_opt->getInt(); - if (obj_wall_extr != 1) - plate_extruders.push_back(obj_wall_extr); + int obj_outer_wall_extr = 0; + if (const ConfigOption* wall_opt = object->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr == 0) + if (const ConfigOption* wall_opt = object->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr != 0) + plate_extruders.push_back(obj_outer_wall_extr); + else if (glb_outer_wall_extr != 0) + plate_extruders.push_back(glb_outer_wall_extr); + + int obj_inner_wall_extr = 0; + if (const ConfigOption* wall_opt = object->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr == 0) + if (const ConfigOption* wall_opt = object->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr != 0) + plate_extruders.push_back(obj_inner_wall_extr); + else if (glb_inner_wall_extr != 0) + plate_extruders.push_back(glb_inner_wall_extr); const int object_sparse_infill_extr = resolve_sparse_infill_filament(object->config, glb_sparse_infill_extr); - if (object_sparse_infill_extr != 1) + if (object_sparse_infill_extr != 0) plate_extruders.push_back(object_sparse_infill_extr); - int obj_solid_infill_extr = 1; - const ConfigOption* solid_infill_opt = object->config.option("solid_infill_filament"); - if (solid_infill_opt != nullptr) - obj_solid_infill_extr = solid_infill_opt->getInt(); - if (obj_solid_infill_extr != 1) - plate_extruders.push_back(obj_solid_infill_extr); - else if (glb_solid_infill_extr != 1) - plate_extruders.push_back(glb_solid_infill_extr); + int obj_internal_solid_extr = 0; + if (const ConfigOption* solid_opt = object->config.option("internal_solid_filament_id"); solid_opt != nullptr) + obj_internal_solid_extr = solid_opt->getInt(); + if (obj_internal_solid_extr != 0) + plate_extruders.push_back(obj_internal_solid_extr); + else if (glb_internal_solid_extr != 0) + plate_extruders.push_back(glb_internal_solid_extr); + + int obj_top_surface_extr = 0; + if (const ConfigOption* top_opt = object->config.option("top_surface_filament_id"); top_opt != nullptr) + obj_top_surface_extr = top_opt->getInt(); + if (obj_top_surface_extr == 0) + obj_top_surface_extr = obj_internal_solid_extr; + if (obj_top_surface_extr != 0) + plate_extruders.push_back(obj_top_surface_extr); + else if (glb_top_surface_extr != 0) + plate_extruders.push_back(glb_top_surface_extr); + + int obj_bottom_surface_extr = 0; + if (const ConfigOption* bottom_opt = object->config.option("bottom_surface_filament_id"); bottom_opt != nullptr) + obj_bottom_surface_extr = bottom_opt->getInt(); + if (obj_bottom_surface_extr == 0) + obj_bottom_surface_extr = obj_internal_solid_extr; + if (obj_bottom_surface_extr != 0) + plate_extruders.push_back(obj_bottom_surface_extr); + else if (glb_bottom_surface_extr != 0) + plate_extruders.push_back(glb_bottom_surface_extr); } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cc0e8ecef11..90e80661cac 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -247,24 +247,20 @@ static void collect_filament_slots_from_config( int num_filaments, std::set& used_slots) { - static const std::vector keys_1based = { - "wall_filament", - "sparse_infill_filament", - "solid_infill_filament" - }; - for (const char* key : keys_1based) - { - const ConfigOptionInt* option = config.option(key); - if (option != nullptr && option->value >= 1 && option->value <= num_filaments) - used_slots.insert(option->value - 1); - } - - static const std::vector keys_0based = { + // All feature filament keys use 0 = "Default" (inherit the active object/part filament), + // so only explicit selections (>= 1) mark a slot as used. + static const std::vector keys_with_default = { + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id", "support_filament", "support_interface_filament", "wipe_tower_filament" }; - for (const char* key : keys_0based) + for (const char* key : keys_with_default) { const ConfigOptionInt* option = config.option(key); if (option != nullptr && option->value >= 1 && option->value <= num_filaments) @@ -288,12 +284,15 @@ static void collect_filament_slots_from_model_config( used_slots.insert(extruder_id - 1); } - // Per-object feature-specific keys (wall_filament, etc.) may be + // Per-object feature-specific keys (outer_wall_filament_id, etc.) may be // overridden independently of the object's primary extruder. static const std::vector feature_keys = { - "wall_filament", - "sparse_infill_filament", - "solid_infill_filament", + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id", "support_filament", "support_interface_filament", "wipe_tower_filament" @@ -1247,54 +1246,6 @@ struct DynamicFilamentList : DynamicList } }; -struct DynamicFilamentList1Based : DynamicFilamentList -{ - void apply_on(Choice *c) override - { - if (items.empty()) - update(true); - auto cb = dynamic_cast(c->window); - auto n = cb->GetSelection(); - cb->Clear(); - for (auto i : items) { - cb->Append(i.first, *i.second); - } - if (n < cb->GetCount()) - cb->SetSelection(n); - } - wxString get_value(int index) override - { - wxString str; - str << index+1; - return str; - } - int index_of(wxString value) override - { - long n = 0; - if(!value.ToLong(&n)) - return -1; - --n; - return (n >= 0 && n <= items.size()) ? int(n) : -1; - } - void update(bool force = false) - { - items.clear(); - if (!force && m_choices.empty()) - return; - auto icons = get_extruder_color_icons(true); - auto presets = wxGetApp().preset_bundle->filament_presets; - for (int i = 0; i < presets.size(); ++i) { - wxString str; - std::string type; - wxGetApp().preset_bundle->filaments.find_preset(presets[i])->get_filament_type(type); - str << type; - items.push_back({str, icons[i]}); - } - DynamicList::update(); - } - -}; - class MixedFilamentColorMatchDialog : public DPIDialog { public: @@ -1819,7 +1770,6 @@ MixedColorMatchRecipeResult prompt_best_color_match_recipe(wxWindow* } static DynamicFilamentList dynamic_filament_list; -static DynamicFilamentList1Based dynamic_filament_list_1_based; static wxString nozzle_type_key_to_label(const std::string& key) { @@ -1839,9 +1789,12 @@ Sidebar::Sidebar(Plater *parent) { Choice::register_dynamic_list("support_filament", &dynamic_filament_list); Choice::register_dynamic_list("support_interface_filament", &dynamic_filament_list); - Choice::register_dynamic_list("wall_filament", &dynamic_filament_list_1_based); - Choice::register_dynamic_list("sparse_infill_filament", &dynamic_filament_list_1_based); - Choice::register_dynamic_list("solid_infill_filament", &dynamic_filament_list_1_based); + Choice::register_dynamic_list("outer_wall_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("inner_wall_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("sparse_infill_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("internal_solid_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("top_surface_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("bottom_surface_filament_id", &dynamic_filament_list); Choice::register_dynamic_list("wipe_tower_filament", &dynamic_filament_list); p->scrolled = new wxPanel(this); @@ -8299,7 +8252,6 @@ void Sidebar::show_SEMM_buttons(bool bshow) void Sidebar::update_dynamic_filament_list() { dynamic_filament_list.update(); - dynamic_filament_list_1_based.update(); } void Sidebar::update_nozzle_settings(bool switch_machine) @@ -9434,7 +9386,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "extruder_colour", "filament_colour", "material_colour", "printable_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", - "brim_width", "wall_loops", "wall_filament", "sparse_infill_density", "sparse_infill_filament", "solid_infill_filament", "top_shell_layers", + "brim_width", "wall_loops", "outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_density", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "top_shell_layers", "enable_support", "support_filament", "support_interface_filament", "support_top_z_distance", "support_bottom_z_distance", "raft_layers", "wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "local_z_wipe_tower_purge_lines", "wipe_tower_max_purge_speed", @@ -12302,7 +12255,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // Validate passed, but also check filament temp mixing as a final // guard. check_filament_temp_mixing() reads directly from preset // configs and catches cases that Print::validate() may miss (e.g. - // wall_filament changes that haven't propagated to PrintRegions yet). + // outer_wall_filament_id changes that haven't propagated to PrintRegions yet). bool filament_ok = q->sync_filament_temp_mixing_notification(); if (filament_ok) { this->partplate_list.get_curr_plate()->update_apply_result_invalid(false); @@ -20543,7 +20496,8 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r sidebar().on_filaments_delete(filament_id); // update global feature filament selections - static const char* keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + static const char* keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament"}; for (auto key : keys) if (p->config->has(key)) { @@ -20810,7 +20764,7 @@ bool Plater::check_filament_temp_mixing(int plate_index) // Collect from the Plater working config. The approach balances // sensitivity against false positives: // - Global features (wipe tower, support) always apply → always collected. - // - Feature-specific keys (wall_filament, infill) depend on the global + // - Feature-specific keys (outer_wall_filament_id, infill) depend on the global // process defaults. They are only collected when at least one object // on the plate uses the default extruder (e=0), which means those // defaults WILL affect the actual slicing output. @@ -20833,9 +20787,12 @@ bool Plater::check_filament_temp_mixing(int plate_index) if (uses_default_extruder) { static const std::vector default_keys = { - "wall_filament", - "sparse_infill_filament", - "solid_infill_filament" + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id" }; for (const char* key : default_keys) { @@ -21133,8 +21090,10 @@ void Plater::on_config_change(const DynamicPrintConfig &config) update_scheduled = true; } // Orca: update when *_filament changed - else if (opt_key == "support_interface_filament" || opt_key == "support_filament" || opt_key == "wall_filament" || - opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament") { + else if (opt_key == "support_interface_filament" || opt_key == "support_filament" || + opt_key == "outer_wall_filament_id" || opt_key == "inner_wall_filament_id" || + opt_key == "sparse_infill_filament_id" || opt_key == "internal_solid_filament_id" || + opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") { update_scheduled = true; } } diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 483ffdc3e94..490e44c954d 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -128,9 +128,12 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle auto feature_extruder_active = [idx_extruder, num_extruders](int i) { return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1; }; - bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("wall_filament")); - bool infill_extruder_active = feature_extruder_active(print_config.opt_int("wall_filament")); - bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_filament")); + bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("outer_wall_filament_id")) + && feature_extruder_active(print_config.opt_int("inner_wall_filament_id")); + bool infill_extruder_active = feature_extruder_active(print_config.opt_int("sparse_infill_filament_id")); + bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("internal_solid_filament_id")) + && feature_extruder_active(print_config.opt_int("top_surface_filament_id")) + && feature_extruder_active(print_config.opt_int("bottom_surface_filament_id")); bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_filament")); bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_interface_filament")); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 58f5d8d5b1f..ae87b7bbbd6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2257,6 +2257,8 @@ void TabPrint::build() auto optgroup = page->new_optgroup(L("Layer height"), L"param_layer_height"); optgroup->append_single_option_line("layer_height","quality_settings_layer_height"); optgroup->append_single_option_line("initial_layer_print_height","quality_settings_layer_height"); + optgroup->append_single_option_line("extruder_layer_height_mode","quality_settings_layer_height"); + optgroup->append_single_option_line("extruder_layer_height_tolerance","quality_settings_layer_height"); optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); optgroup->append_single_option_line("line_width","quality_settings_line_width"); @@ -2499,6 +2501,7 @@ void TabPrint::build() optgroup->append_single_option_line("raft_contact_distance", "support_settings_raft"); optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); + optgroup->append_single_option_line("support_nozzle_diameter", "support_settings_filament"); optgroup->append_single_option_line("support_filament", "support_settings_filament#base"); optgroup->append_single_option_line("support_interface_filament", "support_settings_filament#interface"); optgroup->append_single_option_line("support_interface_not_for_body", "support_settings_filament#avoid-interface-filament-for-base"); @@ -2571,9 +2574,12 @@ void TabPrint::build() optgroup->append_single_option_line("single_extruder_multi_material_priming", "multimaterial_settings_prime_tower"); optgroup = page->new_optgroup(L("Filament for Features"), L"param_filament_for_features"); - optgroup->append_single_option_line("wall_filament", "multimaterial_settings_filament_for_features#walls"); - optgroup->append_single_option_line("sparse_infill_filament", "multimaterial_settings_filament_for_features#infill"); - optgroup->append_single_option_line("solid_infill_filament", "multimaterial_settings_filament_for_features#solid-infill"); + optgroup->append_single_option_line("outer_wall_filament_id", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("inner_wall_filament_id", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("sparse_infill_filament_id", "multimaterial_settings_filament_for_features#infill"); + optgroup->append_single_option_line("internal_solid_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); + optgroup->append_single_option_line("top_surface_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); + optgroup->append_single_option_line("bottom_surface_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); optgroup->append_single_option_line("wipe_tower_filament", "multimaterial_settings_filament_for_features#wipe-tower"); optgroup = page->new_optgroup(L("Ooze prevention"), L"param_ooze_prevention"); @@ -2879,16 +2885,24 @@ static DynamicPrintConfig resolved_model_config_for_tab(const DynamicPrintConfig if (const auto* extruder_opt = config.option("extruder"); extruder_opt != nullptr && extruder_opt->value > 0) { const int extruder = extruder_opt->value; - if (!resolved.has("wall_filament")) - resolved.set_key_value("wall_filament", new ConfigOptionInt(extruder)); - if (!resolved.has("sparse_infill_filament")) - resolved.set_key_value("sparse_infill_filament", new ConfigOptionInt(extruder)); - if (!resolved.has("solid_infill_filament")) - resolved.set_key_value("solid_infill_filament", new ConfigOptionInt(extruder)); - } - - if (!resolved.has("solid_infill_filament") && resolved.has("sparse_infill_filament")) - resolved.set_key_value("solid_infill_filament", new ConfigOptionInt(resolved.opt_int("sparse_infill_filament"))); + for (const char* key : {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id"}) + if (!resolved.has(key)) + resolved.set_key_value(key, new ConfigOptionInt(extruder)); + } + + // 0 = "Default" (use the active object/part filament); only explicit selections propagate. + if (resolved.has("sparse_infill_filament_id") && !resolved.has("internal_solid_filament_id")) + if (const int sparse_infill_filament_id = resolved.opt_int("sparse_infill_filament_id"); sparse_infill_filament_id > 0) + resolved.set_key_value("internal_solid_filament_id", new ConfigOptionInt(sparse_infill_filament_id)); + + if (resolved.has("internal_solid_filament_id")) + if (const int internal_solid_filament_id = resolved.opt_int("internal_solid_filament_id"); internal_solid_filament_id > 0) { + if (!resolved.has("top_surface_filament_id")) + resolved.set_key_value("top_surface_filament_id", new ConfigOptionInt(internal_solid_filament_id)); + if (!resolved.has("bottom_surface_filament_id")) + resolved.set_key_value("bottom_surface_filament_id", new ConfigOptionInt(internal_solid_filament_id)); + } return resolved; } @@ -4851,6 +4865,7 @@ if (is_marlin_flavor) optgroup = page->new_optgroup(L("Layer height limits"), L"param_layer_height"); optgroup->append_single_option_line("min_layer_height", "", extruder_idx); optgroup->append_single_option_line("max_layer_height", "", extruder_idx); + optgroup->append_single_option_line("extruder_layer_height", "", extruder_idx); optgroup = page->new_optgroup(L("Position"), L"param_position"); optgroup->append_single_option_line("extruder_offset", "", extruder_idx); diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 5e282b08a56..f4083b14835 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(${_TEST_NAME}_tests test_gcode.cpp test_gcodewriter.cpp test_model.cpp + test_multi_nozzle_layer_height.cpp test_print.cpp test_printgcode.cpp test_printobject.cpp diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp new file mode 100644 index 00000000000..6b711a65fc0 --- /dev/null +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -0,0 +1,684 @@ +#include + +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Print.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/GCode/ToolOrdering.hpp" +#include + +#include "test_data.hpp" + +using namespace Slic3r; +using namespace Slic3r::Test; + +// ORCA: tests for the per-extruder layer height feature ("extruder_layer_height"). + +template +static void for_each_path(const ExtrusionEntityCollection &collection, const PathFn &fn) +{ + for (const ExtrusionEntity *entity : collection.entities) { + if (auto *sub_collection = dynamic_cast(entity)) + for_each_path(*sub_collection, fn); + else if (auto *loop = dynamic_cast(entity)) { + for (const ExtrusionPath &path : loop->paths) + fn(path); + } else if (auto *multi_path = dynamic_cast(entity)) { + for (const ExtrusionPath &path : multi_path->paths) + fn(path); + } else if (auto *path = dynamic_cast(entity)) + fn(*path); + } +} + +static void collect_path_heights(const ExtrusionEntityCollection &collection, std::vector &heights) +{ + for_each_path(collection, [&heights](const ExtrusionPath &path) { heights.emplace_back(path.height); }); +} + +static std::vector region_path_heights(const LayerRegion *layerm) +{ + std::vector heights; + collect_path_heights(layerm->perimeters, heights); + collect_path_heights(layerm->fills, heights); + return heights; +} + +static void collect_path_role_widths(const ExtrusionEntityCollection &collection, std::vector> &widths) +{ + for_each_path(collection, [&widths](const ExtrusionPath &path) { widths.emplace_back(path.role(), path.width); }); +} + +// Two extruders: a 0.4 mm nozzle printing with the 0.2 mm object layer height and a 0.6 mm nozzle +// with a configurable extruder layer height (0.4 => multiplier 2). +// On this fork's classic multi-tool printers a filament index is the extruder index, so no +// filament map configuration applies. +static DynamicPrintConfig two_extruder_config(double second_extruder_layer_height) +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); + config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(0.4)); + config.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); + config.set_key_value("enable_support", new ConfigOptionBool(false)); + + config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4, 0.6})); + config.set_key_value("extruder_layer_height", new ConfigOptionFloats({0., second_extruder_layer_height})); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.07})); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); + config.set_key_value("filament_diameter", new ConfigOptionFloats({1.75, 1.75})); + config.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00"})); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PLA"})); + config.set_key_value("default_filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00"})); + config.set_key_value("nozzle_temperature", new ConfigOptionInts({210, 210})); + config.set_key_value("nozzle_temperature_range_low", new ConfigOptionInts({190, 190})); + config.set_key_value("nozzle_temperature_range_high", new ConfigOptionInts({240, 240})); + // flush_volumes_matrix must be filament_count^2 entries. + config.set_key_value("flush_multiplier", new ConfigOptionFloat(1.)); + config.set_key_value("flush_volumes_matrix", new ConfigOptionFloats({0, 0, 0, 0})); + // Print::validate() reports motion-ability diagnostics by overwriting the single warning + // out-param; raise the machine limit so the default print accelerations do not clobber the + // layer height warnings under test. + config.set_key_value("machine_max_acceleration_extruding", new ConfigOptionFloats({100000., 100000.})); + return config; +} + +// One object made of two 20x20 mm cube parts side by side; the second part prints with filament 2. +// The parts are 20 * z_scale mm tall (10 mm by default). +static void init_two_part_print(Print &print, Model &model, const DynamicPrintConfig &config, float z_scale = 0.5f) +{ + TriangleMesh fine_mesh = mesh(TestMesh::cube_20x20x20); + fine_mesh.scale(Vec3f(1.f, 1.f, z_scale)); + TriangleMesh coarse_mesh = fine_mesh; + coarse_mesh.translate(30.f, 0.f, 0.f); + + ModelObject *object = model.add_object(); + object->name = "two_part_cube"; + object->add_volume(std::move(fine_mesh)); + ModelVolume *coarse_volume = object->add_volume(std::move(coarse_mesh)); + coarse_volume->config.set("extruder", 2); + object->add_instance(); + + arrange_objects(model, InfiniteBed{}, ArrangeParams{scaled(min_object_distance(config))}); + for (ModelObject *mo : model.objects) + mo->ensure_on_bed(); + print.apply(model, config); + print.set_status_silent(); +} + +static void find_regions(const PrintObject &object, int &fine_region, int &coarse_region) +{ + fine_region = coarse_region = -1; + for (size_t i = 0; i < object.num_printing_regions(); ++ i) { + if (object.printing_region(i).config().outer_wall_filament_id.value == 2) + coarse_region = int(i); + else + fine_region = int(i); + } +} + +SCENARIO("Per-extruder layer height combines region layers", "[MultiNozzleLayerHeight]") { + GIVEN("A two-part object, the second part on a 0.6 mm nozzle with a 0.4 mm extruder layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the configuration passes validation and slices as expected") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + // 0.4 mm first layer + 48 layers of 0.2 mm = 10 mm object height. + REQUIRE(object.layer_count() == 49); + + // The coarse region extrudes on the first layer and then only on every 2nd layer, + // always with 0.4 mm high paths; the layers in between print nothing for it. + size_t coarse_layers = 0, coarse_bad_heights = 0, coarse_unexpected = 0, coarse_missing = 0; + // The fine region extrudes on every layer with the base layer heights. + size_t fine_bad_heights = 0, fine_missing = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const Layer *layer = object.get_layer(int(idx)); + const std::vector coarse_heights = region_path_heights(layer->get_region(coarse_region)); + if (idx % 2 == 0) { + if (coarse_heights.empty()) + ++ coarse_missing; + else + ++ coarse_layers; + for (float height : coarse_heights) + if (std::abs(height - 0.4) > 1e-3) + ++ coarse_bad_heights; + } else if (! coarse_heights.empty()) + ++ coarse_unexpected; + + const std::vector fine_heights = region_path_heights(layer->get_region(fine_region)); + if (fine_heights.empty()) + ++ fine_missing; + const double fine_expected = idx == 0 ? 0.4 : 0.2; + for (float height : fine_heights) + if (std::abs(height - fine_expected) > 1e-3) + ++ fine_bad_heights; + } + CHECK(coarse_missing == 0); + CHECK(coarse_unexpected == 0); + CHECK(coarse_bad_heights == 0); + CHECK(coarse_layers == 25); // layer 0 + the 24 group tops + CHECK(fine_missing == 0); + CHECK(fine_bad_heights == 0); + } + } + + GIVEN("The same object with extruder_layer_height disabled") { + DynamicPrintConfig config = two_extruder_config(0.); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("both regions print on every layer with the base layer heights") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + + size_t missing = 0, bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const Layer *layer = object.get_layer(int(idx)); + const double expected = idx == 0 ? 0.4 : 0.2; + for (int region_id : { fine_region, coarse_region }) { + const std::vector heights = region_path_heights(layer->get_region(region_id)); + if (heights.empty()) + ++ missing; + for (float height : heights) + if (std::abs(height - expected) > 1e-3) + ++ bad_heights; + } + } + CHECK(missing == 0); + CHECK(bad_heights == 0); + } + } +} + +SCENARIO("Per-extruder layer height respects the extruder's minimum layer height", "[MultiNozzleLayerHeight]") { + GIVEN("A 0.6 mm preferred layer height with a 0.4 mm minimum, on a part height leaving a 1-layer tail") { + DynamicPrintConfig config = two_extruder_config(0.6); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.6})); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.4})); + Print print; + Model model; + // 10.2 mm parts: a 0.4 mm first layer + 49 layers of 0.2 mm. 49 is not divisible by the + // multiplier 3, so without the minimum the column would end in a single 0.2 mm layer. + init_two_part_print(print, model, config, 0.51f); + THEN("no layer of the coarse part above the first prints below 0.4 mm") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(coarse_region >= 0); + + size_t below_min = 0, full_runs = 0, forced_runs = 0; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) { + const std::vector heights = region_path_heights(object.get_layer(int(idx))->get_region(coarse_region)); + for (float height : heights) { + if (height < 0.4 - 1e-3) + ++ below_min; + else if (std::abs(height - 0.6) < 1e-3) + ++ full_runs; + else if (std::abs(height - 0.4) < 1e-3) + ++ forced_runs; + } + } + CHECK(below_min == 0); + CHECK(full_runs > 0); + CHECK(forced_runs > 0); + } + } + GIVEN("A preferred layer height below the extruder's minimum layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.45})); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation fails") { + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "extruder_layer_height"); + } + } +} + +// Shared check: the fine part's walls print on every layer with the base layer heights while +// combined 0.4 mm high infill appears on some layers above the first. +static void check_plain_walls_combined_infill(Print &print) +{ + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + + size_t wall_bad_heights = 0, wall_missing = 0, combined_infill_paths = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const LayerRegion *layerm = object.get_layer(int(idx))->get_region(fine_region); + std::vector wall_heights; + collect_path_heights(layerm->perimeters, wall_heights); + if (wall_heights.empty()) + ++ wall_missing; + const double expected = idx == 0 ? 0.4 : 0.2; + for (float height : wall_heights) + if (std::abs(height - expected) > 1e-3) + ++ wall_bad_heights; + if (idx > 0) { + std::vector fill_heights; + collect_path_heights(layerm->fills, fill_heights); + for (float height : fill_heights) + if (std::abs(height - 0.4) < 1e-3) + ++ combined_infill_paths; + } + } + CHECK(wall_missing == 0); + CHECK(wall_bad_heights == 0); + CHECK(combined_infill_paths > 0); +} + +SCENARIO("Per-extruder layer height honors feature filaments", "[MultiNozzleLayerHeight]") { + GIVEN("A part whose sparse infill uses the filament with a 0.4 mm preferred layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + // Both parts print their sparse infill with filament 2; the first part's walls stay on + // filament 1 with no preferred layer height. + config.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the first part prints per-layer walls with sparse infill combined to 0.4 mm") { + check_plain_walls_combined_infill(print); + } + } + + GIVEN("A part whose internal solid infill uses the 0.4 mm filament at 100% infill density") { + DynamicPrintConfig config = two_extruder_config(0.4); + // At 100% density the combined infill is internal solid infill: the preference of ITS + // filament must decide the combined height, not the sparse infill filament's. + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(100)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the first part prints per-layer walls with solid infill combined to 0.4 mm") { + check_plain_walls_combined_infill(print); + } + } + + GIVEN("A part whose outer walls use a filament with a different preferred layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + // The first part's outer walls print with filament 2 while its inner walls stay on + // filament 1: walls print together, so the part keeps the object layer height and the + // unhonored preference is warned about. + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation passes with a warning instead of an error") { + // This fork's validate() appends warnings to a single StringObjectException out-param. + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(! warning.string.empty()); + REQUIRE(warning.opt_key == "extruder_layer_height"); + } + } +} + +SCENARIO("Fill line width follows the filament that prints the surface", "[MultiNozzleLayerHeight]") { + GIVEN("Internal solid infill mapped to the 0.6 mm filament, bottom surfaces staying on the 0.4 mm filament") { + DynamicPrintConfig config = two_extruder_config(0.); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("initial_layer_line_width", new ConfigOptionFloatOrPercent(125., true)); + config.set_key_value("internal_solid_infill_line_width", new ConfigOptionFloatOrPercent(105., true)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("bottom surface widths resolve against their own filament's nozzle") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + + // Bottom surfaces print with filament 1, so their percent line width resolves against + // its 0.4 mm nozzle, while internal solid infill (filament 2) resolves against 0.6 mm. + // Solid fills may stretch line spacing up to 20% to fit a region evenly, so accept + // widths in [nominal, 1.2 * nominal]. + size_t bottom_paths = 0, bottom_bad_widths = 0, solid_paths = 0, solid_bad_widths = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector> widths; + collect_path_role_widths(object.get_layer(int(idx))->get_region(fine_region)->fills, widths); + for (const std::pair &role_width : widths) { + const bool bottom = role_width.first == erBottomSurface; + if (! bottom && role_width.first != erSolidInfill) + continue; + ++ (bottom ? bottom_paths : solid_paths); + const double expected = (idx == 0 ? 1.25 : 1.05) * (bottom ? 0.4 : 0.6); + if (role_width.second < expected - 1e-3 || role_width.second > expected * 1.2 + 1e-3) + ++ (bottom ? bottom_bad_widths : solid_bad_widths); + } + } + CHECK(bottom_paths > 0); + CHECK(bottom_bad_widths == 0); + CHECK(solid_paths > 0); + CHECK(solid_bad_widths == 0); + } + } +} + +SCENARIO("Combined infill respects the printing extruder's layer height limits", "[MultiNozzleLayerHeight]") { + GIVEN("Infill combining to a preferred height above the filament's maximum layer height") { + // Infill on filament 2: preferred layer height 0.6 exceeds its max_layer_height 0.45, + // so combining must stop at 0.4 mm groups instead of building 0.6 mm ones (3 x 0.2). + DynamicPrintConfig config = two_extruder_config(0.6); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); + config.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(2)); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); + Print print; + Model model; + // A single part: filament 2 prints only infill, so its preference skips the strict wall checks. + TriangleMesh cube = mesh(TestMesh::cube_20x20x20); + cube.scale(Vec3f(1.f, 1.f, 0.5f)); + ModelObject *object_model = model.add_object(); + object_model->name = "single_cube"; + object_model->add_volume(std::move(cube)); + object_model->add_instance(); + arrange_objects(model, InfiniteBed{}, ArrangeParams{scaled(min_object_distance(config))}); + for (ModelObject *mo : model.objects) + mo->ensure_on_bed(); + print.apply(model, config); + print.set_status_silent(); + THEN("no combined infill group exceeds the maximum layer height") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + size_t over_max = 0, combined = 0; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) + for (const LayerRegion *layerm : object.get_layer(int(idx))->regions()) { + std::vector heights; + collect_path_heights(layerm->fills, heights); + for (float height : heights) { + if (height > 0.45 + 1e-3) + ++ over_max; + else if (height > 0.2 + 1e-3) + ++ combined; + } + } + CHECK(over_max == 0); + CHECK(combined > 0); + } + } + + GIVEN("Walls combining to a pitch a feature filament of the part cannot print") { + // Both wall filaments map to filament 2 at a 0.4 mm pitch, but top/bottom/solid features + // stay on filament 1 whose max_layer_height (0.3) cannot print that pitch: the part + // falls back to the object layer height with a warning, like disagreeing wall filaments. + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(2)); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); + // Keep the line width checks out of the way, this test targets the height fallback. + config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation warns and the part prints with the object layer height") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("cannot print that height") != std::string::npos); + + print.process(); + // Only the first part falls back; the second is all filament 2 and keeps the pitch. + // Both parts' walls print with filament 2, so the parts are told apart by their + // top surface filament. + const PrintObject &object = *print.objects().front(); + int fine_region = -1; + for (size_t i = 0; i < object.num_printing_regions(); ++ i) + if (object.printing_region(i).config().top_surface_filament_id.value == 1) + fine_region = int(i); + REQUIRE(fine_region >= 0); + size_t wall_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const double expected = idx == 0 ? 0.4 : 0.2; + std::vector heights; + collect_path_heights(object.get_layer(int(idx))->get_region(fine_region)->perimeters, heights); + for (float height : heights) + if (std::abs(height - expected) > 1e-3) + ++ wall_bad_heights; + } + CHECK(wall_bad_heights == 0); + } + } + + GIVEN("A feature filament whose minimum layer height is above the pitch it prints at") { + // Internal solid infill on filament 2 (minimum layer height 0.3) prints with the 0.2 mm + // object layer height while filament 2's preference drives infill combining elsewhere. + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.3})); + // Keep the line width checks out of the way, this test targets the height warning. + config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation warns about printing below the minimum layer height") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("minimum layer") != std::string::npos); + } + } +} + +SCENARIO("Fill collections dispatch to the filament their flow was computed for", "[MultiNozzleLayerHeight]") { + GIVEN("A region with distinct per-feature filaments") { + PrintRegionConfig region_config; + region_config.outer_wall_filament_id.value = 1; + region_config.inner_wall_filament_id.value = 1; + region_config.sparse_infill_filament_id.value = 4; + region_config.internal_solid_filament_id.value = 3; + region_config.top_surface_filament_id.value = 1; + region_config.bottom_surface_filament_id.value = 1; + const PrintRegion region(region_config, region_config.hash(), 0); + LayerTools layer_tools(0.); + + auto collection_extruder = [&](std::initializer_list roles) { + ExtrusionEntityCollection eec; + for (ExtrusionRole role : roles) + eec.entities.push_back(new ExtrusionPath(role)); + return layer_tools.extruder(eec, region); + }; + + THEN("top and bottom surfaces keep their filament when gap fill is mixed in") { + CHECK(collection_extruder({erTopSolidInfill}) == 0); // 0 based filament 1 + CHECK(collection_extruder({erTopSolidInfill, erGapFill}) == 0); + CHECK(collection_extruder({erBottomSurface, erGapFill}) == 0); + CHECK(collection_extruder({erSolidInfill, erGapFill}) == 2); // filament 3 + } + THEN("external bridges print with the bottom surface filament, internal ones stay solid") { + CHECK(collection_extruder({erBridgeInfill}) == 0); // bottom filament 1 + CHECK(collection_extruder({erInternalBridgeInfill}) == 2); // internal solid filament 3 + } + THEN("gap fill with no sibling surface prints with the outer wall filament") { + PrintRegionConfig gap_region_config = region_config; + gap_region_config.outer_wall_filament_id.value = 2; + const PrintRegion gap_region(gap_region_config, gap_region_config.hash(), 0); + ExtrusionEntityCollection eec; + eec.entities.push_back(new ExtrusionPath(erGapFill)); + CHECK(layer_tools.extruder(eec, gap_region) == 1); // 0 based outer wall filament 2 + } + } +} + +SCENARIO("Support nozzle diameter restricts support printing", "[MultiNozzleLayerHeight]") { + // A raft makes the object require support handling without any overhang geometry: the raft + // layers below the object print as support-only layers. + auto raft_config = [](double support_nozzle_diameter) { + DynamicPrintConfig config = two_extruder_config(0.); + config.set_key_value("raft_layers", new ConfigOptionInt(2)); + config.option>("support_type", true)->value = stNormalAuto; + config.set_key_value("support_nozzle_diameter", new ConfigOptionFloat(support_nozzle_diameter)); + config.set_key_value("support_line_width", new ConfigOptionFloatOrPercent(105., true)); + return config; + }; + + GIVEN("A raft restricted to the 0.6 mm nozzle while the default filament prints with 0.4 mm") { + DynamicPrintConfig config = raft_config(0.6); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("support flows, layer height limits and tool ordering follow the 0.6 mm filament") { + REQUIRE(print.validate().string.empty()); + + const PrintObject &object = *print.objects().front(); + // Support / raft flows must resolve width against the restricted nozzle, not against + // extruder 1 that the "default" support filament falls back to. + REQUIRE(double(support_material_flow(&object).width()) == Approx(1.05 * 0.6).margin(1e-4)); + REQUIRE(double(support_material_interface_flow(&object).width()) == Approx(1.05 * 0.6).margin(1e-4)); + + // Support layer height limits follow the restricted nozzle (filament 2: max 0.45). + PrintConfig print_config; + print_config.apply(config, true); + PrintObjectConfig object_config; + object_config.apply(config, true); + const SlicingParameters params = SlicingParameters::create_from_config( + print_config, object_config, 10., std::vector{0, 1}, Vec3d(1., 1., 1.)); + REQUIRE(params.max_suport_layer_height == Approx(0.45).margin(1e-6)); + + // The raft layers below the object print with filament 2 only. + print.process(); + ToolOrdering tool_ordering(print, (unsigned int)-1, false); + size_t support_only_layers = 0, wrong_extruders = 0; + for (const LayerTools &layer_tools : tool_ordering.layer_tools()) + if (layer_tools.has_support && ! layer_tools.has_object) { + ++ support_only_layers; + for (unsigned int extruder_id : layer_tools.extruders) + if (extruder_id != 1) // 0 based: filament 2 prints with the 0.6 mm nozzle + ++ wrong_extruders; + } + CHECK(support_only_layers > 0); + CHECK(wrong_extruders == 0); + } + } + + GIVEN("A support nozzle diameter no extruder has") { + DynamicPrintConfig config = raft_config(0.5); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation fails") { + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "support_nozzle_diameter"); + } + } + + GIVEN("A support filament printing with a different nozzle than the support nozzle diameter") { + DynamicPrintConfig config = raft_config(0.6); + config.set_key_value("support_filament", new ConfigOptionInt(1)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation fails") { + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "support_filament"); + } + } +} + +SCENARIO("A raft keeps the bottom surfaces of combined regions", "[MultiNozzleLayerHeight]") { + GIVEN("A combined region printing on a raft") { + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("raft_layers", new ConfigOptionInt(2)); + config.option>("support_type", true)->value = stNormalAuto; + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the first layer above the raft prints uncombined and carries the bottom surfaces") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + + // The first layer above the raft must not be swallowed by a combined group: surface + // detection can only seed the object's bottom shells there. + const Layer *first_layer = object.get_layer(0); + size_t bottom_regions = 0; + for (int region_id : { fine_region, coarse_region }) { + const LayerRegion *layerm = first_layer->get_region(region_id); + const std::vector heights = region_path_heights(layerm); + CHECK(! heights.empty()); + for (float height : heights) + CHECK(double(height) == Approx(0.2).margin(1e-3)); + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.is_bottom()) { + ++ bottom_regions; + break; + } + } + CHECK(bottom_regions == 2); + + // Combining still happens above the first object layer. + size_t combined_layers = 0; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) + for (float height : region_path_heights(object.get_layer(int(idx))->get_region(coarse_region))) + if (std::abs(height - 0.4) < 1e-3) { + ++ combined_layers; + break; + } + CHECK(combined_layers > 0); + } + } +} + +SCENARIO("Per-extruder layer height validation rejects invalid configurations", "[MultiNozzleLayerHeight]") { + auto expect_error = [](double second_extruder_layer_height) { + DynamicPrintConfig config = two_extruder_config(second_extruder_layer_height); + Print print; + Model model; + init_two_part_print(print, model, config); + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "extruder_layer_height"); + }; + GIVEN("An extruder layer height that is no integer multiple of the object layer height") { + THEN("validation fails") { expect_error(0.5); } + } + GIVEN("An extruder layer height smaller than the object layer height") { + THEN("validation fails") { expect_error(0.1); } + } + GIVEN("An extruder layer height exceeding the nozzle diameter") { + THEN("validation fails") { expect_error(0.8); } + } + GIVEN("An extruder layer height exceeding the extruder's maximum layer height") { + // 0.6 is a multiple of 0.2 and fits the 0.6 mm nozzle, but exceeds max_layer_height 0.45. + THEN("validation fails") { expect_error(0.6); } + } +} diff --git a/tests/libslic3r/test_mixed_filament.cpp b/tests/libslic3r/test_mixed_filament.cpp index b15592e3766..a4e7b0648f3 100644 --- a/tests/libslic3r/test_mixed_filament.cpp +++ b/tests/libslic3r/test_mixed_filament.cpp @@ -476,11 +476,12 @@ TEST_CASE("Grouped manual wall patterns make infill follow the innermost perimet REQUIRE(row.manual_pattern == "12,1"); PrintRegionConfig region_config = static_cast(FullPrintConfig::defaults()); - region_config.wall_filament.value = 3; + region_config.outer_wall_filament_id.value = 3; + region_config.inner_wall_filament_id.value = 3; region_config.wall_loops.value = 2; region_config.sparse_infill_density.value = 15.; - region_config.sparse_infill_filament.value = 2; - region_config.solid_infill_filament.value = 3; + region_config.sparse_infill_filament_id.value = 2; + region_config.internal_solid_filament_id.value = 3; PrintRegion region(region_config); @@ -498,21 +499,21 @@ TEST_CASE("Grouped manual wall patterns make infill follow the innermost perimet layer1.mixed_mgr = &mgr; layer1.num_physical = 2; - CHECK(layer0.wall_filament(region) == 0); - CHECK(layer1.wall_filament(region) == 1); - CHECK(layer0.sparse_infill_filament(region) == 1); - CHECK(layer1.sparse_infill_filament(region) == 1); - CHECK(layer0.solid_infill_filament(region) == 0); - CHECK(layer1.solid_infill_filament(region) == 0); + CHECK(layer0.wall_extruder_id(region) == 0); + CHECK(layer1.wall_extruder_id(region) == 1); + CHECK(layer0.sparse_infill_filament_id(region) == 1); + CHECK(layer1.sparse_infill_filament_id(region) == 1); + CHECK(layer0.internal_solid_filament_id(region) == 0); + CHECK(layer1.internal_solid_filament_id(region) == 0); - region_config.sparse_infill_filament.value = 2; - region_config.solid_infill_filament.value = 2; + region_config.sparse_infill_filament_id.value = 2; + region_config.internal_solid_filament_id.value = 2; PrintRegion overridden_region(region_config); - CHECK(layer0.sparse_infill_filament(overridden_region) == 1); - CHECK(layer1.sparse_infill_filament(overridden_region) == 1); - CHECK(layer0.solid_infill_filament(overridden_region) == 1); - CHECK(layer1.solid_infill_filament(overridden_region) == 1); + CHECK(layer0.sparse_infill_filament_id(overridden_region) == 1); + CHECK(layer1.sparse_infill_filament_id(overridden_region) == 1); + CHECK(layer0.internal_solid_filament_id(overridden_region) == 1); + CHECK(layer1.internal_solid_filament_id(overridden_region) == 1); } TEST_CASE("Mixed filament painted-region resolver collapses ordinary mixed rows to the active physical extruder", "[MixedFilament]") From 0de9b2598b1c33d85670b27a61e113daba160855 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:01:39 +0200 Subject: [PATCH 02/43] fix: allow per-nozzle diameters in the Prepare sidebar (Snapmaker U1) --- src/slic3r/GUI/Plater.cpp | 112 +++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 90e80661cac..1170745f642 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -8311,61 +8312,72 @@ void Sidebar::update_nozzle_settings(bool switch_machine) } diameter_combo->Bind(wxEVT_COMBOBOX, [this, diameter_combo, i](wxCommandEvent& event) { + // ORCA multi-nozzle-size: set ONLY this nozzle's diameter, mirroring the + // Printer Settings -> Extruder tab. Previously this switched the whole printer preset + // to a single-diameter variant (forcing all nozzles to the same size); that defeats + // per-nozzle sizes, which the slicer now supports. + + // Parse the selected diameter from the "0.4mm" combo item. + wxString sel_num = diameter_combo->GetValue(); + if (sel_num.EndsWith("mm")) + sel_num.RemoveLast(2); + double new_nd = 0.; + if (!sel_num.ToCDouble(&new_nd) || new_nd <= 0.) + return; - //auto* pNotice = p->plater->get_notification_manager(); - //if (pNotice) - //{ - // pNotice->close_notification_of_type(NotificationType::CustomNotification); - // pNotice->push_notification(_u8L("Note: Printing PLA Silk on the hot end of 0.6mm hardened steel is not recommended. 0.4mm or smaller specifications are suggested."), 0); - // pNotice->set_slicing_progress_hidden(); - //} - - auto printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; - auto printer_model_opt = printer_config.option("printer_model"); - if (printer_model_opt) { - std::string printer_model = printer_model_opt->value; - bool is_snapmaker_u1 = boost::icontains(printer_model, "Snapmaker") && boost::icontains(printer_model, "U1"); - - if (is_snapmaker_u1) - { - //check the config has flags to tips switch nozzle and all nozzle will be changed to the same type - auto notShow = wxGetApp().app_config->get("app", "sync_diameter_flags"); - if (notShow != "true") - { - RichMessageDialog dlg(static_cast(wxGetApp().mainframe), - _L("Note: Changing this will sync all other nozzles to the same diameter."), - _L("Set Nozzle Diameter"), - wxOK); - dlg.ShowCheckBox(_L("Don't show this again"), false); - auto res = dlg.ShowModal(); - bool isCheckBox = dlg.IsCheckBoxChecked(); - - if (wxID_OK == res) - wxGetApp().app_config->set("app", "sync_diameter_flags", isCheckBox); - } - } - } - - auto diameter = diameter_combo->GetValue().substr(0, 3); - auto preset = wxGetApp().preset_bundle->get_similar_printer_preset({}, diameter.ToStdString()); - if (preset == nullptr) { - BOOST_LOG_TRIVIAL(error) << "get the similar printer preset fail"; + Tab* printer_tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (printer_tab == nullptr) return; - } - preset->is_visible = true; // force visible - - for (size_t i = 0; i < p->m_nozzle_diameter_lists.size(); ++i) { - //set all nozzle use the diameter - p->m_nozzle_diameter_lists[i]->SetValue(diameter + "mm"); - } - wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(preset->name); - // Do not event.Skip(): select_preset rebuilds nozzle UI and can destroy this combo; skipping would let sidebar treat this as bed-type combo and use-after-free. + // Write nozzle_diameter[i] into the edited printer config, like Tab.cpp's extruder page. + DynamicPrintConfig new_conf = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const auto* nozzle_diam_opt = static_cast(new_conf.option("nozzle_diameter")); + if (nozzle_diam_opt == nullptr || i >= nozzle_diam_opt->values.size()) + return; + std::vector nozzle_diameters = nozzle_diam_opt->values; + if (std::abs(nozzle_diameters[i] - new_nd) < EPSILON) + return; // unchanged + nozzle_diameters[i] = new_nd; + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + + // load_config marks the printer preset modified and propagates the change without + // rebuilding these combos or switching presets, so the other nozzles keep their sizes. + printer_tab->load_config(new_conf); + // Do not event.Skip(): this is a plain ComboBox; skipping would let the sidebar treat + // it as the bed-type combo (Plater::priv::on_combobox_select) and mishandle it. }); - auto diam_str = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_variant")->value; - - diameter_combo->SetValue(diam_str + "mm"); + // Show THIS nozzle's own diameter (per-nozzle sizes are supported), selecting the matching + // "x.xmm" item so the read-only combo accepts it. Falls back to the uniform printer_variant + // label only when the per-nozzle value is unavailable. + const double this_nd = (nozzle_diameter && i < nozzle_diameter->values.size()) ? nozzle_diameter->values[i] : 0.; + wxString this_label; + for (unsigned int n = 0; n < diameter_combo->GetCount(); ++n) { + wxString item = diameter_combo->GetString(n); + wxString num = item; + if (num.EndsWith("mm")) + num.RemoveLast(2); + double item_nd = 0.; + if (num.ToCDouble(&item_nd) && std::abs(item_nd - this_nd) < EPSILON) { + this_label = item; + break; + } + } + if (this_label.empty() && this_nd > 0.) { + // A per-nozzle diameter not among the printer's variant list (e.g. an imported config): + // add it so the combo can display the true value. Format with the C locale (period + // decimal) to match the other "x.xmm" items and the ToCDouble parsing above. + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << this_nd; + this_label = wxString(oss.str()) + "mm"; + diameter_combo->AppendString(this_label); + } + if (this_label.empty()) { + const auto *pv = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_variant"); + this_label = (pv ? wxString(pv->value) : wxString()) + "mm"; + } + diameter_combo->SetValue(this_label); p->m_nozzle_diameter_lists.push_back(diameter_combo); From 9a90ccb55fc00b8ab0b0bf8ea8fa5b778a409ae4 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 20:37:46 +0200 Subject: [PATCH 03/43] profiles: express Snapmaker U1 line widths as % of nozzle diameter --- resources/profiles/Snapmaker.json | 2 +- .../Snapmaker/process/fdm_process_U1.json | 16 ++++++++-------- .../process/fdm_process_U1_0.06_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.08_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.10_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.12_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.14_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.18_nozzle_0.6.json | 16 ++++++++-------- .../Snapmaker/process/fdm_process_U1_0.24.json | 2 +- .../process/fdm_process_U1_0.24_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.24_nozzle_0.8.json | 16 ++++++++-------- .../Snapmaker/process/fdm_process_U1_0.28.json | 2 +- .../process/fdm_process_U1_0.30_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.32_nozzle_0.8.json | 16 ++++++++-------- .../process/fdm_process_U1_0.36_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.40_nozzle_0.8.json | 16 ++++++++-------- .../process/fdm_process_U1_0.42_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.48_nozzle_0.8.json | 16 ++++++++-------- .../process/fdm_process_U1_0.56_nozzle_0.8.json | 16 ++++++++-------- .../Snapmaker/process/fdm_process_U1_common.json | 6 +++--- 20 files changed, 134 insertions(+), 134 deletions(-) diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index 268c453c60b..6de5b155eb8 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "02.02.50.02", + "version": "02.02.50.03", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1.json b/resources/profiles/Snapmaker/process/fdm_process_U1.json index 6c9935cce7b..9c75c70c9c5 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1.json @@ -12,18 +12,18 @@ "default_acceleration": "10000", "bridge_no_support": "0", "elefant_foot_compensation": "0.1", - "outer_wall_line_width": "0.42", + "outer_wall_line_width": "105%", "outer_wall_speed": "120", - "line_width": "0.45", + "line_width": "112.5%", "infill_direction": "45", "sparse_infill_density": "15%", "sparse_infill_pattern": "grid", - "initial_layer_line_width": "0.42", + "initial_layer_line_width": "105%", "initial_layer_print_height": "0.2", "initial_layer_speed": "20", "gap_infill_speed": "30", "infill_combination": "0", - "sparse_infill_line_width": "0.45", + "sparse_infill_line_width": "112.5%", "infill_wall_overlap": "15%", "sparse_infill_speed": "50", "interface_shells": "0", @@ -31,7 +31,7 @@ "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}.gcode", "wall_loops": "2", - "inner_wall_line_width": "0.45", + "inner_wall_line_width": "112.5%", "inner_wall_speed": "40", "print_settings_id": "", "raft_layers": "0", @@ -39,13 +39,13 @@ "skirt_distance": "2", "skirt_height": "2", "minimum_sparse_infill_area": "0", - "internal_solid_infill_line_width": "0.45", + "internal_solid_infill_line_width": "112.5%", "internal_solid_infill_speed": "40", "spiral_mode": "0", "standby_temperature_delta": "-5", "enable_support": "0", "support_filament": "0", - "support_line_width": "0.42", + "support_line_width": "105%", "support_interface_filament": "0", "support_on_build_plate_only": "0", "support_top_z_distance": "0.15", @@ -60,7 +60,7 @@ "support_threshold_angle": "40", "support_object_xy_distance": "0.5", "detect_thin_wall": "0", - "top_surface_line_width": "0.42", + "top_surface_line_width": "105%", "top_surface_speed": "30", "travel_speed": "400", "enable_prime_tower": "0", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json index b028d9087a0..36e972e066b 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json index 79430d26d2d..255713a98d3 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json index 3e47d1bf5ae..d86cafb29fa 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json index c52683cb8f4..0a14422db8f 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json index 57119f0f022..58a403c7361 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json index 7d10b214291..156b714e941 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.18", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json index 89224e3a29a..66f83313ca1 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json @@ -6,7 +6,7 @@ "instantiation": "false", "layer_height": "0.24", "elefant_foot_compensation": "0.15", - "top_surface_line_width": "0.45", + "top_surface_line_width": "112.5%", "top_shell_thickness": "1.0", "bridge_flow": "1", "initial_layer_speed": "50", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json index 12104aa0cdf..cd408b77e55 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.24", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json index 5f0b60b231a..49508e88835 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.24", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json index 189d8d337ce..37fd4f68e0e 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json @@ -6,7 +6,7 @@ "instantiation": "false", "layer_height": "0.28", "elefant_foot_compensation": "0.15", - "top_surface_line_width": "0.45", + "top_surface_line_width": "112.5%", "top_shell_thickness": "1.0", "bridge_flow": "1", "initial_layer_speed": "50", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json index fcf87f69513..f753c702d9b 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.3", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json index 43b3dc8f9b9..37441608522 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.32", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json index 01c90927b29..61701643e2d 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.36", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json index 45bd45c8a54..3850aaf0f55 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.4", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json index 57cc629208c..91224ef8427 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.42", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json index be97fb0672b..2bb250ed9bd 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.48", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json index 2aed528ea3a..30225fb7bb0 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.56", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_common.json b/resources/profiles/Snapmaker/process/fdm_process_U1_common.json index 4f9c8a34079..4dc5e352b16 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_common.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_common.json @@ -16,10 +16,10 @@ "enable_arc_fitting": "1", "outer_wall_acceleration": "5000", "wall_infill_order": "inner wall/outer wall/infill", - "line_width": "0.42", + "line_width": "105%", "internal_bridge_support_thickness": "0.8", "initial_layer_acceleration": "500", - "initial_layer_line_width": "0.5", + "initial_layer_line_width": "125%", "initial_layer_speed": "30", "gap_infill_speed": "50", "sparse_infill_speed": "250", @@ -41,7 +41,7 @@ "skirt_height": "1", "skirt_loops": "0", "minimum_sparse_infill_area": "15", - "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_line_width": "105%", "internal_solid_infill_speed": "150", "initial_layer_infill_speed": "60", "resolution": "0.012", From f2bd0299e1ba96b610f51115769bff18d0968b07 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:51:45 +0000 Subject: [PATCH 04/43] feat: port mainline Orca "Filament for Features" enhancements Port of the mainline OrcaSlicer feature chain to this fork (based on Orca ~2.3.4): - Fix Filament for Features (OrcaSlicer#13607) - Fix + Support 'Default' filament option, index 0 (OrcaSlicer#13887) - Filament for features for SEMM (OrcaSlicer#13937) - Divided filament for features (OrcaSlicer#14042) The coarse per-feature filament selectors are split into six independent ones: outer_wall_filament_id, inner_wall_filament_id, sparse_infill_filament_id, internal_solid_filament_id, top_surface_filament_id and bottom_surface_filament_id. All default to 0 ("Default" = use the active object/part filament) instead of the old 1-based scheme where "1" doubled as the default. Backward compatibility: PrintConfigDef::handle_legacy() renames the old keys (wall_filament, sparse_infill_filament, solid_infill_filament and their pre-Orca aliases) on any config load - presets, 3mf project files and imported G-code alike - and maps the legacy default value 1 to 0. Fork-specific integrations kept working: the mixed-filament manager's per-layer filament resolution (LayerTools resolve_mixed, pointillism and grouped-manual-pattern splitting in GCode::process_layer, effective filament remapping in LayerRegion/Fill), Local-Z clipped collections, SSWCP/Flutter filament slot collection, and the object settings tab resolver all route the new six selectors through the same paths the three old ones used. Mixed perimeter collections are now split into outer/inner sub-collections when the two wall filaments differ. --- src/libslic3r/Fill/Fill.cpp | 12 +- src/libslic3r/GCode.cpp | 325 +++++++++++--------- src/libslic3r/GCode/ToolOrdering.cpp | 118 +++++-- src/libslic3r/GCode/ToolOrdering.hpp | 9 +- src/libslic3r/Layer.cpp | 3 +- src/libslic3r/Layer.hpp | 7 + src/libslic3r/LayerRegion.cpp | 17 +- src/libslic3r/MultiMaterialSegmentation.cpp | 2 +- src/libslic3r/PerimeterGenerator.cpp | 8 +- src/libslic3r/Preset.cpp | 4 +- src/libslic3r/PresetBundle.cpp | 13 +- src/libslic3r/Print.cpp | 2 +- src/libslic3r/PrintApply.cpp | 36 ++- src/libslic3r/PrintConfig.cpp | 165 ++++++++-- src/libslic3r/PrintConfig.hpp | 9 +- src/libslic3r/PrintObject.cpp | 145 +++++++-- src/libslic3r/PrintObjectSlice.cpp | 28 +- src/libslic3r/PrintRegion.cpp | 47 ++- src/libslic3r/Support/SupportCommon.cpp | 2 +- src/libslic3r/Support/SupportMaterial.cpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 14 +- src/slic3r/GUI/GUI_Factories.cpp | 2 +- src/slic3r/GUI/GUI_ObjectList.cpp | 12 +- src/slic3r/GUI/PartPlate.cpp | 156 +++++++--- src/slic3r/GUI/Plater.cpp | 119 +++---- src/slic3r/GUI/PresetHints.cpp | 9 +- src/slic3r/GUI/Tab.cpp | 37 ++- tests/libslic3r/test_mixed_filament.cpp | 31 +- 28 files changed, 861 insertions(+), 473 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index ea899aa3b39..c3fd03b0c24 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -896,6 +896,12 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.extrusion_role = erSolidInfill; } } + // ORCA: per-feature filaments. Top and internal solid fills are already resolved + // by layerm.extruder(extrusion_role) above; bottom surfaces print with the bottom + // surface filament, routed through the same mixed-filament remapping as top surfaces. + if (params.extrusion_role == erBottomSurface) + params.extruder = effective_layer_filament_id(layer, + (unsigned int)std::max(0, region_config.bottom_surface_filament_id.value)); // Orca: apply fill multiline only for sparse infill params.multiline = params.extrusion_role == erInternalInfill ? int(region_config.fill_multiline) : 1; @@ -1530,12 +1536,12 @@ void Layer::make_ironing() ((config.top_shell_layers > 0 || (this->object()->print()->config().spiral_mode && config.bottom_shell_layers > 1)) && (config.ironing_type == IroningType::TopSurfaces || (config.ironing_type == IroningType::TopmostOnly && layerm->layer()->upper_layer == nullptr))))) { - if (config.wall_filament == config.solid_infill_filament || config.wall_loops == 0) { + if (config.outer_wall_filament_id == config.top_surface_filament_id || config.wall_loops == 0) { // Iron the whole face. - ironing_params.extruder = config.solid_infill_filament; + ironing_params.extruder = config.top_surface_filament_id; } else { // Iron just the infill. - ironing_params.extruder = config.solid_infill_filament; + ironing_params.extruder = config.top_surface_filament_id; } } if (ironing_params.extruder != -1) { diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index adfcc0dc651..451168de998 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5003,6 +5003,7 @@ LayerResult GCode::process_layer(const Print& print, // Group extrusions by an extruder, then by an object, an island and a region. std::map> by_extruder; + std::vector> split_perimeter_storage; bool is_anything_overridden = const_cast(layer_tools).wiping_extrusions().is_anything_overridden(); const double nozzle_0_mm = m_config.nozzle_diameter.values.empty() ? 0.4 : m_config.nozzle_diameter.get_at(0); const double pointillism_pixel_size_cfg = std::max(0.0, double(m_config.mixed_filament_pointillism_pixel_size.value)); @@ -5026,12 +5027,19 @@ LayerResult GCode::process_layer(const Print& print, return layer_tools.extruder_override; const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) - return unsigned(region.config().sparse_infill_filament.value); + return unsigned(region.config().sparse_infill_filament_id.value); + if (role == erTopSolidInfill || role == erIroning) + return unsigned(region.config().top_surface_filament_id.value); + if (role == erBottomSurface) + return unsigned(region.config().bottom_surface_filament_id.value); if (is_solid_infill(role)) - return unsigned(region.config().solid_infill_filament.value); - return unsigned(region.config().sparse_infill_filament.value); + return unsigned(region.config().internal_solid_filament_id.value); + return unsigned(region.config().sparse_infill_filament_id.value); } - return layer_tools.extruder_override == 0 ? unsigned(region.config().wall_filament.value) : layer_tools.extruder_override; + if (layer_tools.extruder_override != 0) + return layer_tools.extruder_override; + return entities.role() == erPerimeter ? unsigned(region.config().inner_wall_filament_id.value) + : unsigned(region.config().outer_wall_filament_id.value); }; auto configured_extruder_id = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type, @@ -5040,12 +5048,17 @@ LayerResult GCode::process_layer(const Print& print, if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) - return int(layer_tools.sparse_infill_filament(region)); + return int(layer_tools.sparse_infill_filament_id(region)); + if (role == erTopSolidInfill || role == erIroning) + return int(layer_tools.top_surface_filament_id(region)); + if (role == erBottomSurface) + return int(layer_tools.bottom_surface_filament_id(region)); if (is_solid_infill(role)) - return int(layer_tools.solid_infill_filament(region)); - return int(layer_tools.sparse_infill_filament(region)); + return int(layer_tools.internal_solid_filament_id(region)); + return int(layer_tools.sparse_infill_filament_id(region)); } - return int(layer_tools.wall_filament(region)); + return entities.role() == erPerimeter ? int(layer_tools.inner_wall_extruder_id(region)) + : int(layer_tools.wall_extruder_id(region)); }; auto pointillism_sequence_for_filament = [&](unsigned int filament_id_1based) -> const std::vector* { @@ -5585,155 +5598,187 @@ LayerResult GCode::process_layer(const Print& print, local_z_clipped_collections.emplace_back(std::move(clipped_base)); } - const unsigned int configured_filament_id = configured_filament_id_1based(entity_type, *filtered_extrusions, region); - const std::vector* pointillism_sequence = - is_anything_overridden ? nullptr : pointillism_sequence_for_filament(configured_filament_id); - if (pointillism_sequence != nullptr) { - std::vector> split_by_extruder; - PointillismPathSplitStats split_stats; - const size_t sequence_phase = pointillism_sequence->empty() ? - 0 : size_t(std::max(0, layer_tools.layer_index)) % pointillism_sequence->size(); - if (split_extrusion_collection_for_pointillism_paths(*filtered_extrusions, - *pointillism_sequence, - layer_tools.num_physical, - pointillism_segment_len_scaled, - pointillism_line_gap_scaled, - sequence_phase, - split_by_extruder, - split_stats) && - split_stats.bucket_count >= 2) { - ++pointillism_path_split_entities; - pointillism_path_split_segments += split_stats.segment_count; - for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { - std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; - if (!split_collection || split_collection->entities.empty()) - continue; - const ExtrusionEntityCollection* split_ptr = split_collection.get(); - local_z_clipped_collections.emplace_back(std::move(split_collection)); - std::vector& islands = - object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); - for (size_t i = 0; i <= n_slices; ++i) { - const bool last = i == n_slices; - const size_t island_idx = last ? n_slices : slices_test_order[i]; - if (last || entity_matches_surface(island_idx, *split_ptr)) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); - break; + auto process_extrusions = [&](const ExtrusionEntityCollection* current_extrusions, + const ExtrusionEntityCollection* overrides_key, + bool use_overrides) { + const unsigned int configured_filament_id = configured_filament_id_1based(entity_type, *current_extrusions, region); + const std::vector* pointillism_sequence = + is_anything_overridden ? nullptr : pointillism_sequence_for_filament(configured_filament_id); + if (pointillism_sequence != nullptr) { + std::vector> split_by_extruder; + PointillismPathSplitStats split_stats; + const size_t sequence_phase = pointillism_sequence->empty() ? + 0 : size_t(std::max(0, layer_tools.layer_index)) % pointillism_sequence->size(); + if (split_extrusion_collection_for_pointillism_paths(*current_extrusions, + *pointillism_sequence, + layer_tools.num_physical, + pointillism_segment_len_scaled, + pointillism_line_gap_scaled, + sequence_phase, + split_by_extruder, + split_stats) && + split_stats.bucket_count >= 2) { + ++pointillism_path_split_entities; + pointillism_path_split_segments += split_stats.segment_count; + for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { + std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; + if (!split_collection || split_collection->entities.empty()) + continue; + const ExtrusionEntityCollection* split_ptr = split_collection.get(); + local_z_clipped_collections.emplace_back(std::move(split_collection)); + std::vector& islands = + object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + const bool last = i == n_slices; + const size_t island_idx = last ? n_slices : slices_test_order[i]; + if (last || entity_matches_surface(island_idx, *split_ptr)) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); + break; + } } } + return; } - continue; + ++pointillism_path_split_fallbacks; } - ++pointillism_path_split_fallbacks; - } - // This extrusion is part of certain Region, which tells us which extruder should be used for it: - int correct_extruder_id = configured_extruder_id(entity_type, *filtered_extrusions, region); - if (!is_anything_overridden && - entity_type == ObjectByExtruder::Island::Region::PERIMETERS && - layer_tools.mixed_mgr != nullptr && - layer_tools.num_physical > 0 && - correct_extruder_id >= 0) { - const unsigned int mixed_filament_id = - grouped_manual_pattern_mixed_filament_id(entity_type, *filtered_extrusions, region); - if (mixed_filament_id != 0) { - std::vector> split_by_extruder; - size_t bucket_count = 0; - const PrintObject* current_object_for_gradient = - layer_to_print.original_object != nullptr ? layer_to_print.original_object : layer_to_print.object(); - if (split_extrusion_collection_for_multi_perimeter_pattern(*filtered_extrusions, - *layer_tools.mixed_mgr, - mixed_filament_id, - layer_tools.num_physical, - layer_tools.layer_index, - split_by_extruder, - bucket_count, - current_object_for_gradient)) { - if (bucket_count >= 2) { - for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { - std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; - if (!split_collection || split_collection->entities.empty()) - continue; - const ExtrusionEntityCollection* split_ptr = split_collection.get(); - local_z_clipped_collections.emplace_back(std::move(split_collection)); - std::vector& islands = - object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); - for (size_t i = 0; i <= n_slices; ++i) { - const bool last = i == n_slices; - const size_t island_idx = last ? n_slices : slices_test_order[i]; - if (last || entity_matches_surface(island_idx, *split_ptr)) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); - break; + // This extrusion is part of certain Region, which tells us which extruder should be used for it: + int correct_extruder_id = configured_extruder_id(entity_type, *current_extrusions, region); + if (!is_anything_overridden && + entity_type == ObjectByExtruder::Island::Region::PERIMETERS && + layer_tools.mixed_mgr != nullptr && + layer_tools.num_physical > 0 && + correct_extruder_id >= 0) { + const unsigned int mixed_filament_id = + grouped_manual_pattern_mixed_filament_id(entity_type, *current_extrusions, region); + if (mixed_filament_id != 0) { + std::vector> split_by_extruder; + size_t bucket_count = 0; + const PrintObject* current_object_for_gradient = + layer_to_print.original_object != nullptr ? layer_to_print.original_object : layer_to_print.object(); + if (split_extrusion_collection_for_multi_perimeter_pattern(*current_extrusions, + *layer_tools.mixed_mgr, + mixed_filament_id, + layer_tools.num_physical, + layer_tools.layer_index, + split_by_extruder, + bucket_count, + current_object_for_gradient)) { + if (bucket_count >= 2) { + for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { + std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; + if (!split_collection || split_collection->entities.empty()) + continue; + const ExtrusionEntityCollection* split_ptr = split_collection.get(); + local_z_clipped_collections.emplace_back(std::move(split_collection)); + std::vector& islands = + object_islands_by_extruder(by_extruder, unsigned(extruder_idx), layer_to_print_idx, layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + const bool last = i == n_slices; + const size_t island_idx = last ? n_slices : slices_test_order[i]; + if (last || entity_matches_surface(island_idx, *split_ptr)) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, split_ptr, nullptr); + break; + } } } + return; } - continue; - } - if (bucket_count == 1) { - for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { - const std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; - if (split_collection && !split_collection->entities.empty()) { - // by_extruder keys and LayerTools runtime extruder IDs are zero-based here. - correct_extruder_id = int(extruder_idx); - break; + if (bucket_count == 1) { + for (size_t extruder_idx = 0; extruder_idx < split_by_extruder.size(); ++extruder_idx) { + const std::unique_ptr& split_collection = split_by_extruder[extruder_idx]; + if (split_collection && !split_collection->entities.empty()) { + // by_extruder keys and LayerTools runtime extruder IDs are zero-based here. + correct_extruder_id = int(extruder_idx); + break; + } } } } } } - } - // Let's recover vector of extruder overrides: - const WipingExtrusions::ExtruderPerCopy* entity_overrides = nullptr; - if (!layer_tools.has_extruder(correct_extruder_id)) { - // this entity is not overridden, but its extruder is not in layer_tools - we'll print it - // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare - // extruders are eradicated from layer_tools) - correct_extruder_id = layer_tools.extruders.back(); - } - printing_extruders.clear(); - if (is_anything_overridden) { - entity_overrides = const_cast(layer_tools) - .wiping_extrusions() - .get_extruder_overrides(filtered_extrusions, layer_to_print.original_object, correct_extruder_id, - layer_to_print.object()->instances().size()); - if (entity_overrides == nullptr) { - printing_extruders.emplace_back(correct_extruder_id); - } else { - printing_extruders.reserve(entity_overrides->size()); - for (int extruder : *entity_overrides) - printing_extruders.emplace_back(extruder >= 0 ? - // at least one copy is overridden to use this extruder - extruder : - // at least one copy would normally be printed with this extruder - // (see get_extruder_overrides function for explanation) - static_cast(-extruder - 1)); - Slic3r::sort_remove_duplicates(printing_extruders); + // Let's recover vector of extruder overrides: + const WipingExtrusions::ExtruderPerCopy* entity_overrides = nullptr; + if (!layer_tools.has_extruder(correct_extruder_id)) { + // this entity is not overridden, but its extruder is not in layer_tools - we'll print it + // by last extruder on this layer (could happen e.g. when a wiping object is taller than others - dontcare + // extruders are eradicated from layer_tools) + correct_extruder_id = layer_tools.extruders.back(); } - } else - printing_extruders.emplace_back(correct_extruder_id); - - // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: - for (unsigned int extruder : printing_extruders) { - std::vector& islands = - object_islands_by_extruder(by_extruder, extruder, layer_to_print_idx, layers.size(), n_slices + 1); - for (size_t i = 0; i <= n_slices; ++i) { - bool last = i == n_slices; - size_t island_idx = last ? n_slices : slices_test_order[i]; - if ( // extrusions->first_point does not fit inside any slice - last || - // extrusions->first_point fits inside ith slice - entity_matches_surface(island_idx, *filtered_extrusions)) { - if (islands[island_idx].by_region.empty()) - islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); - islands[island_idx].by_region[region.print_region_id()].append(entity_type, filtered_extrusions, - entity_overrides); - break; + printing_extruders.clear(); + if (is_anything_overridden && use_overrides) { + entity_overrides = const_cast(layer_tools) + .wiping_extrusions() + .get_extruder_overrides(overrides_key, layer_to_print.original_object, correct_extruder_id, + layer_to_print.object()->instances().size()); + if (entity_overrides == nullptr) { + printing_extruders.emplace_back(correct_extruder_id); + } else { + printing_extruders.reserve(entity_overrides->size()); + for (int extruder : *entity_overrides) + printing_extruders.emplace_back(extruder >= 0 ? + // at least one copy is overridden to use this extruder + extruder : + // at least one copy would normally be printed with this extruder + // (see get_extruder_overrides function for explanation) + static_cast(-extruder - 1)); + Slic3r::sort_remove_duplicates(printing_extruders); + } + } else + printing_extruders.emplace_back(correct_extruder_id); + + // Now we must add this extrusion into the by_extruder map, once for each extruder that will print it: + for (unsigned int extruder : printing_extruders) { + std::vector& islands = + object_islands_by_extruder(by_extruder, extruder, layer_to_print_idx, layers.size(), n_slices + 1); + for (size_t i = 0; i <= n_slices; ++i) { + bool last = i == n_slices; + size_t island_idx = last ? n_slices : slices_test_order[i]; + if ( // extrusions->first_point does not fit inside any slice + last || + // extrusions->first_point fits inside ith slice + entity_matches_surface(island_idx, *current_extrusions)) { + if (islands[island_idx].by_region.empty()) + islands[island_idx].by_region.assign(print.num_print_regions(), ObjectByExtruder::Island::Region()); + islands[island_idx].by_region[region.print_region_id()].append(entity_type, current_extrusions, + entity_overrides); + break; + } } } + }; + + bool split_mixed_perimeters = + entity_type == ObjectByExtruder::Island::Region::PERIMETERS && + region.config().outer_wall_filament_id.value != region.config().inner_wall_filament_id.value && + filtered_extrusions->role() == erMixed; + + if (split_mixed_perimeters) { + auto outer_perimeters = std::make_unique(); + auto inner_perimeters = std::make_unique(); + for (const ExtrusionEntity* entity : filtered_extrusions->entities) { + const ExtrusionRole role = entity->role(); + if (role == erExternalPerimeter || role == erOverhangPerimeter) + outer_perimeters->append(*entity); + else if (role == erPerimeter) + inner_perimeters->append(*entity); + } + + if (!outer_perimeters->entities.empty()) { + split_perimeter_storage.emplace_back(std::move(outer_perimeters)); + process_extrusions(split_perimeter_storage.back().get(), nullptr, false); + } + if (!inner_perimeters->entities.empty()) { + split_perimeter_storage.emplace_back(std::move(inner_perimeters)); + process_extrusions(split_perimeter_storage.back().get(), nullptr, false); + } + } else { + process_extrusions(filtered_extrusions, filtered_extrusions, true); } } } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 811923daaed..afac29ba51a 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -94,14 +94,18 @@ bool internal_solid_infill_uses_sparse_filament(const PrintRegion ®ion, Extru unsigned int sparse_infill_filament_id_1based(const PrintRegion ®ion) { - return region.config().sparse_infill_filament.value; + return region.config().sparse_infill_filament_id.value; } unsigned int infill_filament_id_1based(const LayerTools &layer_tools, const PrintRegion ®ion, ExtrusionRole role) { if (internal_solid_infill_uses_sparse_filament(region, role)) return sparse_infill_filament_id_1based(region); - return is_solid_infill(role) ? region.config().solid_infill_filament.value : sparse_infill_filament_id_1based(region); + if (role == erTopSolidInfill || role == erIroning) + return region.config().top_surface_filament_id.value; + if (role == erBottomSurface) + return region.config().bottom_surface_filament_id.value; + return is_solid_infill(role) ? region.config().internal_solid_filament_id.value : sparse_infill_filament_id_1based(region); } unsigned int grouped_manual_pattern_mixed_filament_id_for_layer(const LayerTools& layer_tools, @@ -284,25 +288,48 @@ unsigned int LayerTools::resolve_mixed_1based(unsigned int filament_id) const } // Return a zero based extruder from the region, or extruder_override if overriden. -unsigned int LayerTools::wall_filament(const PrintRegion ®ion) const +unsigned int LayerTools::wall_extruder_id(const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); - unsigned int id = (this->extruder_override == 0) ? region.config().wall_filament.value : this->extruder_override; + assert(region.config().outer_wall_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().outer_wall_filament_id.value : this->extruder_override; return resolve_mixed_1based(id) - 1; } -unsigned int LayerTools::sparse_infill_filament(const PrintRegion ®ion) const +unsigned int LayerTools::inner_wall_extruder_id(const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); + assert(region.config().inner_wall_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().inner_wall_filament_id.value : this->extruder_override; + return resolve_mixed_1based(id) - 1; +} + +unsigned int LayerTools::sparse_infill_filament_id(const PrintRegion ®ion) const +{ + assert(region.config().sparse_infill_filament_id.value > 0); unsigned int id = (this->extruder_override == 0) ? sparse_infill_filament_id_1based(region) : this->extruder_override; const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; } -unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const +unsigned int LayerTools::internal_solid_filament_id(const PrintRegion ®ion) const +{ + assert(region.config().internal_solid_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().internal_solid_filament_id.value : this->extruder_override; + const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); + return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; +} + +unsigned int LayerTools::top_surface_filament_id(const PrintRegion ®ion) const { - assert(region.config().solid_infill_filament.value > 0); - unsigned int id = (this->extruder_override == 0) ? region.config().solid_infill_filament.value : this->extruder_override; + assert(region.config().top_surface_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().top_surface_filament_id.value : this->extruder_override; + const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); + return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; +} + +unsigned int LayerTools::bottom_surface_filament_id(const PrintRegion ®ion) const +{ + assert(region.config().bottom_surface_filament_id.value > 0); + unsigned int id = (this->extruder_override == 0) ? region.config().bottom_surface_filament_id.value : this->extruder_override; const unsigned int grouped = grouped_manual_pattern_infill_filament_1based(*this, region, id); return ((grouped != 0) ? grouped : resolve_mixed_1based(id)) - 1; } @@ -310,16 +337,27 @@ unsigned int LayerTools::solid_infill_filament(const PrintRegion ®ion) const // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const { - assert(region.config().wall_filament.value > 0); - assert(region.config().sparse_infill_filament.value > 0); - assert(region.config().solid_infill_filament.value > 0); + assert(region.config().outer_wall_filament_id.value > 0); + assert(region.config().inner_wall_filament_id.value > 0); + assert(region.config().sparse_infill_filament_id.value > 0); + assert(region.config().internal_solid_filament_id.value > 0); + assert(region.config().top_surface_filament_id.value > 0); + assert(region.config().bottom_surface_filament_id.value > 0); if (extrusions.has_infill()) { const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); if (internal_solid_infill_uses_sparse_filament(region, role)) - return sparse_infill_filament(region); - return is_solid_infill(role) ? solid_infill_filament(region) : sparse_infill_filament(region); + return sparse_infill_filament_id(region); + if (extrusions.has_solid_infill()) { + const ExtrusionRole solid_role = extrusions.role(); + if (solid_role == erTopSolidInfill || solid_role == erIroning) + return top_surface_filament_id(region); + if (solid_role == erBottomSurface) + return bottom_surface_filament_id(region); + return internal_solid_filament_id(region); + } + return sparse_infill_filament_id(region); } - return wall_filament(region); + return extrusions.role() == erPerimeter ? inner_wall_extruder_id(region) : wall_extruder_id(region); } static double calc_max_layer_height(const PrintConfig &config, double max_object_layer_height) @@ -564,8 +602,8 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr for (auto object : print.objects()) { auto first_layer = object->get_layer(0); for (auto layerm : first_layer->regions()) { - int extruder_id = layerm->region().config().option("wall_filament")->getInt(); - + int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt(); + for (auto expoly : layerm->raw_slices) { const double nozzle_diameter = print.config().nozzle_diameter.get_at(0); const coordf_t initial_layer_line_width = print.config().get_abs_value("initial_layer_line_width", nozzle_diameter); @@ -610,7 +648,7 @@ std::vector ToolOrdering::generate_first_layer_tool_order(const Pr std::map min_areas_per_extruder; auto first_layer = object.get_layer(0); for (auto layerm : first_layer->regions()) { - int extruder_id = layerm->region().config().option("wall_filament")->getInt(); + int extruder_id = layerm->region().config().option("outer_wall_filament_id")->getInt(); for (auto expoly : layerm->raw_slices) { const double nozzle_diameter = object.print()->config().nozzle_diameter.get_at(0); const coordf_t line_width = object.config().get_abs_value("line_width", nozzle_diameter); @@ -743,7 +781,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } if (something_nonoverriddable){ - const unsigned int configured_wall = (extruder_override == 0) ? region.config().wall_filament.value : extruder_override; + const unsigned int configured_wall = (extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override; unsigned int wall_ext = resolve_mixed(configured_wall, layerCount, float(layer->print_z), float(layer->height), &object); const unsigned int grouped_id = grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall); @@ -773,13 +811,21 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (layerCount == 0) firstLayerExtruders.emplace_back(wall_ext); } + if (extruder_override == 0 && region.config().wall_loops.value > 1) + layer_tools.extruders.emplace_back(resolve_mixed(region.config().inner_wall_filament_id.value, + layerCount, + float(layer->print_z), + float(layer->height), + &object)); } layer_tools.has_object = true; } - bool has_sparse_infill = false; - bool has_solid_infill = false; + bool has_sparse_infill = false; + bool has_internal_solid = false; + bool has_top_solid_surface = false; + bool has_bottom_surface = false; bool something_nonoverriddable = false; for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. @@ -787,8 +833,12 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); if (internal_solid_infill_uses_sparse_filament(region, role)) has_sparse_infill = true; + else if (role == erTopSolidInfill || role == erIroning) + has_top_solid_surface = true; + else if (role == erBottomSurface) + has_bottom_surface = true; else if (is_solid_infill(role)) - has_solid_infill = true; + has_internal_solid = true; else if (role != erNone) has_sparse_infill = true; @@ -800,13 +850,19 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (something_nonoverriddable || !m_print_config_ptr) { if (extruder_override == 0) { - if (has_solid_infill) { - layer_tools.extruders.emplace_back(layer_tools.solid_infill_filament(region) + 1); + if (has_internal_solid) { + layer_tools.extruders.emplace_back(layer_tools.internal_solid_filament_id(region) + 1); + } + if (has_top_solid_surface) { + layer_tools.extruders.emplace_back(layer_tools.top_surface_filament_id(region) + 1); + } + if (has_bottom_surface) { + layer_tools.extruders.emplace_back(layer_tools.bottom_surface_filament_id(region) + 1); } if (has_sparse_infill) { - layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament(region) + 1); + layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament_id(region) + 1); } - } else if (has_solid_infill || has_sparse_infill) { + } else if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill) { layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount, float(layer->print_z), @@ -814,7 +870,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto &object)); } } - if (has_solid_infill || has_sparse_infill) + if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill) layer_tools.has_object = true; } layerCount++; @@ -1507,7 +1563,7 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (wipe_into_infill_only && ! is_infill_first) // In this case we must check that the original extruder is used on this layer before the one we are overridding // (and the perimeters will be finished before the infill is printed): - if (!lt.is_extruder_order(lt.wall_filament(region), new_extruder)) + if (!lt.is_extruder_order(lt.wall_extruder_id(region), new_extruder)) continue; if ((!is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume)) @@ -1625,8 +1681,8 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) if (is_infill_first //BBS //|| object->config().flush_into_objects // in this case the perimeter is overridden, so we can override by the last one safely - || lt.is_extruder_order(lt.wall_filament(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || ! lt.has_extruder(lt.sparse_infill_filament(region)))) // we have to force override - this could violate infill_first (FIXME) + || lt.is_extruder_order(lt.wall_extruder_id(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints + || ! lt.has_extruder(lt.sparse_infill_filament_id(region)))) // we have to force override - this could violate infill_first (FIXME) set_extruder_override(fill, object, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); else { // In this case we can (and should) leave it to be printed normally. diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 070b4c4d210..996d4cdca58 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -106,9 +106,12 @@ class LayerTools bool has_extruder(unsigned int extruder) const { return std::find(this->extruders.begin(), this->extruders.end(), extruder) != this->extruders.end(); } // Return a zero based extruder from the region, or extruder_override if overriden. - unsigned int wall_filament(const PrintRegion ®ion) const; - unsigned int sparse_infill_filament(const PrintRegion ®ion) const; - unsigned int solid_infill_filament(const PrintRegion ®ion) const; + unsigned int wall_extruder_id(const PrintRegion ®ion) const; + unsigned int inner_wall_extruder_id(const PrintRegion ®ion) const; + unsigned int sparse_infill_filament_id(const PrintRegion ®ion) const; + unsigned int internal_solid_filament_id(const PrintRegion ®ion) const; + unsigned int top_surface_filament_id(const PrintRegion ®ion) const; + unsigned int bottom_surface_filament_id(const PrintRegion ®ion) const; // Returns a zero based extruder this eec should be printed with, according to PrintRegion config or extruder_override if overriden. unsigned int extruder(const ExtrusionEntityCollection &extrusions, const PrintRegion ®ion) const; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index e428a1673bd..7d5716c2391 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -181,7 +181,8 @@ bool Layer::is_perimeter_compatible(const PrintRegion& a, const PrintRegion& b) const PrintRegionConfig& config = a.config(); const PrintRegionConfig& other_config = b.config(); - return config.wall_filament == other_config.wall_filament + return config.outer_wall_filament_id == other_config.outer_wall_filament_id + && config.inner_wall_filament_id == other_config.inner_wall_filament_id && config.wall_loops == other_config.wall_loops && config.wall_sequence == other_config.wall_sequence && config.is_infill_first == other_config.is_infill_first diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index b62144a2fea..2412ad99c4a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -16,8 +16,15 @@ using LayerPtrs = std::vector; class LayerRegion; using LayerRegionPtrs = std::vector; class PrintRegion; +class PrintRegionConfig; class PrintObject; +// Snapmaker mixed filament: resolve a configured 1-based filament id to the physical filament +// actually printing on this layer (0 "Default" passes through unchanged). The infill variant +// additionally resolves grouped manual patterns like the innermost perimeter. +unsigned int effective_layer_filament_id(const Layer &layer, unsigned int filament_id); +unsigned int effective_infill_filament_id(const Layer &layer, const PrintRegionConfig &config, unsigned int filament_id); + namespace FillAdaptive { struct Octree; }; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 0cb8b681871..6d4ffc15861 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -20,8 +20,6 @@ namespace Slic3r { -namespace { - unsigned int effective_layer_filament_id(const Layer &layer, unsigned int filament_id) { if (filament_id == 0) @@ -81,16 +79,14 @@ unsigned int effective_infill_filament_id(const Layer &layer, const PrintRegionC object); } -} // namespace - unsigned int LayerRegion::extruder(FlowRole role) const { const PrintRegionConfig &config = this->region().config(); unsigned int filament_id = 0; if (role == frInfill) - filament_id = config.sparse_infill_filament.value; + filament_id = config.sparse_infill_filament_id.value; else if (role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON) - filament_id = config.sparse_infill_filament.value; + filament_id = config.sparse_infill_filament_id.value; else filament_id = this->region().extruder(role); @@ -188,9 +184,12 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe 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)))); + perimeter_config.outer_wall_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.outer_wall_filament_id.value)))); + perimeter_config.inner_wall_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.inner_wall_filament_id.value)))); + perimeter_config.sparse_infill_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.sparse_infill_filament_id.value)))); + perimeter_config.internal_solid_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.internal_solid_filament_id.value)))); + perimeter_config.top_surface_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.top_surface_filament_id.value)))); + perimeter_config.bottom_surface_filament_id.value = int(effective_layer_filament_id(*this->layer(), unsigned(std::max(0, region_config.bottom_surface_filament_id.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. diff --git a/src/libslic3r/MultiMaterialSegmentation.cpp b/src/libslic3r/MultiMaterialSegmentation.cpp index 055ad377966..dee98e67e1f 100644 --- a/src/libslic3r/MultiMaterialSegmentation.cpp +++ b/src/libslic3r/MultiMaterialSegmentation.cpp @@ -1346,7 +1346,7 @@ static inline std::vector> segmentation_top_and_bottom_l if (const PrintRegionConfig &config = region->region().config(); // color_idx == 0 means "don't know" extruder aka the underlying extruder. // As this region may split existing regions, we collect statistics over all regions for color_idx == 0. - color_idx == 0 || config.wall_filament == int(color_idx)) { + color_idx == 0 || config.outer_wall_filament_id == int(color_idx)) { //BBS: the extrusion line width is outer wall rather than inner wall const double nozzle_diameter = print_object.print()->config().nozzle_diameter.get_at(0); double outer_wall_line_width = config.get_abs_value("outer_wall_line_width", nozzle_diameter); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index 4e0ddd0f3fd..a467cbaf934 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -605,7 +605,7 @@ void PerimeterGenerator::split_top_surfaces(const ExPolygons &orig_polygons, ExP // get the real top surface ExPolygons grown_lower_slices; ExPolygons bridge_checker; - auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + auto nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1); // Check whether surface be bridge or not if (this->lower_slices != NULL) { // BBS: get the Polygons below the polygon this layer @@ -1152,7 +1152,7 @@ void PerimeterGenerator::process_classic() // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1); m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2))); } @@ -2101,7 +2101,7 @@ void PerimeterGenerator::process_arachne() // We consider overhang any part where the entire nozzle diameter is not supported by the // lower layer, so we take lower slices and offset them by half the nozzle diameter used // in the current layer - double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->wall_filament - 1); + double nozzle_diameter = this->print_config->nozzle_diameter.get_at(this->config->outer_wall_filament_id - 1); m_lower_slices_polygons = offset(*this->lower_slices, float(scale_(+nozzle_diameter / 2))); } @@ -2537,7 +2537,7 @@ bool PerimeterGeneratorLoop::is_internal_contour() const std::vector PerimeterGenerator::generate_lower_polygons_series(float width) { - float nozzle_diameter = print_config->nozzle_diameter.get_at(config->wall_filament - 1); + float nozzle_diameter = print_config->nozzle_diameter.get_at(config->outer_wall_filament_id - 1); float start_offset = -0.5 * width; float end_offset = 0.5 * nozzle_diameter; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 8882ce9a3af..f499f0c9627 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -892,8 +892,8 @@ static std::vector s_Preset_print_options { "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", - "filename_format", "wall_filament", "support_bottom_z_distance", - "sparse_infill_filament", "solid_infill_filament", "support_filament", "support_interface_filament","support_interface_not_for_body", + "filename_format", "outer_wall_filament_id", "inner_wall_filament_id", "support_bottom_z_distance", + "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament","support_interface_not_for_body", "ooze_prevention", "standby_temperature_delta", "preheat_time","delta_temperature","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "skin_infill_line_width","skeleton_infill_line_width", diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp index f97f468fe47..0d50076e0e9 100644 --- a/src/libslic3r/PresetBundle.cpp +++ b/src/libslic3r/PresetBundle.cpp @@ -2641,13 +2641,16 @@ DynamicPrintConfig PresetBundle::full_fff_config() const opt->value = boost::algorithm::clamp(opt->value, 0, int(num_filaments)); } - static const char* keys_1based[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament"}; - for (size_t i = 0; i < sizeof(keys_1based) / sizeof(keys_1based[0]); ++ i) { - std::string key = std::string(keys_1based[i]); + static const char* keys_with_default[] = { + "outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id" + }; + for (size_t i = 0; i < sizeof(keys_with_default) / sizeof(keys_with_default[0]); ++ i) { + std::string key = std::string(keys_with_default[i]); auto *opt = dynamic_cast(out.option(key, false)); assert(opt != nullptr); - if(opt->value < 1 || opt->value > int(num_filaments)) - opt->value = 1; + if(opt->value < 0 || opt->value > int(num_filaments)) + opt->value = 0; } out.option("print_settings_id", true)->value = this->prints.get_selected_preset_name(); out.option("filament_settings_id", true)->values = this->filament_presets; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 703e8ed32db..bb87f706e13 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2186,7 +2186,7 @@ Flow Print::brim_flow() const frPerimeter, // Flow::new_from_config_width takes care of the percent to value substitution width, - (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().wall_filament-1), + (float)m_config.nozzle_diameter.get_at(m_print_regions.front()->config().outer_wall_filament_id-1), (float)this->skirt_first_layer_height()); } diff --git a/src/libslic3r/PrintApply.cpp b/src/libslic3r/PrintApply.cpp index 31a5e53e650..06494be0fc9 100644 --- a/src/libslic3r/PrintApply.cpp +++ b/src/libslic3r/PrintApply.cpp @@ -828,9 +828,12 @@ bool verify_update_print_object_regions( 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; - cfg.solid_infill_filament.value = region.extruder_id; - cfg.sparse_infill_filament.value = region.extruder_id; + cfg.outer_wall_filament_id.value = region.extruder_id; + cfg.inner_wall_filament_id.value = region.extruder_id; + cfg.internal_solid_filament_id.value = region.extruder_id; + cfg.top_surface_filament_id.value = region.extruder_id; + cfg.bottom_surface_filament_id.value = region.extruder_id; + cfg.sparse_infill_filament_id.value = region.extruder_id; if (cfg != region.region->config()) { // Region configuration changed. if (print_region_ref_cnt(*region.region) == 0) { @@ -1073,20 +1076,29 @@ static PrintObjectRegions* generate_print_object_regions( (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; + cfg.outer_wall_filament_id.value = painted_extruder_id; + cfg.inner_wall_filament_id.value = painted_extruder_id; + cfg.internal_solid_filament_id.value = painted_extruder_id; + cfg.top_surface_filament_id.value = painted_extruder_id; + cfg.bottom_surface_filament_id.value = painted_extruder_id; + cfg.sparse_infill_filament_id.value = painted_extruder_id; // Keep PrintRegion config-interned. If a painted target resolves to the same // config as its parent, alias it instead of creating a duplicate PrintRegion. PrintRegion *painted_region = get_create_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) { + if (painted_region->config().outer_wall_filament_id.value != painted_extruder_id || + painted_region->config().inner_wall_filament_id.value != painted_extruder_id || + painted_region->config().internal_solid_filament_id.value != painted_extruder_id || + painted_region->config().top_surface_filament_id.value != painted_extruder_id || + painted_region->config().bottom_surface_filament_id.value != painted_extruder_id || + painted_region->config().sparse_infill_filament_id.value != painted_extruder_id) { BOOST_LOG_TRIVIAL(warning) << "Painted region filament mismatch" << " requested_extruder_id=" << painted_extruder_id - << " wall_filament=" << painted_region->config().wall_filament.value - << " solid_infill_filament=" << painted_region->config().solid_infill_filament.value - << " sparse_infill_filament=" << painted_region->config().sparse_infill_filament.value + << " outer_wall_filament_id=" << painted_region->config().outer_wall_filament_id.value + << " inner_wall_filament_id=" << painted_region->config().inner_wall_filament_id.value + << " internal_solid_filament_id=" << painted_region->config().internal_solid_filament_id.value + << " top_surface_filament_id=" << painted_region->config().top_surface_filament_id.value + << " bottom_surface_filament_id=" << painted_region->config().bottom_surface_filament_id.value + << " sparse_infill_filament_id=" << painted_region->config().sparse_infill_filament_id.value << " parent_region_id=" << parent_region_id << " parent_print_region_id=" << parent_region.region->print_object_region_id() << " painted_print_region_id=" << painted_region->print_object_region_id(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c4881c23c9a..0e45a0f9e50 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3400,14 +3400,14 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloatOrPercent(100., true)); - def = this->add("sparse_infill_filament", coInt); + def = this->add("sparse_infill_filament_id", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Infill"); def->category = L("Extruders"); - def->tooltip = L("Filament to print internal sparse infill."); - def->min = 1; + def->tooltip = L("Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament."); + def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); def = this->add("sparse_infill_line_width", coFloatOrPercent); def->label = L("Sparse infill"); @@ -4139,14 +4139,23 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(true)); - def = this->add("wall_filament", coInt); + def = this->add("outer_wall_filament_id", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Walls"); + def->label = L("Outer walls"); def->category = L("Extruders"); - def->tooltip = L("Filament to print walls."); - def->min = 1; + def->tooltip = L("Filament to print outer walls.\n\"Default\" uses the active object/part filament."); + def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("inner_wall_filament_id", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Inner walls"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print inner walls.\n\"Default\" uses the active object/part filament."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); def = this->add("inner_wall_line_width", coFloatOrPercent); def->label = L("Inner wall"); @@ -4936,14 +4945,32 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(15)); - def = this->add("solid_infill_filament", coInt); + def = this->add("internal_solid_filament_id", coInt); def->gui_type = ConfigOptionDef::GUIType::i_enum_open; - def->label = L("Solid infill"); + def->label = L("Internal solid infill"); def->category = L("Extruders"); - def->tooltip = L("Filament to print solid infill."); - def->min = 1; + def->tooltip = L("Filament to print internal solid infill.\n\"Default\" uses the active object/part filament."); + def->min = 0; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionInt(1)); + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("top_surface_filament_id", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Top surface"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print top surface.\n\"Default\" uses the active object/part filament."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); + + def = this->add("bottom_surface_filament_id", coInt); + def->gui_type = ConfigOptionDef::GUIType::i_enum_open; + def->label = L("Bottom surface"); + def->category = L("Extruders"); + def->tooltip = L("Filament to print bottom surface.\n\"Default\" uses the active object/part filament."); + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionInt(0)); def = this->add("internal_solid_infill_line_width", coFloatOrPercent); def->label = L("Internal solid infill"); @@ -5314,7 +5341,7 @@ void PrintConfigDef::init_fff_params() def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Support/raft base"); def->category = L("Support"); - def->tooltip = L("Filament to print support base and raft. \"Default\" means no specific filament for support and current filament is used."); + def->tooltip = L("Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used."); def->min = 0; def->mode = comSimple; def->set_default_value(new ConfigOptionInt(0)); @@ -5349,7 +5376,7 @@ void PrintConfigDef::init_fff_params() def->gui_type = ConfigOptionDef::GUIType::i_enum_open; def->label = L("Support/raft interface"); def->category = L("Support"); - def->tooltip = L("Filament to print support interface. \"Default\" means no specific filament for support interface and current filament is used."); + def->tooltip = L("Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used."); def->min = 0; // BBS def->mode = comSimple; @@ -7145,12 +7172,34 @@ void PrintConfigDef::handle_legacy(t_config_option_key &opt_key, std::string &va opt_key = "change_filament_gcode"; } else if (opt_key == "bridge_fan_speed") { opt_key = "overhang_fan_speed"; - } else if (opt_key == "infill_extruder") { - opt_key = "sparse_infill_filament"; - }else if (opt_key == "solid_infill_extruder") { - opt_key = "solid_infill_filament"; - }else if (opt_key == "perimeter_extruder") { - opt_key = "wall_filament"; + } else if (opt_key == "infill_extruder" || opt_key == "sparse_infill_filament") { + // ORCA: legacy feature-filament selector. Pre-2.4.0-dev these keys were 1-based and the + // default value "1" meant "the first/active filament". The current scheme uses a dedicated + // key where 0 = "Default" (inherit the object/part filament) and 1..N = explicit filament. + // Renaming to the new *_id key here means every config (process presets, 3mf project + // settings, imported gcode) is translated uniformly on load - not just the version-gated + // 3mf paths the old bespoke migration covered - and a brand-new key can never be misread as + // a legacy default. Map the legacy default "1" to "0" (inherit); keep explicit values >1. + opt_key = "sparse_infill_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "solid_infill_extruder" || opt_key == "solid_infill_filament") { + opt_key = "internal_solid_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "top_solid_infill_filament") { + opt_key = "top_surface_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "bottom_solid_infill_filament") { + opt_key = "bottom_surface_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "perimeter_extruder" || opt_key == "wall_filament" || opt_key == "wall_filament_id") { + opt_key = "outer_wall_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "inner_wall_filament") { + opt_key = "inner_wall_filament_id"; + if (value == "1") value = "0"; + } else if (opt_key == "outer_wall_filament") { + opt_key = "outer_wall_filament_id"; + if (value == "1") value = "0"; }else if(opt_key == "wipe_tower_extruder") { opt_key = "wipe_tower_filament"; }else if (opt_key == "support_material_extruder") { @@ -7398,10 +7447,18 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) int extruder = this->option("extruder")->getInt(); this->erase("extruder"); if (extruder != 0) { - if (!this->has("sparse_infill_filament")) - this->option("sparse_infill_filament", true)->setInt(extruder); - if (!this->has("wall_filament")) - this->option("wall_filament", true)->setInt(extruder); + if (!this->has("sparse_infill_filament_id") || this->option("sparse_infill_filament_id")->getInt() == 0) + this->option("sparse_infill_filament_id", true)->setInt(extruder); + if (!this->has("outer_wall_filament_id") || this->option("outer_wall_filament_id")->getInt() == 0) + this->option("outer_wall_filament_id", true)->setInt(extruder); + if (!this->has("inner_wall_filament_id") || this->option("inner_wall_filament_id")->getInt() == 0) + this->option("inner_wall_filament_id", true)->setInt(extruder); + if (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0) + this->option("internal_solid_filament_id", true)->setInt(extruder); + if (!this->has("top_surface_filament_id") || this->option("top_surface_filament_id")->getInt() == 0) + this->option("top_surface_filament_id", true)->setInt(extruder); + if (!this->has("bottom_surface_filament_id") || this->option("bottom_surface_filament_id")->getInt() == 0) + this->option("bottom_surface_filament_id", true)->setInt(extruder); // Don't propagate the current extruder to support. // For non-soluble supports, the default "0" extruder means to use the active extruder, // for soluble supports one certainly does not want to set the extruder to non-soluble. @@ -7420,8 +7477,24 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) this->option("wipe_tower_filament")->setInt(0); } - if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) - this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); + if (this->has("sparse_infill_filament_id")) { + int sparse_infill_filament_id = this->option("sparse_infill_filament_id")->getInt(); + if (sparse_infill_filament_id > 0 && (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0)) + this->option("internal_solid_filament_id", true)->setInt(sparse_infill_filament_id); + } + + const int internal_solid = this->has("internal_solid_filament_id") ? this->option("internal_solid_filament_id")->getInt() : 0; + const int top_surface = this->has("top_surface_filament_id") ? this->option("top_surface_filament_id")->getInt() : 0; + const int bottom_surface = this->has("bottom_surface_filament_id") ? this->option("bottom_surface_filament_id")->getInt() : 0; + + if (internal_solid == 0 && top_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(top_surface); + if (internal_solid == 0 && bottom_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(bottom_surface); + if (top_surface == 0 && internal_solid > 0) + this->option("top_surface_filament_id", true)->setInt(internal_solid); + if (bottom_surface == 0 && internal_solid > 0) + this->option("bottom_surface_filament_id", true)->setInt(internal_solid); if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { @@ -7479,10 +7552,18 @@ void DynamicPrintConfig::normalize_fdm_1() int extruder = this->option("extruder")->getInt(); this->erase("extruder"); if (extruder != 0) { - if (!this->has("sparse_infill_filament")) - this->option("sparse_infill_filament", true)->setInt(extruder); - if (!this->has("wall_filament")) - this->option("wall_filament", true)->setInt(extruder); + if (!this->has("sparse_infill_filament_id") || this->option("sparse_infill_filament_id")->getInt() == 0) + this->option("sparse_infill_filament_id", true)->setInt(extruder); + if (!this->has("outer_wall_filament_id") || this->option("outer_wall_filament_id")->getInt() == 0) + this->option("outer_wall_filament_id", true)->setInt(extruder); + if (!this->has("inner_wall_filament_id") || this->option("inner_wall_filament_id")->getInt() == 0) + this->option("inner_wall_filament_id", true)->setInt(extruder); + if (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0) + this->option("internal_solid_filament_id", true)->setInt(extruder); + if (!this->has("top_surface_filament_id") || this->option("top_surface_filament_id")->getInt() == 0) + this->option("top_surface_filament_id", true)->setInt(extruder); + if (!this->has("bottom_surface_filament_id") || this->option("bottom_surface_filament_id")->getInt() == 0) + this->option("bottom_surface_filament_id", true)->setInt(extruder); // Don't propagate the current extruder to support. // For non-soluble supports, the default "0" extruder means to use the active extruder, // for soluble supports one certainly does not want to set the extruder to non-soluble. @@ -7493,8 +7574,24 @@ void DynamicPrintConfig::normalize_fdm_1() } } - if (!this->has("solid_infill_filament") && this->has("sparse_infill_filament")) - this->option("solid_infill_filament", true)->setInt(this->option("sparse_infill_filament")->getInt()); + if (this->has("sparse_infill_filament_id")) { + int sparse_infill_filament_id = this->option("sparse_infill_filament_id")->getInt(); + if (sparse_infill_filament_id > 0 && (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0)) + this->option("internal_solid_filament_id", true)->setInt(sparse_infill_filament_id); + } + + const int internal_solid = this->has("internal_solid_filament_id") ? this->option("internal_solid_filament_id")->getInt() : 0; + const int top_surface = this->has("top_surface_filament_id") ? this->option("top_surface_filament_id")->getInt() : 0; + const int bottom_surface = this->has("bottom_surface_filament_id") ? this->option("bottom_surface_filament_id")->getInt() : 0; + + if (internal_solid == 0 && top_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(top_surface); + if (internal_solid == 0 && bottom_surface > 0) + this->option("internal_solid_filament_id", true)->setInt(bottom_surface); + if (top_surface == 0 && internal_solid > 0) + this->option("top_surface_filament_id", true)->setInt(internal_solid); + if (bottom_surface == 0 && internal_solid > 0) + this->option("bottom_surface_filament_id", true)->setInt(internal_solid); if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 68d18bccf2c..102f1a7105e 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1010,7 +1010,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, fuzzy_skin_octaves)) ((ConfigOptionFloat, fuzzy_skin_persistence)) ((ConfigOptionFloat, gap_infill_speed)) - ((ConfigOptionInt, sparse_infill_filament)) + ((ConfigOptionInt, sparse_infill_filament_id)) ((ConfigOptionFloatOrPercent, sparse_infill_line_width)) ((ConfigOptionPercent, infill_wall_overlap)) ((ConfigOptionPercent, top_bottom_infill_wall_overlap)) @@ -1036,14 +1036,17 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, ironing_angle)) // Detect bridging perimeters ((ConfigOptionBool, detect_overhang_wall)) - ((ConfigOptionInt, wall_filament)) + ((ConfigOptionInt, outer_wall_filament_id)) + ((ConfigOptionInt, inner_wall_filament_id)) ((ConfigOptionFloatOrPercent, inner_wall_line_width)) ((ConfigOptionFloat, inner_wall_speed)) // Total number of perimeters. ((ConfigOptionInt, wall_loops)) ((ConfigOptionBool, alternate_extra_wall)) ((ConfigOptionFloat, minimum_sparse_infill_area)) - ((ConfigOptionInt, solid_infill_filament)) + ((ConfigOptionInt, internal_solid_filament_id)) + ((ConfigOptionInt, top_surface_filament_id)) + ((ConfigOptionInt, bottom_surface_filament_id)) ((ConfigOptionFloatOrPercent, internal_solid_infill_line_width)) ((ConfigOptionFloat, internal_solid_infill_speed)) // Detect thin walls. diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index f649c0e2b1e..df7cbcdbcd9 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -223,7 +223,7 @@ void PrintObject::_transform_hole_to_polyholes() bool twist = this->m_layers[layer_idx]->m_regions[region_idx]->region().config().hole_to_polyhole_twisted.value; if (diameter_max - diameter_min < max_variation * 2 && diameter_line_max - diameter_line_min < max_variation * 2) { layerid2center[layer_idx].emplace_back( - std::tuple{center, diameter_max, layer->m_regions[region_idx]->region().config().wall_filament.value, max_variation, twist}, & hole); + std::tuple{center, diameter_max, layer->m_regions[region_idx]->region().config().outer_wall_filament_id.value, max_variation, twist}, & hole); } } } @@ -1092,8 +1092,10 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "bottom_shell_thickness" || opt_key == "top_shell_thickness" || opt_key == "minimum_sparse_infill_area" - || opt_key == "sparse_infill_filament" - || opt_key == "solid_infill_filament" + || opt_key == "sparse_infill_filament_id" + || opt_key == "internal_solid_filament_id" + || opt_key == "top_surface_filament_id" + || opt_key == "bottom_surface_filament_id" || opt_key == "sparse_infill_line_width" || opt_key == "skin_infill_line_width" || opt_key == "skeleton_infill_line_width" @@ -1151,7 +1153,8 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPrepareInfill); } else if ( opt_key == "outer_wall_line_width" - || opt_key == "wall_filament" + || opt_key == "outer_wall_filament_id" + || opt_key == "inner_wall_filament_id" || opt_key == "fuzzy_skin" || opt_key == "fuzzy_skin_thickness" || opt_key == "fuzzy_skin_point_distance" @@ -3190,6 +3193,15 @@ static void clamp_exturder_to_default(ConfigOptionInt &opt, size_t num_total_fil opt.value = 1; } +// ORCA: feature filament selectors use 0 = "Default" (inherit the active object/part filament). +// Once the object/part/layer-range overrides are resolved, any remaining "Default" or invalid +// value falls back to filament 1. +static void clamp_feature_filament_to_valid(ConfigOptionInt &opt, size_t num_total_filaments) +{ + if (opt.value <= 0 || opt.value > (int)num_total_filaments) + opt.value = 1; +} + PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObjectConfig &default_object_config, const ModelObject &object, size_t num_extruders) { PrintObjectConfig config = default_object_config; @@ -3205,55 +3217,124 @@ PrintObjectConfig PrintObject::object_config_from_model_object(const PrintObject } const std::string key_extruder { "extruder" }; -static constexpr const std::initializer_list keys_extruders { "sparse_infill_filament"sv, "solid_infill_filament"sv, "wall_filament"sv }; +static constexpr const std::initializer_list keys_extruders { + "sparse_infill_filament_id"sv, + "internal_solid_filament_id"sv, + "top_surface_filament_id"sv, + "bottom_surface_filament_id"sv, + "outer_wall_filament_id"sv, + "inner_wall_filament_id"sv +}; -static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in) +struct FeatureFilamentOverrideMask { - // 1) Copy the "extruder key to sparse_infill_filament and wall_filament. + bool sparse_infill_filament_id = false; + bool internal_solid_filament_id = false; + bool top_surface_filament_id = false; + bool bottom_surface_filament_id = false; + bool outer_wall_filament_id = false; + bool inner_wall_filament_id = false; +}; + +static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides) +{ + // 1) Explicit feature filament values take precedence over base extruder fallback. auto *opt_extruder = in.opt(key_extruder); - if (opt_extruder) - if (int extruder = opt_extruder->value; extruder != 0) { - // Not a default extruder. - out.sparse_infill_filament.value = extruder; - out.solid_infill_filament.value = extruder; - out.wall_filament.value = extruder; - } + int base_extruder = (opt_extruder != nullptr) ? opt_extruder->value : 0; + // 2) Copy the rest of the values. for (auto it = in.cbegin(); it != in.cend(); ++ it) if (it->first != key_extruder) if (ConfigOption* my_opt = out.option(it->first, false); my_opt != nullptr) { if (one_of(it->first, keys_extruders)) { - // Ignore "default" extruders. + // "Default" (0) clears explicit override for this scope and lets fallback apply. int extruder = static_cast(it->second.get())->value; - if (extruder > 0) + if (extruder > 0) { my_opt->setInt(extruder); + if (it->first == "sparse_infill_filament_id") + feature_overrides.sparse_infill_filament_id = true; + else if (it->first == "internal_solid_filament_id") + feature_overrides.internal_solid_filament_id = true; + else if (it->first == "top_surface_filament_id") + feature_overrides.top_surface_filament_id = true; + else if (it->first == "bottom_surface_filament_id") + feature_overrides.bottom_surface_filament_id = true; + else if (it->first == "outer_wall_filament_id") + feature_overrides.outer_wall_filament_id = true; + else if (it->first == "inner_wall_filament_id") + feature_overrides.inner_wall_filament_id = true; + } else { + if (it->first == "sparse_infill_filament_id") + feature_overrides.sparse_infill_filament_id = false; + else if (it->first == "internal_solid_filament_id") + feature_overrides.internal_solid_filament_id = false; + else if (it->first == "top_surface_filament_id") + feature_overrides.top_surface_filament_id = false; + else if (it->first == "bottom_surface_filament_id") + feature_overrides.bottom_surface_filament_id = false; + else if (it->first == "outer_wall_filament_id") + feature_overrides.outer_wall_filament_id = false; + else if (it->first == "inner_wall_filament_id") + feature_overrides.inner_wall_filament_id = false; + } } else my_opt->set(it->second.get()); } + + // 3) Apply base extruder only to features that were not explicitly overridden. + if (base_extruder > 0) { + if (!feature_overrides.sparse_infill_filament_id) + out.sparse_infill_filament_id.value = base_extruder; + if (!feature_overrides.internal_solid_filament_id) + out.internal_solid_filament_id.value = base_extruder; + if (!feature_overrides.top_surface_filament_id) + out.top_surface_filament_id.value = base_extruder; + if (!feature_overrides.bottom_surface_filament_id) + out.bottom_surface_filament_id.value = base_extruder; + if (!feature_overrides.outer_wall_filament_id) + out.outer_wall_filament_id.value = base_extruder; + if (!feature_overrides.inner_wall_filament_id) + out.inner_wall_filament_id.value = base_extruder; + } } 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) { PrintRegionConfig config = default_or_parent_region_config; + FeatureFilamentOverrideMask feature_overrides; + + // For model parts, non-zero values coming from the print defaults should stay explicit. + if (volume.is_model_part()) { + feature_overrides.sparse_infill_filament_id = (config.sparse_infill_filament_id.value > 0); + feature_overrides.internal_solid_filament_id = (config.internal_solid_filament_id.value > 0); + feature_overrides.top_surface_filament_id = (config.top_surface_filament_id.value > 0); + feature_overrides.bottom_surface_filament_id = (config.bottom_surface_filament_id.value > 0); + feature_overrides.outer_wall_filament_id = (config.outer_wall_filament_id.value > 0); + feature_overrides.inner_wall_filament_id = (config.inner_wall_filament_id.value > 0); + } + if (volume.is_model_part()) { // default_or_parent_region_config contains the Print's PrintRegionConfig. // Override with ModelObject's PrintRegionConfig values. - apply_to_print_region_config(config, volume.get_object()->config.get()); + apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides); } else { // default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config. } - apply_to_print_region_config(config, volume.config.get()); + apply_to_print_region_config(config, volume.config.get(), feature_overrides); if (! volume.material_id().empty()) - apply_to_print_region_config(config, volume.material()->config.get()); + apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides); if (layer_range_config != nullptr) { // Not applicable to modifiers. assert(volume.is_model_part()); - apply_to_print_region_config(config, *layer_range_config); + apply_to_print_region_config(config, *layer_range_config, feature_overrides); } - // Clamp invalid extruders to the default extruder (with index 1). - clamp_exturder_to_default(config.sparse_infill_filament, num_extruders); - clamp_exturder_to_default(config.wall_filament, num_extruders); - clamp_exturder_to_default(config.solid_infill_filament, num_extruders); + // Resolve feature defaults and clamp invalid extruders to index 1. + clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.outer_wall_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.inner_wall_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.internal_solid_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.top_surface_filament_id, num_extruders); + clamp_feature_filament_to_valid(config.bottom_surface_filament_id, num_extruders); if (config.sparse_infill_density.value < 0.00011f) // Switch of infill for very low infill rates, also avoid division by zero in infill generator for these very low rates. // See GH issue #5910. @@ -3316,9 +3397,12 @@ SlicingParameters PrintObject::slicing_parameters(const DynamicPrintConfig &full object_config.brim_type != btNoBrim && object_config.brim_width > 0., object_extruders); for (const std::pair &range_and_config : model_object.layer_config_ranges) - if (range_and_config.second.has("wall_filament") || - range_and_config.second.has("sparse_infill_filament") || - range_and_config.second.has("solid_infill_filament")) + if (range_and_config.second.has("outer_wall_filament_id") || + range_and_config.second.has("inner_wall_filament_id") || + range_and_config.second.has("sparse_infill_filament_id") || + range_and_config.second.has("internal_solid_filament_id") || + range_and_config.second.has("top_surface_filament_id") || + range_and_config.second.has("bottom_surface_filament_id")) PrintRegion::collect_object_printing_extruders( print_config, region_config_from_model_volume(default_region_config, &range_and_config.second.get(), *model_volume, filament_extruders), @@ -4285,9 +4369,10 @@ void PrintObject::combine_infill() // Limit the number of combined layers to the maximum height allowed by this regions' nozzle. //FIXME limit the layer height to max_layer_height - double nozzle_diameter = this->print()->config().nozzle_diameter.get_at(region.config().wall_filament.value - 1); - nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament.value - 1)); - nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().solid_infill_filament.value - 1)); + double nozzle_diameter = this->print()->config().nozzle_diameter.get_at(region.config().outer_wall_filament_id.value - 1); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().inner_wall_filament_id.value - 1)); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1)); + nozzle_diameter = std::min(nozzle_diameter, this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1)); //Orca: Limit combination of infill to up to infill_combination_max_layer_height const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 688c0f2aee4..5a225bff655 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -2575,7 +2575,7 @@ static bool apply_mixed_region_surface_offsets(PrintObject &print_object) if (layerm == nullptr || layerm->slices.empty()) continue; - const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value)); + const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().outer_wall_filament_id.value)); if (!mixed_mgr.is_mixed(filament_id, num_physical)) continue; @@ -2769,7 +2769,7 @@ static std::vector> whole_object_local_z_segmentation_by if (layerm == nullptr || layerm->slices.empty()) continue; - const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().wall_filament.value)); + const unsigned int filament_id = unsigned(std::max(0, layerm->region().config().outer_wall_filament_id.value)); if (!mixed_mgr.is_mixed(filament_id, num_physical)) continue; if (filament_id >= segmentation[layer_id].size()) @@ -4469,7 +4469,7 @@ static inline void apply_mm_segmentation(PrintObject &print_object, std::vector< 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; + if (const int cfg_wall = parent_print_region.config().outer_wall_filament_id.value; cfg_wall >= 1 && cfg_wall <= int(by_extruder.size())) self_extruder_id = cfg_wall; if (clamp_parent_to_geometry && default_bbox.defined && parent_layer_region_bbox.overlap(default_bbox)) @@ -4732,7 +4732,7 @@ static std::vector collect_surface_emboss_mixe 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)) + if (!mixed_mgr.is_mixed(unsigned(std::max(0, volume_region.region->config().outer_wall_filament_id.value)), num_physical)) continue; out.push_back({ volume, region_id }); @@ -4767,7 +4767,7 @@ static void export_surface_emboss_mixed_layer_svg( }) != candidates.end(); SVG::ExPolygonAttributes attrs( - "region " + std::to_string(region_id) + " wall=" + std::to_string(layerm->region().config().wall_filament.value), + "region " + std::to_string(region_id) + " wall=" + std::to_string(layerm->region().config().outer_wall_filament_id.value), is_candidate ? "#3b82f6" : "#bfc5cc", is_candidate ? 0.35f : 0.14f); attrs.outline_width = scale_(0.05f); @@ -4829,9 +4829,9 @@ static void dump_surface_emboss_mixed_layer_state( 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 + << " wall=" << layerm->region().config().outer_wall_filament_id.value + << " sparse=" << layerm->region().config().sparse_infill_filament_id.value + << " solid=" << layerm->region().config().internal_solid_filament_id.value << " area=" << slice_area; append_surface_emboss_mixed_debug_line(print_object, line.str()); } @@ -4848,14 +4848,14 @@ static void dump_surface_emboss_mixed_layer_state( << " candidate region=" << candidate.region_id << " volume_name=" << candidate.volume->name << " volume_extruder=" << candidate.volume->extruder_id() - << " cfg_wall=" << layerm->region().config().wall_filament.value + << " cfg_wall=" << layerm->region().config().outer_wall_filament_id.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; + const int cfg_wall = layerm->region().config().outer_wall_filament_id.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; @@ -4949,7 +4949,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object continue; processed_region_ids.emplace_back(region_id); - const unsigned int filament_id = unsigned(std::max(0, volume_region.region->config().wall_filament.value)); + const unsigned int filament_id = unsigned(std::max(0, volume_region.region->config().outer_wall_filament_id.value)); if (!mixed_mgr.is_mixed(filament_id, num_physical)) continue; @@ -4982,7 +4982,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object << " region=" << region_id << " volume_name=" << volume->name << " volume_extruder=" << volume->extruder_id() - << " cfg_wall=" << volume_region.region->config().wall_filament.value + << " cfg_wall=" << volume_region.region->config().outer_wall_filament_id.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)); @@ -5007,7 +5007,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object 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)) + if (target_layerm->region().config().outer_wall_filament_id.value == int(filament_id)) continue; ExPolygons stolen = intersection_ex(target_layerm->slices.surfaces, override_mask); @@ -5024,7 +5024,7 @@ static bool apply_surface_emboss_mixed_region_override(PrintObject &print_object << " layer=" << layer_id << " emboss_region=" << region_id << " from_region=" << target_region_id - << " from_wall=" << target_layerm->region().config().wall_filament.value + << " from_wall=" << target_layerm->region().config().outer_wall_filament_id.value << " stolen_area=" << std::abs(area(stolen)); append_surface_emboss_mixed_debug_line(print_object, line.str()); diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index f4b82faf4ab..1d4e6a3017c 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -18,14 +18,16 @@ bool internal_solid_infill_uses_sparse_filament(const PrintRegionConfig &config, unsigned int PrintRegion::extruder(FlowRole role) const { size_t extruder = 0; - if (role == frPerimeter || role == frExternalPerimeter) - extruder = m_config.wall_filament; + if (role == frPerimeter) + extruder = m_config.inner_wall_filament_id; + else if (role == frExternalPerimeter) + extruder = m_config.outer_wall_filament_id; else if (role == frInfill) - extruder = m_config.sparse_infill_filament; + extruder = m_config.sparse_infill_filament_id; else if (role == frSolidInfill) - extruder = internal_solid_infill_uses_sparse_filament(m_config, role) ? m_config.sparse_infill_filament : m_config.solid_infill_filament; + extruder = internal_solid_infill_uses_sparse_filament(m_config, role) ? m_config.sparse_infill_filament_id : m_config.internal_solid_filament_id; else if (role == frTopSolidInfill) - extruder = m_config.solid_infill_filament; + extruder = m_config.top_surface_filament_id; else throw Slic3r::InvalidArgument("Unknown role"); return extruder; @@ -64,9 +66,12 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he coordf_t PrintRegion::nozzle_dmr_avg(const PrintConfig &print_config) const { - return (print_config.nozzle_diameter.get_at(m_config.wall_filament.value - 1) + - print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament.value - 1) + - print_config.nozzle_diameter.get_at(m_config.solid_infill_filament.value - 1)) / 3.; + return (print_config.nozzle_diameter.get_at(m_config.outer_wall_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.inner_wall_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.sparse_infill_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.internal_solid_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.top_surface_filament_id.value - 1) + + print_config.nozzle_diameter.get_at(m_config.bottom_surface_filament_id.value - 1)) / 6.; } coordf_t PrintRegion::bridging_height_avg(const PrintConfig &print_config) const @@ -83,12 +88,19 @@ void PrintRegion::collect_object_printing_extruders(const PrintConfig &print_con int i = std::max(0, extruder_id - 1); object_extruders.emplace_back((i >= num_extruders) ? 0 : i); }; - if (region_config.wall_loops.value > 0 || has_brim) - emplace_extruder(region_config.wall_filament); + if (region_config.wall_loops.value > 0 || has_brim) { + emplace_extruder(region_config.outer_wall_filament_id); + if (region_config.wall_loops.value > 1) + emplace_extruder(region_config.inner_wall_filament_id); + } if (region_config.sparse_infill_density.value > 0) - emplace_extruder(region_config.sparse_infill_filament); - if (region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0) - emplace_extruder(region_config.solid_infill_filament); + emplace_extruder(region_config.sparse_infill_filament_id); + if (region_config.sparse_infill_density.value > 0 || region_config.top_shell_layers.value > 0 || region_config.bottom_shell_layers.value > 0) + emplace_extruder(region_config.internal_solid_filament_id); + if (region_config.top_shell_layers.value > 0) + emplace_extruder(region_config.top_surface_filament_id); + if (region_config.bottom_shell_layers.value > 0) + emplace_extruder(region_config.bottom_surface_filament_id); } void PrintRegion::collect_object_printing_extruders(const Print &print, std::vector &object_extruders) const @@ -98,9 +110,12 @@ void PrintRegion::collect_object_printing_extruders(const Print &print, std::vec #ifndef NDEBUG // BBS auto num_extruders = int(print.config().filament_diameter.size()); - assert(this->config().wall_filament <= num_extruders); - assert(this->config().sparse_infill_filament <= num_extruders); - assert(this->config().solid_infill_filament <= num_extruders); + assert(this->config().outer_wall_filament_id <= num_extruders); + assert(this->config().inner_wall_filament_id <= num_extruders); + assert(this->config().sparse_infill_filament_id <= num_extruders); + assert(this->config().internal_solid_filament_id <= num_extruders); + assert(this->config().top_surface_filament_id <= num_extruders); + assert(this->config().bottom_surface_filament_id <= num_extruders); #endif collect_object_printing_extruders(print.config(), this->config(), print.has_brim(), object_extruders); } diff --git a/src/libslic3r/Support/SupportCommon.cpp b/src/libslic3r/Support/SupportCommon.cpp index 0de4067770a..903e9a9614a 100644 --- a/src/libslic3r/Support/SupportCommon.cpp +++ b/src/libslic3r/Support/SupportCommon.cpp @@ -55,7 +55,7 @@ void remove_bridges_from_contacts( // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. Polygons lower_grown_slices = expand(lower_layer.lslices, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament - 1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id - 1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 7580976ac99..1d04e954afa 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1224,7 +1224,7 @@ namespace SupportMaterialInternal { // Surface supporting this layer, expanded by 0.5 * nozzle_diameter, as we consider this kind of overhang to be sufficiently supported. Polygons lower_grown_slices = expand(lower_layer_polygons, //FIXME to mimic the decision in the perimeter generator, we should use half the external perimeter width. - 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().wall_filament-1))), + 0.5f * float(scale_(print_config.nozzle_diameter.get_at(layerm.region().config().outer_wall_filament_id-1))), SUPPORT_SURFACES_OFFSET_PARAMETERS); // Collect perimeters of this layer. //FIXME split_at_first_point() could split a bridge mid-way diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 11c2bd16482..b0fc711f343 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -557,9 +557,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field(el, have_perimeters); bool have_infill = config->option("sparse_infill_density")->value > 0; - // sparse_infill_filament uses the same logic as in Print::extruders() + // sparse_infill_filament_id uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", - "minimum_sparse_infill_area", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) + "minimum_sparse_infill_area", "sparse_infill_filament_id", "infill_anchor_max","infill_shift_step","sparse_infill_rotate_template","symmetric_infill_y_axis"}) toggle_line(el, have_infill); bool have_combined_infill = config->opt_bool("infill_combination") && have_infill; @@ -614,7 +614,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : { "infill_direction", "sparse_infill_line_width", "fill_multiline","gap_fill_target","filter_out_gap_fill","infill_wall_overlap", "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle", - "solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "solid_infill_filament", + "solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", }) toggle_field(el, have_infill || has_solid_infill); @@ -653,8 +653,9 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co bool have_brim_width = (config->opt_enum("brim_type") != btNoBrim) && config->opt_enum("brim_type") != btAutoBrim && config->opt_enum("brim_type") != btPainted; toggle_field("brim_width", have_brim_width); - // wall_filament uses the same logic as in Print::extruders() - toggle_field("wall_filament", have_perimeters || have_brim); + // Wall filament selectors use the same logic as in Print::extruders(). + toggle_field("outer_wall_filament_id", have_perimeters || have_brim); + toggle_field("inner_wall_filament_id", have_perimeters || have_brim); bool have_brim_ear = (config->opt_enum("brim_type") == btEar); const auto brim_width = config->opt_float("brim_width"); @@ -760,9 +761,6 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co for (auto el : { "prime_tower_width", "prime_tower_brim_width"}) toggle_line(el, have_prime_tower); - for (auto el : {"wall_filament", "sparse_infill_filament", "solid_infill_filament", "wipe_tower_filament"}) - toggle_line(el, !bSEMM); - bool purge_in_primetower = preset_bundle->printers.get_edited_preset().config.opt_bool("purge_in_prime_tower"); for (auto el : {"wipe_tower_cone_angle", diff --git a/src/slic3r/GUI/GUI_Factories.cpp b/src/slic3r/GUI/GUI_Factories.cpp index 87a6d909cb5..5ef5b1846b6 100644 --- a/src/slic3r/GUI/GUI_Factories.cpp +++ b/src/slic3r/GUI/GUI_Factories.cpp @@ -146,7 +146,7 @@ std::map> SettingsFactory::PART_CAT }}, { L("Strength"), {{"wall_loops", "",1},{"top_shell_layers", L("Top Solid Layers"),1},{"top_shell_thickness", L("Top Minimum Shell Thickness"),1},{"top_surface_density", L("Top Surface Density"),1}, {"bottom_shell_layers", L("Bottom Solid Layers"),1}, {"bottom_shell_thickness", L("Bottom Minimum Shell Thickness"),1},{"bottom_surface_density", L("Bottom Surface Density"),1}, - {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"sparse_infill_filament", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, + {"sparse_infill_density", "",1},{"sparse_infill_pattern", "",1},{"sparse_infill_filament_id", "",1},{"lateral_lattice_angle_1", "",1},{"lateral_lattice_angle_2", "",1},{"infill_overhang_angle", "",1},{"infill_anchor", "",1},{"infill_anchor_max", "",1},{"top_surface_pattern", "",1},{"bottom_surface_pattern", "",1}, {"internal_solid_infill_pattern", "",1}, {"align_infill_direction_to_model", "", 1}, {"extra_solid_infills", "", 1}, {"infill_combination", "",1}, {"infill_combination_max_layer_height", "",1}, {"infill_wall_overlap", "",1},{"top_bottom_infill_wall_overlap", "",1}, {"solid_infill_direction", "",1}, {"infill_direction", "",1}, {"bridge_angle", "",1}, {"internal_bridge_angle", "",1}, {"minimum_sparse_infill_area", "",1} diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index b1130ea33c1..be251b4a450 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -701,7 +701,8 @@ void ObjectList::update_filament_values_for_items(const size_t filaments_count) } m_objects_model->SetExtruder(extruder, item); - static const char *keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + static const char *keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament"}; for (auto key : keys) if (object->config.has(key) && object->config.opt_int(key) > filaments_count) @@ -877,7 +878,8 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz } m_objects_model->SetExtruder(extruder, item); - static const char* keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + static const char* keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament"}; for (auto key : keys) { if (object->config.has(key)) { @@ -5902,9 +5904,9 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) else config.set_key_value("extruder", new ConfigOptionInt(new_extruder)); - // config.set("sparse_infill_filament", new_extruder); - // config.set("solid_infill_filament", new_extruder); - // config.set("wall_filament", new_extruder); + // config.set("sparse_infill_filament_id", new_extruder); + // config.set("internal_solid_filament_id", new_extruder); + // config.set("outer_wall_filament_id", new_extruder); wxGetApp().obj_list()->update_selections(); diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index 435b72d0933..87142ca5a41 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -80,7 +80,7 @@ namespace { template int resolve_sparse_infill_filament(const ConfigLike &config, int inherited_sparse_infill_filament) { - const ConfigOption *sparse_opt = config.option("sparse_infill_filament"); + const ConfigOption *sparse_opt = config.option("sparse_infill_filament_id"); return sparse_opt != nullptr && sparse_opt->getInt() > 0 ? sparse_opt->getInt() : inherited_sparse_infill_filament; } @@ -1370,9 +1370,16 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const const DynamicPrintConfig& glb_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; int glb_support_intf_extr = glb_config.opt_int("support_interface_filament"); int glb_support_extr = glb_config.opt_int("support_filament"); - int glb_wall_extr = glb_config.opt_int("wall_filament"); - int glb_sparse_infill_extr = glb_config.opt_int("sparse_infill_filament"); - int glb_solid_infill_extr = glb_config.opt_int("solid_infill_filament"); + int glb_outer_wall_extr = glb_config.opt_int("outer_wall_filament_id"); + int glb_inner_wall_extr = glb_config.opt_int("inner_wall_filament_id"); + if (glb_outer_wall_extr == 0) glb_outer_wall_extr = glb_inner_wall_extr; + if (glb_inner_wall_extr == 0) glb_inner_wall_extr = glb_outer_wall_extr; + int glb_sparse_infill_extr = glb_config.opt_int("sparse_infill_filament_id"); + int glb_internal_solid_extr = glb_config.opt_int("internal_solid_filament_id"); + int glb_top_surface_extr = glb_config.opt_int("top_surface_filament_id"); + int glb_bottom_surface_extr = glb_config.opt_int("bottom_surface_filament_id"); + if (glb_top_surface_extr == 0) glb_top_surface_extr = glb_internal_solid_extr; + if (glb_bottom_surface_extr == 0) glb_bottom_surface_extr = glb_internal_solid_extr; bool glb_support = glb_config.opt_bool("enable_support"); glb_support |= glb_config.opt_int("raft_layers") > 0; @@ -1426,25 +1433,59 @@ std::vector PartPlate::get_extruders(bool conside_custom_gcode) const plate_extruders.push_back(glb_support_extr); } - int obj_wall_extr = glb_wall_extr; - const ConfigOption* wall_opt = mo->config.option("wall_filament"); - if (wall_opt != nullptr) - obj_wall_extr = wall_opt->getInt(); - if (obj_wall_extr != 1) - plate_extruders.push_back(obj_wall_extr); + int obj_outer_wall_extr = 0; + if (const ConfigOption* wall_opt = mo->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr == 0) + if (const ConfigOption* wall_opt = mo->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr != 0) + plate_extruders.push_back(obj_outer_wall_extr); + else if (glb_outer_wall_extr != 0) + plate_extruders.push_back(glb_outer_wall_extr); + + int obj_inner_wall_extr = 0; + if (const ConfigOption* wall_opt = mo->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr == 0) + if (const ConfigOption* wall_opt = mo->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr != 0) + plate_extruders.push_back(obj_inner_wall_extr); + else if (glb_inner_wall_extr != 0) + plate_extruders.push_back(glb_inner_wall_extr); const int object_sparse_infill_extr = resolve_sparse_infill_filament(mo->config, glb_sparse_infill_extr); - if (object_sparse_infill_extr != 1) + if (object_sparse_infill_extr != 0) plate_extruders.push_back(object_sparse_infill_extr); - int obj_solid_infill_extr = 1; - const ConfigOption* solid_infill_opt = mo->config.option("solid_infill_filament"); - if (solid_infill_opt != nullptr) - obj_solid_infill_extr = solid_infill_opt->getInt(); - if (obj_solid_infill_extr != 1) - plate_extruders.push_back(obj_solid_infill_extr); - else if (glb_solid_infill_extr != 1) - plate_extruders.push_back(glb_solid_infill_extr); + int obj_internal_solid_extr = 0; + if (const ConfigOption* solid_opt = mo->config.option("internal_solid_filament_id"); solid_opt != nullptr) + obj_internal_solid_extr = solid_opt->getInt(); + if (obj_internal_solid_extr != 0) + plate_extruders.push_back(obj_internal_solid_extr); + else if (glb_internal_solid_extr != 0) + plate_extruders.push_back(glb_internal_solid_extr); + + int obj_top_surface_extr = 0; + if (const ConfigOption* top_opt = mo->config.option("top_surface_filament_id"); top_opt != nullptr) + obj_top_surface_extr = top_opt->getInt(); + if (obj_top_surface_extr == 0) + obj_top_surface_extr = obj_internal_solid_extr; + if (obj_top_surface_extr != 0) + plate_extruders.push_back(obj_top_surface_extr); + else if (glb_top_surface_extr != 0) + plate_extruders.push_back(glb_top_surface_extr); + + int obj_bottom_surface_extr = 0; + if (const ConfigOption* bottom_opt = mo->config.option("bottom_surface_filament_id"); bottom_opt != nullptr) + obj_bottom_surface_extr = bottom_opt->getInt(); + if (obj_bottom_surface_extr == 0) + obj_bottom_surface_extr = obj_internal_solid_extr; + if (obj_bottom_surface_extr != 0) + plate_extruders.push_back(obj_bottom_surface_extr); + else if (glb_bottom_surface_extr != 0) + plate_extruders.push_back(glb_bottom_surface_extr); } @@ -1484,9 +1525,16 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D // if 3mf file int glb_support_intf_extr = full_config.opt_int("support_interface_filament"); int glb_support_extr = full_config.opt_int("support_filament"); - int glb_wall_extr = full_config.opt_int("wall_filament"); - int glb_sparse_infill_extr = full_config.opt_int("sparse_infill_filament"); - int glb_solid_infill_extr = full_config.opt_int("solid_infill_filament"); + int glb_outer_wall_extr = full_config.opt_int("outer_wall_filament_id"); + int glb_inner_wall_extr = full_config.opt_int("inner_wall_filament_id"); + if (glb_outer_wall_extr == 0) glb_outer_wall_extr = glb_inner_wall_extr; + if (glb_inner_wall_extr == 0) glb_inner_wall_extr = glb_outer_wall_extr; + int glb_sparse_infill_extr = full_config.opt_int("sparse_infill_filament_id"); + int glb_internal_solid_extr = full_config.opt_int("internal_solid_filament_id"); + int glb_top_surface_extr = full_config.opt_int("top_surface_filament_id"); + int glb_bottom_surface_extr = full_config.opt_int("bottom_surface_filament_id"); + if (glb_top_surface_extr == 0) glb_top_surface_extr = glb_internal_solid_extr; + if (glb_bottom_surface_extr == 0) glb_bottom_surface_extr = glb_internal_solid_extr; bool glb_support = full_config.opt_bool("enable_support"); glb_support |= full_config.opt_int("raft_layers") > 0; @@ -1570,25 +1618,59 @@ std::vector PartPlate::get_extruders_under_cli(bool conside_custom_gcode, D else if (glb_support_extr != 0) plate_extruders.push_back(glb_support_extr); - int obj_wall_extr = glb_wall_extr; - const ConfigOption* wall_opt = object->config.option("wall_filament"); - if (wall_opt != nullptr) - obj_wall_extr = wall_opt->getInt(); - if (obj_wall_extr != 1) - plate_extruders.push_back(obj_wall_extr); + int obj_outer_wall_extr = 0; + if (const ConfigOption* wall_opt = object->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr == 0) + if (const ConfigOption* wall_opt = object->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_outer_wall_extr = wall_opt->getInt(); + if (obj_outer_wall_extr != 0) + plate_extruders.push_back(obj_outer_wall_extr); + else if (glb_outer_wall_extr != 0) + plate_extruders.push_back(glb_outer_wall_extr); + + int obj_inner_wall_extr = 0; + if (const ConfigOption* wall_opt = object->config.option("inner_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr == 0) + if (const ConfigOption* wall_opt = object->config.option("outer_wall_filament_id"); wall_opt != nullptr) + obj_inner_wall_extr = wall_opt->getInt(); + if (obj_inner_wall_extr != 0) + plate_extruders.push_back(obj_inner_wall_extr); + else if (glb_inner_wall_extr != 0) + plate_extruders.push_back(glb_inner_wall_extr); const int object_sparse_infill_extr = resolve_sparse_infill_filament(object->config, glb_sparse_infill_extr); - if (object_sparse_infill_extr != 1) + if (object_sparse_infill_extr != 0) plate_extruders.push_back(object_sparse_infill_extr); - int obj_solid_infill_extr = 1; - const ConfigOption* solid_infill_opt = object->config.option("solid_infill_filament"); - if (solid_infill_opt != nullptr) - obj_solid_infill_extr = solid_infill_opt->getInt(); - if (obj_solid_infill_extr != 1) - plate_extruders.push_back(obj_solid_infill_extr); - else if (glb_solid_infill_extr != 1) - plate_extruders.push_back(glb_solid_infill_extr); + int obj_internal_solid_extr = 0; + if (const ConfigOption* solid_opt = object->config.option("internal_solid_filament_id"); solid_opt != nullptr) + obj_internal_solid_extr = solid_opt->getInt(); + if (obj_internal_solid_extr != 0) + plate_extruders.push_back(obj_internal_solid_extr); + else if (glb_internal_solid_extr != 0) + plate_extruders.push_back(glb_internal_solid_extr); + + int obj_top_surface_extr = 0; + if (const ConfigOption* top_opt = object->config.option("top_surface_filament_id"); top_opt != nullptr) + obj_top_surface_extr = top_opt->getInt(); + if (obj_top_surface_extr == 0) + obj_top_surface_extr = obj_internal_solid_extr; + if (obj_top_surface_extr != 0) + plate_extruders.push_back(obj_top_surface_extr); + else if (glb_top_surface_extr != 0) + plate_extruders.push_back(glb_top_surface_extr); + + int obj_bottom_surface_extr = 0; + if (const ConfigOption* bottom_opt = object->config.option("bottom_surface_filament_id"); bottom_opt != nullptr) + obj_bottom_surface_extr = bottom_opt->getInt(); + if (obj_bottom_surface_extr == 0) + obj_bottom_surface_extr = obj_internal_solid_extr; + if (obj_bottom_surface_extr != 0) + plate_extruders.push_back(obj_bottom_surface_extr); + else if (glb_bottom_surface_extr != 0) + plate_extruders.push_back(glb_bottom_surface_extr); } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index cc0e8ecef11..90e80661cac 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -247,24 +247,20 @@ static void collect_filament_slots_from_config( int num_filaments, std::set& used_slots) { - static const std::vector keys_1based = { - "wall_filament", - "sparse_infill_filament", - "solid_infill_filament" - }; - for (const char* key : keys_1based) - { - const ConfigOptionInt* option = config.option(key); - if (option != nullptr && option->value >= 1 && option->value <= num_filaments) - used_slots.insert(option->value - 1); - } - - static const std::vector keys_0based = { + // All feature filament keys use 0 = "Default" (inherit the active object/part filament), + // so only explicit selections (>= 1) mark a slot as used. + static const std::vector keys_with_default = { + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id", "support_filament", "support_interface_filament", "wipe_tower_filament" }; - for (const char* key : keys_0based) + for (const char* key : keys_with_default) { const ConfigOptionInt* option = config.option(key); if (option != nullptr && option->value >= 1 && option->value <= num_filaments) @@ -288,12 +284,15 @@ static void collect_filament_slots_from_model_config( used_slots.insert(extruder_id - 1); } - // Per-object feature-specific keys (wall_filament, etc.) may be + // Per-object feature-specific keys (outer_wall_filament_id, etc.) may be // overridden independently of the object's primary extruder. static const std::vector feature_keys = { - "wall_filament", - "sparse_infill_filament", - "solid_infill_filament", + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id", "support_filament", "support_interface_filament", "wipe_tower_filament" @@ -1247,54 +1246,6 @@ struct DynamicFilamentList : DynamicList } }; -struct DynamicFilamentList1Based : DynamicFilamentList -{ - void apply_on(Choice *c) override - { - if (items.empty()) - update(true); - auto cb = dynamic_cast(c->window); - auto n = cb->GetSelection(); - cb->Clear(); - for (auto i : items) { - cb->Append(i.first, *i.second); - } - if (n < cb->GetCount()) - cb->SetSelection(n); - } - wxString get_value(int index) override - { - wxString str; - str << index+1; - return str; - } - int index_of(wxString value) override - { - long n = 0; - if(!value.ToLong(&n)) - return -1; - --n; - return (n >= 0 && n <= items.size()) ? int(n) : -1; - } - void update(bool force = false) - { - items.clear(); - if (!force && m_choices.empty()) - return; - auto icons = get_extruder_color_icons(true); - auto presets = wxGetApp().preset_bundle->filament_presets; - for (int i = 0; i < presets.size(); ++i) { - wxString str; - std::string type; - wxGetApp().preset_bundle->filaments.find_preset(presets[i])->get_filament_type(type); - str << type; - items.push_back({str, icons[i]}); - } - DynamicList::update(); - } - -}; - class MixedFilamentColorMatchDialog : public DPIDialog { public: @@ -1819,7 +1770,6 @@ MixedColorMatchRecipeResult prompt_best_color_match_recipe(wxWindow* } static DynamicFilamentList dynamic_filament_list; -static DynamicFilamentList1Based dynamic_filament_list_1_based; static wxString nozzle_type_key_to_label(const std::string& key) { @@ -1839,9 +1789,12 @@ Sidebar::Sidebar(Plater *parent) { Choice::register_dynamic_list("support_filament", &dynamic_filament_list); Choice::register_dynamic_list("support_interface_filament", &dynamic_filament_list); - Choice::register_dynamic_list("wall_filament", &dynamic_filament_list_1_based); - Choice::register_dynamic_list("sparse_infill_filament", &dynamic_filament_list_1_based); - Choice::register_dynamic_list("solid_infill_filament", &dynamic_filament_list_1_based); + Choice::register_dynamic_list("outer_wall_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("inner_wall_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("sparse_infill_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("internal_solid_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("top_surface_filament_id", &dynamic_filament_list); + Choice::register_dynamic_list("bottom_surface_filament_id", &dynamic_filament_list); Choice::register_dynamic_list("wipe_tower_filament", &dynamic_filament_list); p->scrolled = new wxPanel(this); @@ -8299,7 +8252,6 @@ void Sidebar::show_SEMM_buttons(bool bshow) void Sidebar::update_dynamic_filament_list() { dynamic_filament_list.update(); - dynamic_filament_list_1_based.update(); } void Sidebar::update_nozzle_settings(bool switch_machine) @@ -9434,7 +9386,8 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "extruder_colour", "filament_colour", "material_colour", "printable_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", - "brim_width", "wall_loops", "wall_filament", "sparse_infill_density", "sparse_infill_filament", "solid_infill_filament", "top_shell_layers", + "brim_width", "wall_loops", "outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_density", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "top_shell_layers", "enable_support", "support_filament", "support_interface_filament", "support_top_z_distance", "support_bottom_z_distance", "raft_layers", "wipe_tower_rotation_angle", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extra_flow", "local_z_wipe_tower_purge_lines", "wipe_tower_max_purge_speed", @@ -12302,7 +12255,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool // Validate passed, but also check filament temp mixing as a final // guard. check_filament_temp_mixing() reads directly from preset // configs and catches cases that Print::validate() may miss (e.g. - // wall_filament changes that haven't propagated to PrintRegions yet). + // outer_wall_filament_id changes that haven't propagated to PrintRegions yet). bool filament_ok = q->sync_filament_temp_mixing_notification(); if (filament_ok) { this->partplate_list.get_curr_plate()->update_apply_result_invalid(false); @@ -20543,7 +20496,8 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r sidebar().on_filaments_delete(filament_id); // update global feature filament selections - static const char* keys[] = {"wall_filament", "sparse_infill_filament", "solid_infill_filament", + static const char* keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament"}; for (auto key : keys) if (p->config->has(key)) { @@ -20810,7 +20764,7 @@ bool Plater::check_filament_temp_mixing(int plate_index) // Collect from the Plater working config. The approach balances // sensitivity against false positives: // - Global features (wipe tower, support) always apply → always collected. - // - Feature-specific keys (wall_filament, infill) depend on the global + // - Feature-specific keys (outer_wall_filament_id, infill) depend on the global // process defaults. They are only collected when at least one object // on the plate uses the default extruder (e=0), which means those // defaults WILL affect the actual slicing output. @@ -20833,9 +20787,12 @@ bool Plater::check_filament_temp_mixing(int plate_index) if (uses_default_extruder) { static const std::vector default_keys = { - "wall_filament", - "sparse_infill_filament", - "solid_infill_filament" + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id" }; for (const char* key : default_keys) { @@ -21133,8 +21090,10 @@ void Plater::on_config_change(const DynamicPrintConfig &config) update_scheduled = true; } // Orca: update when *_filament changed - else if (opt_key == "support_interface_filament" || opt_key == "support_filament" || opt_key == "wall_filament" || - opt_key == "sparse_infill_filament" || opt_key == "solid_infill_filament") { + else if (opt_key == "support_interface_filament" || opt_key == "support_filament" || + opt_key == "outer_wall_filament_id" || opt_key == "inner_wall_filament_id" || + opt_key == "sparse_infill_filament_id" || opt_key == "internal_solid_filament_id" || + opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") { update_scheduled = true; } } diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index 483ffdc3e94..490e44c954d 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -128,9 +128,12 @@ std::string PresetHints::maximum_volumetric_flow_description(const PresetBundle auto feature_extruder_active = [idx_extruder, num_extruders](int i) { return i <= 0 || i > num_extruders || idx_extruder == -1 || idx_extruder == i - 1; }; - bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("wall_filament")); - bool infill_extruder_active = feature_extruder_active(print_config.opt_int("wall_filament")); - bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("solid_infill_filament")); + bool perimeter_extruder_active = feature_extruder_active(print_config.opt_int("outer_wall_filament_id")) + && feature_extruder_active(print_config.opt_int("inner_wall_filament_id")); + bool infill_extruder_active = feature_extruder_active(print_config.opt_int("sparse_infill_filament_id")); + bool solid_infill_extruder_active = feature_extruder_active(print_config.opt_int("internal_solid_filament_id")) + && feature_extruder_active(print_config.opt_int("top_surface_filament_id")) + && feature_extruder_active(print_config.opt_int("bottom_surface_filament_id")); bool support_material_extruder_active = feature_extruder_active(print_config.opt_int("support_filament")); bool support_material_interface_extruder_active = feature_extruder_active(print_config.opt_int("support_interface_filament")); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 58f5d8d5b1f..6e5f86b52fe 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2571,9 +2571,12 @@ void TabPrint::build() optgroup->append_single_option_line("single_extruder_multi_material_priming", "multimaterial_settings_prime_tower"); optgroup = page->new_optgroup(L("Filament for Features"), L"param_filament_for_features"); - optgroup->append_single_option_line("wall_filament", "multimaterial_settings_filament_for_features#walls"); - optgroup->append_single_option_line("sparse_infill_filament", "multimaterial_settings_filament_for_features#infill"); - optgroup->append_single_option_line("solid_infill_filament", "multimaterial_settings_filament_for_features#solid-infill"); + optgroup->append_single_option_line("outer_wall_filament_id", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("inner_wall_filament_id", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("sparse_infill_filament_id", "multimaterial_settings_filament_for_features#infill"); + optgroup->append_single_option_line("internal_solid_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); + optgroup->append_single_option_line("top_surface_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); + optgroup->append_single_option_line("bottom_surface_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); optgroup->append_single_option_line("wipe_tower_filament", "multimaterial_settings_filament_for_features#wipe-tower"); optgroup = page->new_optgroup(L("Ooze prevention"), L"param_ooze_prevention"); @@ -2879,16 +2882,24 @@ static DynamicPrintConfig resolved_model_config_for_tab(const DynamicPrintConfig if (const auto* extruder_opt = config.option("extruder"); extruder_opt != nullptr && extruder_opt->value > 0) { const int extruder = extruder_opt->value; - if (!resolved.has("wall_filament")) - resolved.set_key_value("wall_filament", new ConfigOptionInt(extruder)); - if (!resolved.has("sparse_infill_filament")) - resolved.set_key_value("sparse_infill_filament", new ConfigOptionInt(extruder)); - if (!resolved.has("solid_infill_filament")) - resolved.set_key_value("solid_infill_filament", new ConfigOptionInt(extruder)); - } - - if (!resolved.has("solid_infill_filament") && resolved.has("sparse_infill_filament")) - resolved.set_key_value("solid_infill_filament", new ConfigOptionInt(resolved.opt_int("sparse_infill_filament"))); + for (const char* key : {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id"}) + if (!resolved.has(key)) + resolved.set_key_value(key, new ConfigOptionInt(extruder)); + } + + // 0 = "Default" (use the active object/part filament); only explicit selections propagate. + if (resolved.has("sparse_infill_filament_id") && !resolved.has("internal_solid_filament_id")) + if (const int sparse_infill_filament_id = resolved.opt_int("sparse_infill_filament_id"); sparse_infill_filament_id > 0) + resolved.set_key_value("internal_solid_filament_id", new ConfigOptionInt(sparse_infill_filament_id)); + + if (resolved.has("internal_solid_filament_id")) + if (const int internal_solid_filament_id = resolved.opt_int("internal_solid_filament_id"); internal_solid_filament_id > 0) { + if (!resolved.has("top_surface_filament_id")) + resolved.set_key_value("top_surface_filament_id", new ConfigOptionInt(internal_solid_filament_id)); + if (!resolved.has("bottom_surface_filament_id")) + resolved.set_key_value("bottom_surface_filament_id", new ConfigOptionInt(internal_solid_filament_id)); + } return resolved; } diff --git a/tests/libslic3r/test_mixed_filament.cpp b/tests/libslic3r/test_mixed_filament.cpp index b15592e3766..a4e7b0648f3 100644 --- a/tests/libslic3r/test_mixed_filament.cpp +++ b/tests/libslic3r/test_mixed_filament.cpp @@ -476,11 +476,12 @@ TEST_CASE("Grouped manual wall patterns make infill follow the innermost perimet REQUIRE(row.manual_pattern == "12,1"); PrintRegionConfig region_config = static_cast(FullPrintConfig::defaults()); - region_config.wall_filament.value = 3; + region_config.outer_wall_filament_id.value = 3; + region_config.inner_wall_filament_id.value = 3; region_config.wall_loops.value = 2; region_config.sparse_infill_density.value = 15.; - region_config.sparse_infill_filament.value = 2; - region_config.solid_infill_filament.value = 3; + region_config.sparse_infill_filament_id.value = 2; + region_config.internal_solid_filament_id.value = 3; PrintRegion region(region_config); @@ -498,21 +499,21 @@ TEST_CASE("Grouped manual wall patterns make infill follow the innermost perimet layer1.mixed_mgr = &mgr; layer1.num_physical = 2; - CHECK(layer0.wall_filament(region) == 0); - CHECK(layer1.wall_filament(region) == 1); - CHECK(layer0.sparse_infill_filament(region) == 1); - CHECK(layer1.sparse_infill_filament(region) == 1); - CHECK(layer0.solid_infill_filament(region) == 0); - CHECK(layer1.solid_infill_filament(region) == 0); + CHECK(layer0.wall_extruder_id(region) == 0); + CHECK(layer1.wall_extruder_id(region) == 1); + CHECK(layer0.sparse_infill_filament_id(region) == 1); + CHECK(layer1.sparse_infill_filament_id(region) == 1); + CHECK(layer0.internal_solid_filament_id(region) == 0); + CHECK(layer1.internal_solid_filament_id(region) == 0); - region_config.sparse_infill_filament.value = 2; - region_config.solid_infill_filament.value = 2; + region_config.sparse_infill_filament_id.value = 2; + region_config.internal_solid_filament_id.value = 2; PrintRegion overridden_region(region_config); - CHECK(layer0.sparse_infill_filament(overridden_region) == 1); - CHECK(layer1.sparse_infill_filament(overridden_region) == 1); - CHECK(layer0.solid_infill_filament(overridden_region) == 1); - CHECK(layer1.solid_infill_filament(overridden_region) == 1); + CHECK(layer0.sparse_infill_filament_id(overridden_region) == 1); + CHECK(layer1.sparse_infill_filament_id(overridden_region) == 1); + CHECK(layer0.internal_solid_filament_id(overridden_region) == 1); + CHECK(layer1.internal_solid_filament_id(overridden_region) == 1); } TEST_CASE("Mixed filament painted-region resolver collapses ordinary mixed rows to the active physical extruder", "[MixedFilament]") From 43f6d274c9492d3d646cae3082e551ce8f8fd01c Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 20:37:46 +0200 Subject: [PATCH 05/43] profiles: express Snapmaker U1 line widths as % of nozzle diameter --- resources/profiles/Snapmaker.json | 2 +- .../Snapmaker/process/fdm_process_U1.json | 16 ++++++++-------- .../process/fdm_process_U1_0.06_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.08_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.10_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.12_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.14_nozzle_0.2.json | 16 ++++++++-------- .../process/fdm_process_U1_0.18_nozzle_0.6.json | 16 ++++++++-------- .../Snapmaker/process/fdm_process_U1_0.24.json | 2 +- .../process/fdm_process_U1_0.24_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.24_nozzle_0.8.json | 16 ++++++++-------- .../Snapmaker/process/fdm_process_U1_0.28.json | 2 +- .../process/fdm_process_U1_0.30_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.32_nozzle_0.8.json | 16 ++++++++-------- .../process/fdm_process_U1_0.36_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.40_nozzle_0.8.json | 16 ++++++++-------- .../process/fdm_process_U1_0.42_nozzle_0.6.json | 16 ++++++++-------- .../process/fdm_process_U1_0.48_nozzle_0.8.json | 16 ++++++++-------- .../process/fdm_process_U1_0.56_nozzle_0.8.json | 16 ++++++++-------- .../Snapmaker/process/fdm_process_U1_common.json | 6 +++--- 20 files changed, 134 insertions(+), 134 deletions(-) diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index 268c453c60b..6de5b155eb8 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "02.02.50.02", + "version": "02.02.50.03", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1.json b/resources/profiles/Snapmaker/process/fdm_process_U1.json index 6c9935cce7b..9c75c70c9c5 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1.json @@ -12,18 +12,18 @@ "default_acceleration": "10000", "bridge_no_support": "0", "elefant_foot_compensation": "0.1", - "outer_wall_line_width": "0.42", + "outer_wall_line_width": "105%", "outer_wall_speed": "120", - "line_width": "0.45", + "line_width": "112.5%", "infill_direction": "45", "sparse_infill_density": "15%", "sparse_infill_pattern": "grid", - "initial_layer_line_width": "0.42", + "initial_layer_line_width": "105%", "initial_layer_print_height": "0.2", "initial_layer_speed": "20", "gap_infill_speed": "30", "infill_combination": "0", - "sparse_infill_line_width": "0.45", + "sparse_infill_line_width": "112.5%", "infill_wall_overlap": "15%", "sparse_infill_speed": "50", "interface_shells": "0", @@ -31,7 +31,7 @@ "reduce_infill_retraction": "0", "filename_format": "{input_filename_base}.gcode", "wall_loops": "2", - "inner_wall_line_width": "0.45", + "inner_wall_line_width": "112.5%", "inner_wall_speed": "40", "print_settings_id": "", "raft_layers": "0", @@ -39,13 +39,13 @@ "skirt_distance": "2", "skirt_height": "2", "minimum_sparse_infill_area": "0", - "internal_solid_infill_line_width": "0.45", + "internal_solid_infill_line_width": "112.5%", "internal_solid_infill_speed": "40", "spiral_mode": "0", "standby_temperature_delta": "-5", "enable_support": "0", "support_filament": "0", - "support_line_width": "0.42", + "support_line_width": "105%", "support_interface_filament": "0", "support_on_build_plate_only": "0", "support_top_z_distance": "0.15", @@ -60,7 +60,7 @@ "support_threshold_angle": "40", "support_object_xy_distance": "0.5", "detect_thin_wall": "0", - "top_surface_line_width": "0.42", + "top_surface_line_width": "105%", "top_surface_speed": "30", "travel_speed": "400", "enable_prime_tower": "0", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json index b028d9087a0..36e972e066b 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.06_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json index 79430d26d2d..255713a98d3 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.08_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json index 3e47d1bf5ae..d86cafb29fa 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.10_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json index c52683cb8f4..0a14422db8f 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.12_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json index 57119f0f022..58a403c7361 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.14_nozzle_0.2.json @@ -10,14 +10,14 @@ "bottom_shell_layers": "5", "top_shell_layers": "7", "bridge_flow": "1", - "line_width": "0.22", - "outer_wall_line_width": "0.22", - "initial_layer_line_width": "0.25", - "sparse_infill_line_width": "0.22", - "inner_wall_line_width": "0.22", - "internal_solid_infill_line_width": "0.22", - "support_line_width": "0.22", - "top_surface_line_width": "0.22", + "line_width": "110%", + "outer_wall_line_width": "110%", + "initial_layer_line_width": "125%", + "sparse_infill_line_width": "110%", + "inner_wall_line_width": "110%", + "internal_solid_infill_line_width": "110%", + "support_line_width": "110%", + "top_surface_line_width": "110%", "initial_layer_speed": "40", "initial_layer_infill_speed": "70", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json index 7d10b214291..156b714e941 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.18_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.18", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json index 89224e3a29a..66f83313ca1 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24.json @@ -6,7 +6,7 @@ "instantiation": "false", "layer_height": "0.24", "elefant_foot_compensation": "0.15", - "top_surface_line_width": "0.45", + "top_surface_line_width": "112.5%", "top_shell_thickness": "1.0", "bridge_flow": "1", "initial_layer_speed": "50", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json index 12104aa0cdf..cd408b77e55 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.24", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json index 5f0b60b231a..49508e88835 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.24_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.24", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json index 189d8d337ce..37fd4f68e0e 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.28.json @@ -6,7 +6,7 @@ "instantiation": "false", "layer_height": "0.28", "elefant_foot_compensation": "0.15", - "top_surface_line_width": "0.45", + "top_surface_line_width": "112.5%", "top_shell_thickness": "1.0", "bridge_flow": "1", "initial_layer_speed": "50", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json index fcf87f69513..f753c702d9b 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.30_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.3", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json index 43b3dc8f9b9..37441608522 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.32_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.32", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json index 01c90927b29..61701643e2d 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.36_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.36", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json index 45bd45c8a54..3850aaf0f55 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.40_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.4", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json index 57cc629208c..91224ef8427 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.42_nozzle_0.6.json @@ -7,14 +7,14 @@ "layer_height": "0.42", "initial_layer_print_height": "0.3", "bridge_flow": "1", - "line_width": "0.62", - "outer_wall_line_width": "0.62", - "initial_layer_line_width": "0.62", - "sparse_infill_line_width": "0.62", - "inner_wall_line_width": "0.62", - "internal_solid_infill_line_width": "0.62", - "support_line_width": "0.62", - "top_surface_line_width": "0.62", + "line_width": "103.33%", + "outer_wall_line_width": "103.33%", + "initial_layer_line_width": "103.33%", + "sparse_infill_line_width": "103.33%", + "inner_wall_line_width": "103.33%", + "internal_solid_infill_line_width": "103.33%", + "support_line_width": "103.33%", + "top_surface_line_width": "103.33%", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", "sparse_infill_speed": "100", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json index be97fb0672b..2bb250ed9bd 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.48_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.48", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json b/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json index 2aed528ea3a..30225fb7bb0 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_0.56_nozzle_0.8.json @@ -7,14 +7,14 @@ "layer_height": "0.56", "initial_layer_print_height": "0.4", "bridge_flow": "1", - "line_width": "0.82", - "outer_wall_line_width": "0.82", - "initial_layer_line_width": "0.82", - "sparse_infill_line_width": "0.82", - "inner_wall_line_width": "0.82", - "internal_solid_infill_line_width": "0.82", - "support_line_width": "0.82", - "top_surface_line_width": "0.82", + "line_width": "102.5%", + "outer_wall_line_width": "102.5%", + "initial_layer_line_width": "102.5%", + "sparse_infill_line_width": "102.5%", + "inner_wall_line_width": "102.5%", + "internal_solid_infill_line_width": "102.5%", + "support_line_width": "102.5%", + "top_surface_line_width": "102.5%", "top_surface_pattern": "monotonic", "initial_layer_speed": "35", "initial_layer_infill_speed": "55", diff --git a/resources/profiles/Snapmaker/process/fdm_process_U1_common.json b/resources/profiles/Snapmaker/process/fdm_process_U1_common.json index 4f9c8a34079..4dc5e352b16 100644 --- a/resources/profiles/Snapmaker/process/fdm_process_U1_common.json +++ b/resources/profiles/Snapmaker/process/fdm_process_U1_common.json @@ -16,10 +16,10 @@ "enable_arc_fitting": "1", "outer_wall_acceleration": "5000", "wall_infill_order": "inner wall/outer wall/infill", - "line_width": "0.42", + "line_width": "105%", "internal_bridge_support_thickness": "0.8", "initial_layer_acceleration": "500", - "initial_layer_line_width": "0.5", + "initial_layer_line_width": "125%", "initial_layer_speed": "30", "gap_infill_speed": "50", "sparse_infill_speed": "250", @@ -41,7 +41,7 @@ "skirt_height": "1", "skirt_loops": "0", "minimum_sparse_infill_area": "15", - "internal_solid_infill_line_width": "0.42", + "internal_solid_infill_line_width": "105%", "internal_solid_infill_speed": "150", "initial_layer_infill_speed": "60", "resolution": "0.012", From 28618a00d41b8e3e0e16df183d192d346dc9d6a8 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:51:46 +0000 Subject: [PATCH 06/43] feat: port per-extruder layer height (multi-nozzle printing) Port of LixNix/OrcaSlicer-multi-nozzle-size-printing commit 944da137 "Add per extruder layer height" onto this fork. New printer option extruder_layer_height (per extruder): the preferred layer height of each extruder, for printers whose extruders carry different nozzle sizes. Object parts whose walls are assigned to an extruder preferring an integer multiple N of the object layer height print only on every Nth layer with N-times-thicker extrusions wherever geometry allows (region slices are combined at the slicing step); sparse infill assigned to such an extruder is combined up to that height instead. New print options extruder_layer_height_mode (consistent/adaptive) and extruder_layer_height_tolerance control the run combining, and support_nozzle_diameter restricts support, raft and interface printing to filaments of one nozzle diameter. Includes: per-tool line widths on the wipe tower (WipeTower2), tool ordering that skips combined-away layers and prefers nozzle-matching filaments for flushing, per-feature-filament flow resolution (bottom surfaces / external bridges / gap fill print with their own filament's nozzle width), support flow and layer height limits honoring the support nozzle restriction, and extensive Print::validate() checks. Adaptations to this fork: no BBL filament-map rerouting (a filament index is the extruder index on classic multi-tool printers), the single-StringObjectException warning channel of this fork's Print::validate(), mixed-filament (Local-Z / pointillism / grouped patterns) integration kept working - resolved effective filaments feed the new flow lookups and the wipe tower's Local-Z planning gained the same per-tool widths. All new behavior is inert while the new options stay at their defaults (0). Tests: tests/fff_print/test_multi_nozzle_layer_height.cpp (ported to Catch2 v2 and this fork's APIs). --- src/libslic3r/Brim.cpp | 5 +- src/libslic3r/Fill/Fill.cpp | 13 +- src/libslic3r/Flow.cpp | 19 +- src/libslic3r/Flow.hpp | 2 + src/libslic3r/GCode.cpp | 57 +- src/libslic3r/GCode/ToolOrdering.cpp | 162 ++++- src/libslic3r/GCode/WipeTower2.cpp | 87 ++- src/libslic3r/GCode/WipeTower2.hpp | 11 +- src/libslic3r/Layer.cpp | 19 +- src/libslic3r/Layer.hpp | 21 +- src/libslic3r/LayerRegion.cpp | 47 +- src/libslic3r/PerimeterGenerator.cpp | 29 +- src/libslic3r/PerimeterGenerator.hpp | 10 +- src/libslic3r/Preset.cpp | 4 +- src/libslic3r/Print.cpp | 275 ++++++- src/libslic3r/Print.hpp | 23 +- src/libslic3r/PrintConfig.cpp | 73 +- src/libslic3r/PrintConfig.hpp | 13 + src/libslic3r/PrintObject.cpp | 312 +++++++- src/libslic3r/PrintObjectSlice.cpp | 190 +++++ src/libslic3r/PrintRegion.cpp | 7 +- src/libslic3r/Slicing.cpp | 39 +- src/libslic3r/Support/SupportMaterial.cpp | 2 +- src/libslic3r/Support/SupportParameters.hpp | 3 +- src/libslic3r/Support/TreeSupport.cpp | 2 +- src/slic3r/GUI/ConfigManipulation.cpp | 8 + src/slic3r/GUI/Tab.cpp | 4 + tests/fff_print/CMakeLists.txt | 1 + .../test_multi_nozzle_layer_height.cpp | 684 ++++++++++++++++++ 29 files changed, 1994 insertions(+), 128 deletions(-) create mode 100644 tests/fff_print/test_multi_nozzle_layer_height.cpp diff --git a/src/libslic3r/Brim.cpp b/src/libslic3r/Brim.cpp index 5ee502acb89..30368cce989 100644 --- a/src/libslic3r/Brim.cpp +++ b/src/libslic3r/Brim.cpp @@ -1040,7 +1040,10 @@ static ExPolygons outer_inner_brim_area(const Print& print, } support_material_extruder = object->config().support_filament; if (support_material_extruder == 0 && object->has_support_material()) { - if (print.config().print_sequence == PrintSequence::ByObject) + // ORCA: under the support nozzle diameter restriction the brim uses the support's resolved filament. + if (unsigned int resolved = object->resolved_default_support_filament(); resolved > 0) + support_material_extruder = resolved; + else if (print.config().print_sequence == PrintSequence::ByObject) support_material_extruder = objectWithExtruder.second; else support_material_extruder = printExtruders.front() + 1; diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index c3fd03b0c24..7f1187c1d21 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -899,7 +899,9 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p // ORCA: per-feature filaments. Top and internal solid fills are already resolved // by layerm.extruder(extrusion_role) above; bottom surfaces print with the bottom // surface filament, routed through the same mixed-filament remapping as top surfaces. - if (params.extrusion_role == erBottomSurface) + // External bridges are bottom surfaces, so they print with the bottom surface filament + // too (internal bridges keep their pre-seeded filament). + if (params.extrusion_role == erBottomSurface || params.extrusion_role == erBridgeInfill) params.extruder = effective_layer_filament_id(layer, (unsigned int)std::max(0, region_config.bottom_surface_filament_id.value)); // Orca: apply fill multiline only for sparse infill @@ -925,9 +927,12 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.bridge = is_bridge || Fill::use_bridge_flow(params.pattern); const bool is_thick_bridge = surface.is_bridge() && (surface.is_internal_bridge() ? object_config.thick_internal_bridges : object_config.thick_bridges); params.flow = params.bridge ? - //Orca: enable thick bridge based on config - layerm.bridging_flow(extrusion_role, is_thick_bridge) : - layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness); + //Orca: enable thick bridge based on config. Combined layers stamp their full thickness on the surface; the non-thick bridge flow must be based on it. + layerm.bridging_flow(extrusion_role, is_thick_bridge, params.extruder, + (surface.thickness == -1) ? 0. : surface.thickness) : + // Width resolves against the nozzle of the filament that actually prints + // (params.extruder), which may differ from the role's default filament mapping. + layerm.flow(extrusion_role, (surface.thickness == -1) ? layer.height : surface.thickness, params.extruder); // record speed params if (!params.bridge) { if (params.extrusion_role == erInternalInfill) diff --git a/src/libslic3r/Flow.cpp b/src/libslic3r/Flow.cpp index dbe7e157ab1..0008c13e4d8 100644 --- a/src/libslic3r/Flow.cpp +++ b/src/libslic3r/Flow.cpp @@ -211,21 +211,29 @@ double Flow::mm3_per_mm() const return res; } +// Nozzle diameter driving support / raft flows. A "default" (0) filament prints with the active extruder; the support_nozzle_diameter restriction defines which nozzle that may be. +float support_material_nozzle_diameter(const PrintObject *object, int configured_filament) +{ + if (configured_filament == 0 && object->config().support_nozzle_diameter.value > 0.) + return float(object->config().support_nozzle_diameter.value); + // for configured_filament == 0 (use the current extruder), get_at returns the 0th component. + return float(object->print()->config().nozzle_diameter.get_at(configured_filament - 1)); +} + Flow support_material_flow(const PrintObject *object, float layer_height) { return Flow::new_from_config_width( frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_line_width.value > 0) ? object->config().support_line_width : object->config().line_width, - // if object->config().support_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament-1)), + support_material_nozzle_diameter(object, object->config().support_filament), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } //BBS Flow support_transition_flow(const PrintObject* object) { //BBS: support transition of tree support is bridge flow - float dmr = float(object->print()->config().nozzle_diameter.get_at(object->config().support_filament - 1)); + float dmr = support_material_nozzle_diameter(object, object->config().support_filament); return Flow::bridging_flow(dmr, dmr); } @@ -237,7 +245,7 @@ Flow support_material_1st_layer_flow(const PrintObject *object, float layer_heig frSupportMaterial, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (width.value > 0) ? width : object->config().line_width, - float(print_config.nozzle_diameter.get_at(object->config().support_filament-1)), + support_material_nozzle_diameter(object, object->config().support_filament), (layer_height > 0.f) ? layer_height : float(print_config.initial_layer_print_height.value)); } @@ -247,8 +255,7 @@ Flow support_material_interface_flow(const PrintObject *object, float layer_heig frSupportMaterialInterface, // The width parameter accepted by new_from_config_width is of type ConfigOptionFloatOrPercent, the Flow class takes care of the percent to value substitution. (object->config().support_line_width > 0) ? object->config().support_line_width : object->config().line_width, - // if object->config().support_interface_filament == 0 (which means to not trigger tool change, but use the current extruder instead), get_at will return the 0th component. - float(object->print()->config().nozzle_diameter.get_at(object->config().support_interface_filament-1)), + support_material_nozzle_diameter(object, object->config().support_interface_filament), (layer_height > 0.f) ? layer_height : float(object->config().layer_height.value)); } diff --git a/src/libslic3r/Flow.hpp b/src/libslic3r/Flow.hpp index 79cb1b324d6..a8028a9c6fa 100644 --- a/src/libslic3r/Flow.hpp +++ b/src/libslic3r/Flow.hpp @@ -139,6 +139,8 @@ class Flow bool m_bridge { false }; }; +// ORCA: nozzle driving support / raft flows: the configured filament's, or the support_nozzle_diameter restriction when the filament is left at "default" (0). +extern float support_material_nozzle_diameter(const PrintObject *object, int configured_filament); extern Flow support_material_flow(const PrintObject* object, float layer_height = 0.f); extern Flow support_transition_flow(const PrintObject *object); //BBS extern Flow support_material_1st_layer_flow(const PrintObject *object, float layer_height = 0.f); diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 451168de998..f4dc14bb12a 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1513,7 +1513,12 @@ std::vector GCode::collect_layers_to_print(const PrintObjec } extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); } - double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_to_print.layer()->height + + double layer_span = layer_to_print.layer()->height; + // ORCA: the top layer of a combined group prints the whole group at once, so tolerate a gap of combined_height() (equals the layer height when not combined). + if (layer_to_print.object_layer != nullptr) + for (const LayerRegion *layerm : layer_to_print.object_layer->regions()) + layer_span = std::max(layer_span, layerm->combined_height()); + double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_span + std::max(0., extra_gap); // Negative support_contact_z is not taken into account, it can result in false positives in cases @@ -5394,6 +5399,29 @@ LayerResult GCode::process_layer(const Print& print, if (interface_dontcare) interface_extruder = dontcare_extruder; } + // ORCA: with a support nozzle diameter restriction, ("don't care") support/interface may only use a nozzle-matching extruder; prefer one scheduled on this layer (as ToolOrdering did). + if (object.config().support_nozzle_diameter.value > 0.) { + auto restrict_to_support_nozzle = [&print, &object, &layer_tools](unsigned int extruder_id) -> unsigned int { + if (object.support_filament_allowed(extruder_id + 1)) + return extruder_id; + unsigned int fallback = extruder_id; + bool have_fallback = false; + for (unsigned int candidate : layer_tools.extruders) // 0 based at this point + if (object.support_filament_allowed(candidate + 1)) { + if (! print.config().filament_soluble.get_at(candidate)) + return candidate; + if (! have_fallback) { + fallback = candidate; + have_fallback = true; + } + } + return fallback; + }; + if (support_dontcare) + support_extruder = restrict_to_support_nozzle(support_extruder); + if (interface_dontcare) + interface_extruder = restrict_to_support_nozzle(interface_extruder); + } // Both the support and the support interface are printed with the same extruder, therefore // the interface may be interleaved with the support base. bool single_extruder = !has_support || support_extruder == interface_extruder; @@ -5762,10 +5790,31 @@ LayerResult GCode::process_layer(const Print& print, auto outer_perimeters = std::make_unique(); auto inner_perimeters = std::make_unique(); for (const ExtrusionEntity* entity : filtered_extrusions->entities) { - const ExtrusionRole role = entity->role(); - if (role == erExternalPerimeter || role == erOverhangPerimeter) + // ORCA: chaining may put an overhang path first and fully overhanging loops + // have no plain perimeter path: classify by scanning every path; anything without + // an inner perimeter path uses the outer wall filament (matches PerimeterGenerator's + // overhang flows). + bool has_external = false, has_internal = false; + auto classify = [&](const ExtrusionPaths& paths) { + for (const ExtrusionPath& path : paths) { + if (path.role() == erExternalPerimeter) + has_external = true; + else if (path.role() == erPerimeter) + has_internal = true; + } + }; + if (const auto* loop = dynamic_cast(entity)) + classify(loop->paths); + else if (const auto* multi_path = dynamic_cast(entity)) + classify(multi_path->paths); + else { + const ExtrusionRole role = entity->role(); + has_external = role == erExternalPerimeter; + has_internal = role == erPerimeter; + } + if (has_external || !has_internal) outer_perimeters->append(*entity); - else if (role == erPerimeter) + else inner_perimeters->append(*entity); } diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index afac29ba51a..547185789e7 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -344,14 +344,28 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c assert(region.config().top_surface_filament_id.value > 0); assert(region.config().bottom_surface_filament_id.value > 0); if (extrusions.has_infill()) { - const ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); + ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); + // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + if (role == erGapFill) + for (const ExtrusionEntity *ee : extrusions.entities) + if (ee->role() != erGapFill) { + role = ee->role(); + break; + } if (internal_solid_infill_uses_sparse_filament(region, role)) return sparse_infill_filament_id(region); if (extrusions.has_solid_infill()) { - const ExtrusionRole solid_role = extrusions.role(); + ExtrusionRole solid_role = extrusions.role(); + // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + if (solid_role == erMixed) + for (const ExtrusionEntity *ee : extrusions.entities) + if (ee->role() != erGapFill) { + solid_role = ee->role(); + break; + } if (solid_role == erTopSolidInfill || solid_role == erIroning) return top_surface_filament_id(region); - if (solid_role == erBottomSurface) + if (solid_role == erBottomSurface || solid_role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) return bottom_surface_filament_id(region); return internal_solid_filament_id(region); } @@ -731,6 +745,22 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto float(support_layer->print_z), float(support_layer->height), &object); + // support nozzle diameter restriction, resolve a "default" (0) support filament to a nozzle-matching one here, preferring a filament this layer already prints with to save toolchanges. + if (object.config().support_nozzle_diameter.value > 0.) { + auto restrict_default_filament = [&object, &layer_tools](unsigned int configured) -> unsigned int { + if (configured != 0) + return configured; + const PrintConfig &print_config = object.print()->config(); + for (unsigned int filament : layer_tools.extruders) // 1 based at this point + if (filament > 0 && object.support_filament_allowed(filament) && + ! print_config.filament_soluble.get_at(filament - 1)) + return filament; + unsigned int resolved = object.resolved_default_support_filament(); + return resolved > 0 ? resolved : configured; + }; + extruder_support = restrict_default_filament(extruder_support); + extruder_interface = restrict_default_filament(extruder_interface); + } if (has_support) layer_tools.extruders.push_back(extruder_support); if (has_interface) @@ -811,7 +841,14 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (layerCount == 0) firstLayerExtruders.emplace_back(wall_ext); } - if (extruder_override == 0 && region.config().wall_loops.value > 1) + // alternate_extra_wall should add an inner loop on odd layers (mirrors PerimeterGenerator's loop_number and the spiral gate of LayerRegion::make_perimeters()); layers dropping the loop again (only_one_wall_top / only_one_wall_first_layer) may reserve the filament unused, which merely costs a toolchange. + const bool spiral_vase_layer = object.print()->config().spiral_mode.value && + layer->id() >= size_t(region.config().bottom_shell_layers.value) && + layer->print_z >= region.config().bottom_shell_thickness - EPSILON; + if (extruder_override == 0 && + (region.config().wall_loops.value > 1 || + (region.config().alternate_extra_wall.value && layer->id() % 2 == 1 && + region.config().sparse_infill_density.value > 0 && !spiral_vase_layer))) layer_tools.extruders.emplace_back(resolve_mixed(region.config().inner_wall_filament_id.value, layerCount, float(layer->print_z), @@ -826,19 +863,30 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto bool has_internal_solid = false; bool has_top_solid_surface = false; bool has_bottom_surface = false; + bool has_pure_gap_fill = false; bool something_nonoverriddable = false; for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); ExtrusionRole role = fill->entities.empty() ? erNone : fill->entities.front()->role(); + // gap fill inherits its surface's filament; classify by the first non-gap-fill entity (must match LayerTools::extruder()). + if (role == erGapFill) + for (const ExtrusionEntity *fill_entity : fill->entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } if (internal_solid_infill_uses_sparse_filament(region, role)) has_sparse_infill = true; else if (role == erTopSolidInfill || role == erIroning) has_top_solid_surface = true; - else if (role == erBottomSurface) + else if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces has_bottom_surface = true; else if (is_solid_infill(role)) has_internal_solid = true; + else if (role == erGapFill) + // perimeter-generated gap fill with no sibling surface dispatches to the outer wall filament. + has_pure_gap_fill = true; else if (role != erNone) has_sparse_infill = true; @@ -862,7 +910,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto if (has_sparse_infill) { layer_tools.extruders.emplace_back(layer_tools.sparse_infill_filament_id(region) + 1); } - } else if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill) { + if (has_pure_gap_fill) { + // perimeter-generated gap fill with no sibling surface uses the outer wall filament. + layer_tools.extruders.emplace_back(layer_tools.wall_extruder_id(region) + 1); + } + } else if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill || has_pure_gap_fill) { layer_tools.extruders.emplace_back(resolve_mixed(extruder_override, layerCount, float(layer->print_z), @@ -870,7 +922,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto &object)); } } - if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill) + if (has_internal_solid || has_top_solid_surface || has_bottom_surface || has_sparse_infill || has_pure_gap_fill) layer_tools.has_object = true; } layerCount++; @@ -1116,12 +1168,23 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. + // Per-extruder layer height combines layers away, leaving many LayerTools empty; skip them and compare against the last printing layer. + // Without the feature keep the historic behavior of stopping at the first empty layer. + const bool skip_empty_layer_tools = std::any_of(config.extruder_layer_height.values.begin(), + config.extruder_layer_height.values.end(), [](double h) { return h > 0.; }); + const LayerTools *lt_last_printing = nullptr; for (unsigned int i=0; i+1 1)) + if (lt.extruders.empty() || lt_next.extruders.empty()) { + if (! skip_empty_layer_tools) + break; + if (lt_last_printing == nullptr || lt_next.extruders.empty()) + continue; + } + if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt_last_printing->extruders.back() || lt_next.extruders.size() > 1)) lt_next.has_wipe_tower = true; // We should also check that the next wipe tower layer is no further than max_layer_height: unsigned int j = i+1; @@ -1461,12 +1524,37 @@ int WipingExtrusions::last_nonsoluble_extruder_on_layer(const PrintConfig& print return (-1); } +// diameter of the nozzle a 0-based filament prints through. +// On classic multi-tool printers the filament index is the extruder index. +static double filament_nozzle_diameter(const Print &print, unsigned int filament_id) +{ + return print.config().nozzle_diameter.get_at(filament_id); +} + // Decides whether this entity could be overridden bool WipingExtrusions::is_overriddable(const ExtrusionEntityCollection& eec, const PrintConfig& print_config, const PrintObject& object, const PrintRegion& region) const { - if (print_config.filament_soluble.get_at(m_layer_tools->extruder(eec, region))) + const unsigned int intended_filament = m_layer_tools->extruder(eec, region); + if (print_config.filament_soluble.get_at(intended_filament)) return false; + // Entity widths/heights were computed for its filament's nozzle, so only a filament with the same nozzle diameter may take it over; with no candidate it must keep its own filament. + if (print_config.nozzle_diameter.values.size() > 1) { + const Print &print = *object.print(); + const double intended_nozzle = filament_nozzle_diameter(print, intended_filament); + bool has_candidate = false; + for (size_t filament = 0; filament < print_config.filament_soluble.values.size(); ++ filament) + if (filament != intended_filament && + !print_config.filament_soluble.get_at(filament) && + !print_config.filament_is_support.get_at(filament) && + std::abs(filament_nozzle_diameter(print, (unsigned int)filament) - intended_nozzle) < EPSILON) { + has_candidate = true; + break; + } + if (!has_candidate) + return false; + } + if (object.config().flush_into_objects) return true; @@ -1560,6 +1648,11 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (!is_overriddable(*fill, print.config(), *object, region)) continue; + // Only wipe into entities computed for this extruder's nozzle diameter. + if (std::abs(filament_nozzle_diameter(print, lt.extruder(*fill, region)) - + filament_nozzle_diameter(print, new_extruder)) > EPSILON) + continue; + if (wipe_into_infill_only && ! is_infill_first) // In this case we must check that the original extruder is used on this layer before the one we are overridding // (and the perimeters will be finished before the infill is printed): @@ -1581,7 +1674,10 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int { for (const ExtrusionEntity* ee : layerm->perimeters.entities) { auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume) { + if (is_overriddable(*fill, print.config(), *object, region) && !is_entity_overridden(fill, object, copy) && fill->total_volume() > min_infill_volume && + // nozzle diameter must match + std::abs(filament_nozzle_diameter(print, lt.extruder(*fill, region)) - + filament_nozzle_diameter(print, new_extruder)) < EPSILON) { set_extruder_override(fill, object, copy, new_extruder, num_of_copies); if ((volume_to_wipe -= float(fill->total_volume())) <= 0.f) // More material was purged already than asked for. @@ -1600,8 +1696,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (this_support_layer == nullptr) break; - bool support_overriddable = object_config.support_filament == 0; - bool support_intf_overriddable = object_config.support_interface_filament == 0; + // Only a nozzle matching the object's support nozzle diameter restriction may flush into its support. + bool support_overriddable = object_config.support_filament == 0 && object->support_filament_allowed(new_extruder + 1); + bool support_intf_overriddable = object_config.support_interface_filament == 0 && object->support_filament_allowed(new_extruder + 1); if (!support_overriddable && !support_intf_overriddable) break; @@ -1651,6 +1748,25 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) unsigned int first_nonsoluble_extruder = first_nonsoluble_extruder_on_layer(print.config()); unsigned int last_nonsoluble_extruder = last_nonsoluble_extruder_on_layer(print.config()); + // Prefer filaments printing through the nozzle diameter the entity's widths were computed for; returns -1 when the layer offers none. + auto nonsoluble_matching_extruder = [this, &print](bool first, unsigned int intended_filament) -> int { + const PrintConfig &config = print.config(); + const double intended_nozzle = filament_nozzle_diameter(print, intended_filament); + auto matches = [&](unsigned int filament) { + return !config.filament_soluble.get_at(filament) && !config.filament_is_support.get_at(filament) && + std::abs(filament_nozzle_diameter(print, filament) - intended_nozzle) < EPSILON; + }; + const std::vector &extruders = m_layer_tools->extruders; + if (first) { + for (auto it = extruders.begin(); it != extruders.end(); ++ it) + if (matches(*it)) return int(*it); + } else { + for (auto it = extruders.rbegin(); it != extruders.rend(); ++ it) + if (matches(*it)) return int(*it); + } + return -1; + }; + for (const PrintObject* object : print.objects()) { // Finds this layer: const Layer* this_layer = object->get_layer_at_printz(lt.print_z, EPSILON); @@ -1682,8 +1798,13 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) //BBS //|| object->config().flush_into_objects // in this case the perimeter is overridden, so we can override by the last one safely || lt.is_extruder_order(lt.wall_extruder_id(region), last_nonsoluble_extruder // !infill_first, but perimeter is already printed when last extruder prints - || ! lt.has_extruder(lt.sparse_infill_filament_id(region)))) // we have to force override - this could violate infill_first (FIXME) - set_extruder_override(fill, object, copy, (is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder), num_of_copies); + || ! lt.has_extruder(lt.sparse_infill_filament_id(region)))) { // we have to force override - this could violate infill_first (FIXME) + // Prefer a nozzle-matching filament; fall back so the entity still prints. + int flush_extruder = nonsoluble_matching_extruder(is_infill_first, lt.extruder(*fill, region)); + if (flush_extruder < 0) + flush_extruder = is_infill_first ? first_nonsoluble_extruder : last_nonsoluble_extruder; + set_extruder_override(fill, object, copy, flush_extruder, num_of_copies); + } else { // In this case we can (and should) leave it to be printed normally. // Force overriding would mean it gets printed before its perimeter. @@ -1693,8 +1814,13 @@ void WipingExtrusions::ensure_perimeters_infills_order(const Print& print) // Now the same for perimeters - see comments above for explanation: for (const ExtrusionEntity* ee : layerm->perimeters.entities) { // iterate through all perimeter Collections auto* fill = dynamic_cast(ee); - if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, object, copy)) - set_extruder_override(fill, object, copy, (is_infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder), num_of_copies); + if (is_overriddable(*fill, print.config(), *object, region) && ! is_entity_overridden(fill, object, copy)) { + // Same nozzle-matching preference as above. + int flush_extruder = nonsoluble_matching_extruder(!is_infill_first, lt.extruder(*fill, region)); + if (flush_extruder < 0) + flush_extruder = is_infill_first ? last_nonsoluble_extruder : first_nonsoluble_extruder; + set_extruder_override(fill, object, copy, flush_extruder, num_of_copies); + } } } } diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 6a06416fd37..bf222eca77a 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -1422,7 +1422,10 @@ void WipeTower2::set_extruder(size_t idx, const PrintConfig& config) if (max_vol_speed != 0.f) m_filpar[idx].max_e_speed = (max_vol_speed / filament_area()); - m_perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; // all extruders are now assumed to have the same diameter + // ORCA: every tool purges with a width matching its own nozzle; the tower's geometric layout + // (m_perimeter_width) uses the widest configured nozzle, not whichever was configured last. + m_filpar[idx].perimeter_width = nozzle_diameter * Width_To_Nozzle_Ratio; + m_perimeter_width = idx == 0 ? m_filpar[idx].perimeter_width : std::max(m_perimeter_width, m_filpar[idx].perimeter_width); if (m_semm) { std::istringstream stream{config.filament_ramming_parameters.get_at(idx)}; @@ -1494,7 +1497,7 @@ std::vector WipeTower2::prime( for (size_t idx_tool = 0; idx_tool < tools.size(); ++idx_tool) { size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow).set_z(m_z_pos).set_initial_tool(m_current_tool); writer.set_is_prime(true); @@ -1588,7 +1591,7 @@ WipeTower::ToolChangeResult WipeTower2::emit_planned_tool_change(const WipeTower (!is_no_tool_sentinel(tool) ? wipe_area + m_depth_traversed - 0.5f * m_perimeter_width : m_wipe_tower_depth - m_perimeter_width)); - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1701,7 +1704,7 @@ WipeTower::ToolChangeResult WipeTower2::local_z_tool_change(size_t new_tool, { const size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -1758,7 +1761,8 @@ WipeTower::ToolChangeResult WipeTower2::local_z_tool_change(size_t new_tool, float WipeTower2::predict_ramming_end_x(int old_tool, float layer_height) const { - float line_width = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // Must match the per-tool ramming line width used by toolchange_Unload(). + float line_width = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; // Same xl/xr as Unload, using actual ramming line_width for the offset float xl = m_perimeter_width / 2.f + 1.f * line_width; @@ -1798,7 +1802,7 @@ void WipeTower2::toolchange_Unload(WipeTowerWriter2& writer, float xl = cleaning_box.ld.x() + 1.f * m_perimeter_width; float xr = cleaning_box.rd.x() - 1.f * m_perimeter_width; - const float line_width = m_perimeter_width * + const float line_width = tool_perimeter_width(m_current_tool) * m_filpar[m_current_tool].ramming_line_width_multiplicator; // desired ramming line thickness const float y_step = line_width * m_filpar[m_current_tool].ramming_step_multiplicator * m_extra_spacing_ramming; // spacing between lines in mm @@ -1886,7 +1890,7 @@ void WipeTower2::toolchange_Unload(WipeTowerWriter2& writer, } } Vec2f end_of_ramming(writer.x(), writer.y()); - writer.change_analyzer_line_width(m_perimeter_width); // so the next lines are not affected by ramming_line_width_multiplier + writer.change_analyzer_line_width(tool_perimeter_width(m_current_tool)); // so the next lines are not affected by ramming_line_width_multiplier // Retraction: if (m_enable_filament_ramming) @@ -2034,6 +2038,8 @@ void WipeTower2::toolchange_Change(WipeTowerWriter2& writer, const size_t new_to writer.flush_planner_queue(); m_current_tool = new_tool; + // The new toolhead may have a different nozzle, so the extrusion flow must follow the tool change. + m_extrusion_flow = extrusion_flow(m_layer_height); } void WipeTower2::toolchange_Load(WipeTowerWriter2& writer, const WipeTower::box_coordinates& cleaning_box) @@ -2071,14 +2077,15 @@ void WipeTower2::toolchange_Wipe(WipeTowerWriter2& writer, const WipeTower::box_ const float& xr = cleaning_box.rd.x(); writer.set_extrusion_flow(m_extrusion_flow * m_extra_flow); - const float line_width = m_perimeter_width * m_extra_flow; + // Purge lines use the tool's own width; row spacing dy keeps m_perimeter_width so consumed depth matches the plan. + const float line_width = tool_perimeter_width(m_current_tool) * m_extra_flow; writer.change_analyzer_line_width(line_width); // Variables x_to_wipe and traversed_x are here to be able to make sure it always wipes at least // the ordered volume, even if it means violating the box. This can later be removed and simply // wipe until the end of the assigned area. - float x_to_wipe = volume_to_length(wipe_volume, m_perimeter_width, m_layer_height) / m_extra_flow; + float x_to_wipe = volume_to_length(wipe_volume, tool_perimeter_width(m_current_tool), m_layer_height) / m_extra_flow; float dy = (is_first_layer() ? m_extra_flow : m_extra_spacing_wipe) * m_perimeter_width; // Don't use the extra spacing for the first layer, but do use the spacing resulting from increased flow. // All the calculations in all other places take the spacing into account for all the layers. @@ -2192,7 +2199,7 @@ void WipeTower2::toolchange_Wipe(WipeTowerWriter2& writer, const WipeTower::box_ } writer.set_extrusion_flow(m_extrusion_flow); // Reset the extrusion flow. - writer.change_analyzer_line_width(m_perimeter_width); + writer.change_analyzer_line_width(tool_perimeter_width(m_current_tool)); } WipeTower::ToolChangeResult WipeTower2::finish_layer() @@ -2202,7 +2209,7 @@ WipeTower::ToolChangeResult WipeTower2::finish_layer() size_t old_tool = m_current_tool; - WipeTowerWriter2 writer(m_layer_height, m_perimeter_width, m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); + WipeTowerWriter2 writer(m_layer_height, tool_perimeter_width(m_current_tool), m_gcode_flavor, m_filpar, m_enable_arc_fitting, m_printer_model); writer.set_extrusion_flow(m_extrusion_flow) .set_z(m_z_pos) .set_initial_tool(m_current_tool) @@ -2424,12 +2431,13 @@ std::vector> WipeTower2::extract_wipe_volumes(const PrintConf return wipe_volumes; } -static float get_wipe_depth(float volume, float layer_height, float perimeter_width, float extra_flow, float extra_spacing, float width) +// purge_line_width is what the purging tool extrudes, row_spacing_width the tower's geometric row pitch - they differ on printers with mixed nozzle diameters. +static float get_wipe_depth(float volume, float layer_height, float purge_line_width, float row_spacing_width, float extra_flow, float extra_spacing, float width) { - float length_to_extrude = (volume_to_length(volume, perimeter_width, layer_height)) / extra_flow; + float length_to_extrude = (volume_to_length(volume, purge_line_width, layer_height)) / extra_flow; length_to_extrude = std::max(length_to_extrude, 0.f); - return (int(length_to_extrude / width) + 1) * perimeter_width * extra_spacing; + return (int(length_to_extrude / width) + 1) * row_spacing_width * extra_spacing; } // Appends a toolchange into m_plan and calculates neccessary depth of the corresponding box @@ -2447,7 +2455,8 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i return; // this is an actual toolchange - let's calculate depth to reserve on the wipe tower - float ramming_lw = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // ramming uses the OLD tool's own line width and the wipe the NEW tool's; the planned depths must match the widths toolchange_Unload() / toolchange_Wipe() will actually use. + float ramming_lw = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; float width = m_wipe_tower_width - m_perimeter_width - 2 * ramming_lw; // Orca: For non-SEMM multi-toolhead, ramming_speed contains only flow (not speed), so 0.25f * flow is meaningless. // Use the actual multitool_ramming_volume instead. @@ -2461,9 +2470,9 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i m_extra_spacing_ramming; float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); - float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par); - float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, width); + float first_wipe_volume = length_to_volume(first_wipe_line, tool_perimeter_width(new_tool) * m_extra_flow, layer_height_par); + float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, tool_perimeter_width(new_tool), + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); m_plan.back().tool_changes.push_back( WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); @@ -2482,7 +2491,8 @@ void WipeTower2::plan_local_z_toolchange(float z_par, float layer_height_par, un if (old_tool == new_tool) return; - float ramming_lw = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // ramming uses the OLD tool's own line width and the wipe the NEW tool's (see plan_toolchange()). + float ramming_lw = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; float width = m_wipe_tower_width - m_perimeter_width - 2 * ramming_lw; float ramming_volume = m_semm ? 0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f) @@ -2494,9 +2504,9 @@ void WipeTower2::plan_local_z_toolchange(float z_par, float layer_height_par, un m_extra_spacing_ramming; float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); - float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height_par); - float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, width); + float first_wipe_volume = length_to_volume(first_wipe_line, tool_perimeter_width(new_tool) * m_extra_flow, layer_height_par); + float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height_par, tool_perimeter_width(new_tool), + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); m_plan.back().local_z_tool_changes.push_back( WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume)); @@ -2514,9 +2524,15 @@ void WipeTower2::plan_local_z_reserve(float z_par, float layer_height_par, size_ const float mini_wipe_depth = m_local_z_wipe_tower_purge_lines * m_perimeter_width * m_extra_spacing_wipe; const float wipe_width = std::max(0.f, m_wipe_tower_width - 3.f * m_perimeter_width); + // The reserving slot does not know which tool will purge into it; plan the wipe length with the + // narrowest tool line width (worst case) while keeping the geometric row pitch (m_perimeter_width). + float min_tool_perimeter_width = m_perimeter_width; + for (const FilamentParameters& filament : m_filpar) + if (filament.perimeter_width > 0.f) + min_tool_perimeter_width = std::min(min_tool_perimeter_width, filament.perimeter_width); const float wiping_depth = wipe_width > WT_EPSILON ? - get_wipe_depth(std::max(0.f, wipe_volume), layer_height_par, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, wipe_width) : + get_wipe_depth(std::max(0.f, wipe_volume), layer_height_par, min_tool_perimeter_width, + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, wipe_width) : 0.f; float max_ramming_depth = 0.f; @@ -2526,9 +2542,11 @@ void WipeTower2::plan_local_z_reserve(float z_par, float layer_height_par, size_ if (!do_ramming || filament.ramming_speed.empty()) continue; - const float line_width = m_perimeter_width * filament.ramming_line_width_multiplicator; + // Each tool rams with its own line width (see toolchange_Unload()). + const float tool_width = filament.perimeter_width > 0.f ? filament.perimeter_width : m_perimeter_width; + const float line_width = tool_width * filament.ramming_line_width_multiplicator; const float line_step = - (m_perimeter_width * filament.ramming_line_width_multiplicator * filament.ramming_step_multiplicator) * + (tool_width * filament.ramming_line_width_multiplicator * filament.ramming_step_multiplicator) * m_extra_spacing_ramming; if (line_width <= WT_EPSILON || line_step <= WT_EPSILON) continue; @@ -2597,15 +2615,17 @@ void WipeTower2::save_on_last_wipe() if (i == idx) { float width = m_wipe_tower_width - 3 * m_perimeter_width; // width we draw into - float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), m_perimeter_width, + // ORCA: the finish layer and the wipe are extruded by the new tool with its own line width. + float volume_to_save = length_to_volume(finish_layer().total_extrusion_length_in_plane(), + tool_perimeter_width(toolchange.new_tool), m_layer_info->height); float volume_left_to_wipe = std::max(m_filpar[toolchange.new_tool].filament_minimal_purge_on_wipe_tower, toolchange.wipe_volume_total - volume_to_save); float volume_we_need_depth_for = std::max(0.f, volume_left_to_wipe - length_to_volume(toolchange.first_wipe_line, - m_perimeter_width * m_extra_flow, + tool_perimeter_width(toolchange.new_tool) * m_extra_flow, m_layer_info->height)); - float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, m_perimeter_width, m_extra_flow, - m_extra_spacing_wipe, width); + float depth_to_wipe = get_wipe_depth(volume_we_need_depth_for, m_layer_info->height, tool_perimeter_width(toolchange.new_tool), + m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); toolchange.required_depth = toolchange.ramming_depth + depth_to_wipe; toolchange.wipe_volume = volume_left_to_wipe; @@ -2930,7 +2950,8 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(int old_tool, i float ramming_volume = m_semm ? 0.25f * std::accumulate(m_filpar[old_tool].ramming_speed.begin(), m_filpar[old_tool].ramming_speed.end(), 0.f) : m_filpar[old_tool].multitool_ramming_volume; - float line_width = m_perimeter_width * m_filpar[old_tool].ramming_line_width_multiplicator; + // ramming uses the OLD tool's own line width and the wipe the NEW tool's (see plan_toolchange()). + float line_width = tool_perimeter_width(old_tool) * m_filpar[old_tool].ramming_line_width_multiplicator; float width = m_wipe_tower_width - m_perimeter_width - 2 * line_width; if (std::fabs(width) < EPSILON) { assert(false); @@ -2943,9 +2964,9 @@ WipeTower2::WipeTowerInfo::ToolChange WipeTower2::set_toolchange(int old_tool, i float ramming_depth = num_lines * line_width * m_filpar[old_tool].ramming_step_multiplicator * m_extra_spacing_ramming; float first_wipe_line = -(width * ((length_to_extrude / width) - int(length_to_extrude / width)) - width); - float first_wipe_volume = length_to_volume(first_wipe_line, m_perimeter_width * m_extra_flow, layer_height); + float first_wipe_volume = length_to_volume(first_wipe_line, tool_perimeter_width(new_tool) * m_extra_flow, layer_height); float wiping_depth = get_wipe_depth(wipe_volume - first_wipe_volume, layer_height, - m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); + tool_perimeter_width(new_tool), m_perimeter_width, m_extra_flow, m_extra_spacing_wipe, width); return WipeTowerInfo::ToolChange(old_tool, new_tool, ramming_depth + wiping_depth, ramming_depth, first_wipe_line, wipe_volume); } diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp index 459beddb057..aa23c553cd8 100644 --- a/src/libslic3r/GCode/WipeTower2.hpp +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -159,6 +159,8 @@ class WipeTower2 float max_e_speed = std::numeric_limits::max(); std::vector ramming_speed; float nozzle_diameter; + // ORCA: tower line width this tool extrudes (nozzle_diameter * Width_To_Nozzle_Ratio). + float perimeter_width = 0.f; float filament_area; bool multitool_ramming; float multitool_ramming_time = 0.f; @@ -185,6 +187,10 @@ class WipeTower2 float filament_area() const { return m_filpar[0].filament_area; // all extruders are assumed to have the same filament diameter at this point } + // Line width the given tool extrudes on the tower; m_perimeter_width (widest nozzle) keeps the geometric layout: line spacing, box sizes, depth planning. + float tool_perimeter_width(size_t tool) const { + return tool < m_filpar.size() && m_filpar[tool].perimeter_width > 0.f ? m_filpar[tool].perimeter_width : m_perimeter_width; + } bool m_change_pressure = true; float m_change_pressure_value = 0.0; @@ -272,12 +278,13 @@ class WipeTower2 bool is_first_layer() const { return size_t(m_layer_info - m_plan.begin()) == m_first_layer_idx; } - // Calculates extrusion flow needed to produce required line width for given layer height + // Calculates extrusion flow needed to produce required line width for given layer height. + // ORCA: uses the current tool's own line width, matching its nozzle. float extrusion_flow(float layer_height = -1.f) const // negative layer_height - return current m_extrusion_flow { if ( layer_height < 0 ) return m_extrusion_flow; - return layer_height * ( m_perimeter_width - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); + return layer_height * ( tool_perimeter_width(m_current_tool) - layer_height * (1.f-float(M_PI)/4.f)) / filament_area(); } diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 7d5716c2391..33665a525db 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -74,6 +74,21 @@ LayerRegion* Layer::add_region(const PrintRegion *print_region) return m_regions.back(); } +// ORCA: per-extruder layer height, see PrintObject::apply_extruder_layer_heights(). +double LayerRegion::combined_height() const +{ + return m_combined_height > 0. ? m_combined_height : m_layer->height; +} + +const Layer* LayerRegion::combined_lower_layer() const +{ + const Layer *layer = m_layer; + // count == 0 (layer combined away into a group top) is treated as 1; it produces no extrusions anyway. + for (unsigned short i = 0, count = std::max(m_combined_layer_count, 1); layer != nullptr && i < count; ++ i) + layer = layer->lower_layer; + return layer; +} + // merge all regions' slices to get islands void Layer::make_slices() { @@ -243,7 +258,9 @@ void Layer::make_perimeters() if (! (*it)->slices.empty()) { LayerRegion* other_layerm = *it; const PrintRegion &other_region = other_layerm->region(); - if (is_perimeter_compatible(this_region, other_region)) + // Regions combined to different extruder layer heights extrude with different heights and must not share a make_perimeters() call. + if ((*layerm)->combined_layer_count() == other_layerm->combined_layer_count() && + is_perimeter_compatible(this_region, other_region)) { other_layerm->perimeters.clear(); other_layerm->fills.clear(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 2412ad99c4a..cc3021f7dd5 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -81,8 +81,11 @@ class LayerRegion unsigned int extruder(FlowRole role) const; Flow flow(FlowRole role) const; Flow flow(FlowRole role, double layer_height) const; - Flow flow(FlowRole role, double layer_height, bool use_initial_layer_width) const; - Flow bridging_flow(FlowRole role, bool thick_bridge = false) const; + // filament_id: 1-based filament actually printing this flow when it differs from the role's default mapping (e.g. top/bottom surface fills, external bridges), 0 to resolve from the role. + Flow flow(FlowRole role, double layer_height, unsigned int filament_id) const; + Flow flow(FlowRole role, double layer_height, bool use_initial_layer_width, unsigned int filament_id = 0) const; + // layer_height overrides m_layer->height for the non-thick flow (combined layer groups print thicker), 0 to use m_layer->height. + Flow bridging_flow(FlowRole role, bool thick_bridge = false, unsigned int filament_id = 0, double layer_height = 0.) const; void slices_to_fill_surfaces_clipped(); void prepare_fill_surfaces(); @@ -107,6 +110,17 @@ class LayerRegion //BBS void simplify_infill_extrusion_entity() { simplify_entity_collection(&fills); } void simplify_wall_extrusion_entity() { simplify_entity_collection(&perimeters); } + + // ORCA: per-extruder layer height. Number of object layers this region's extrusions cover here: + // > 1 on the top layer of a group combined by PrintObject::apply_extruder_layer_heights(), 0 on + // the combined-away layers below such a top (their slices are empty and print nothing; the 0 + // tells them apart from layers where the region's geometry is genuinely absent), 1 otherwise. + unsigned short combined_layer_count() const { return m_combined_layer_count; } + // Extrusion height at this layer (sum of the covered layer heights when combined, else layer height). + double combined_height() const; + // Layer right below the covered group; replaces Layer::lower_layer for overhang / bridge + // detection of combined regions. May be nullptr. + const Layer* combined_lower_layer() const; private: void simplify_entity_collection(ExtrusionEntityCollection* entity_collection); void simplify_path(ExtrusionPath* path); @@ -123,6 +137,9 @@ class LayerRegion private: Layer *m_layer; const PrintRegion *m_region; + // ORCA: set by PrintObject::apply_extruder_layer_heights(), see combined_layer_count() / combined_height(). + unsigned short m_combined_layer_count { 1 }; + double m_combined_height { 0. }; }; class Layer diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 6d4ffc15861..4aa45311918 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -105,7 +105,13 @@ Flow LayerRegion::flow(FlowRole role, double layer_height) const return this->flow(role, layer_height, m_layer->id() == 0); } -Flow LayerRegion::flow(FlowRole role, double layer_height, bool use_initial_layer_width) const +// filament_id: 1-based filament actually printing this flow when it differs from the role's default mapping (e.g. top/bottom surface fills, external bridges), 0 to resolve from the role. +Flow LayerRegion::flow(FlowRole role, double layer_height, unsigned int filament_id) const +{ + return this->flow(role, layer_height, m_layer->id() == 0, filament_id); +} + +Flow LayerRegion::flow(FlowRole role, double layer_height, bool use_initial_layer_width, unsigned int filament_id) const { const PrintConfig &print_config = m_layer->object()->print()->config(); ConfigOptionFloatOrPercent config_width; @@ -130,17 +136,19 @@ Flow LayerRegion::flow(FlowRole role, double layer_height, bool use_initial_laye if (config_width.value == 0) config_width = m_layer->object()->config().line_width; - const auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1)); + // Width resolves against the nozzle of the filament that actually prints (filament_id, when given), + // which may differ from the role's default filament mapping. + const auto nozzle_diameter = float(print_config.nozzle_diameter.get_at((filament_id > 0 ? filament_id : this->extruder(role)) - 1)); return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } -Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const +Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge, unsigned int filament_id, double layer_height) const { const PrintRegion ®ion = this->region(); const PrintRegionConfig ®ion_config = region.config(); const PrintObject &print_object = *this->layer()->object(); Flow bridge_flow; - auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at(this->extruder(role) - 1)); + auto nozzle_diameter = float(print_object.print()->config().nozzle_diameter.get_at((filament_id > 0 ? filament_id : this->extruder(role)) - 1)); if (thick_bridge) { // The old Slic3r way (different from all other slicers): Use rounded extrusions. // Get the configured nozzle_diameter for the extruder associated to the flow role requested. @@ -149,7 +157,8 @@ Flow LayerRegion::bridging_flow(FlowRole role, bool thick_bridge) const bridge_flow = Flow::bridging_flow(float(sqrt(region_config.bridge_flow)) * nozzle_diameter, nozzle_diameter); } else { // The same way as other slicers: Use normal extrusions. Apply bridge_flow while maintaining the original spacing. - bridge_flow = this->flow(role).with_flow_ratio(region_config.bridge_flow); + // Combined layer groups stamp their full thickness on the surface; the base flow must be resolved at that height. + bridge_flow = this->flow(role, layer_height > 0. ? layer_height : m_layer->height, filament_id).with_flow_ratio(region_config.bridge_flow); } return bridge_flow; @@ -196,18 +205,21 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe (this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) && this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON); + // ORCA: on the top layer of a combined group all perimeters extrude with the whole group's height. + const double perimeter_height = this->combined_height(); + PerimeterGenerator g( // input: &slices, &compatible_regions, - this->layer()->height, + perimeter_height, this->layer()->slice_z, - this->flow(frPerimeter), + this->flow(frPerimeter, perimeter_height), &perimeter_config, &this->layer()->object()->config(), &print_config, spiral_mode, - + // output: &this->perimeters, &this->thin_fills, @@ -215,10 +227,11 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe //BBS fill_no_overlap ); - - if (this->layer()->lower_layer != nullptr) + + // Detect overhangs / bridges against the layer below the whole combined group (combined_lower_layer() == lower_layer for regular regions). + if (const Layer *lower_layer = this->combined_lower_layer(); lower_layer != nullptr) // Cummulative sum of polygons over all the regions. - g.lower_slices = &this->layer()->lower_layer->lslices; + g.lower_slices = &lower_layer->lslices; if (this->layer()->upper_layer != NULL) g.upper_slices = &this->layer()->upper_layer->lslices; @@ -227,9 +240,15 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe g.upper_slices_same_region = &this->layer()->upper_layer->get_region(region_id)->slices; g.layer_id = (int)this->layer()->id(); - g.ext_perimeter_flow = this->flow(frExternalPerimeter); - g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges); - g.solid_infill_flow = this->flow(frSolidInfill); + g.ext_perimeter_flow = this->flow(frExternalPerimeter, perimeter_height); + g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, 0, perimeter_height); + // Overhangs of external / fully overhanging loops dispatch to the outer wall filament (GCode::process_layer() splits mixed perimeters); resolve their width against its nozzle. + // The filament id comes from the resolved perimeter_config (mixed-filament aware), not the raw region config. + g.ext_overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, + (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value), perimeter_height); + g.solid_infill_flow = this->flow(frSolidInfill, perimeter_height); + // Gap fill dispatches to the outer wall filament (LayerTools::extruder()); resolve its width against its nozzle. + g.gap_fill_flow = this->flow(frSolidInfill, perimeter_height, (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value)); if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) g.process_arachne(); diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index a467cbaf934..3bf94f0b2ac 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -197,10 +197,14 @@ static ExtrusionEntityCollection traverse_loops(const PerimeterGenerator &perime // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter if (remain_polines.size() != 0) { - extrusion_paths_append(paths, std::move(remain_polines), - erOverhangPerimeter, perimeter_generator.mm3_per_mm_overhang(), - perimeter_generator.overhang_flow.width(), - perimeter_generator.overhang_flow.height()); + // External / fully overhanging loops (no supported path at all) dispatch to the outer wall filament (GCode::process_layer()); their overhangs use that filament's flow. + const bool overhang_external = is_external || paths.empty(); + const Flow &loop_overhang_flow = overhang_external ? perimeter_generator.ext_overhang_flow : + perimeter_generator.overhang_flow; + extrusion_paths_append(paths, std::move(remain_polines), erOverhangPerimeter, + overhang_external ? perimeter_generator.ext_mm3_per_mm_overhang() : + perimeter_generator.mm3_per_mm_overhang(), + loop_overhang_flow.width(), loop_overhang_flow.height()); } // Reapply the nearest point search for starting point. @@ -444,8 +448,13 @@ static ExtrusionEntityCollection traverse_extrusions(const PerimeterGenerator& p // get overhang paths by checking what parts of this loop fall // outside the grown lower slices (thus where the distance between // the loop centerline and original lower slices is >= half nozzle diameter + // External / fully overhanging extrusions (no supported path at all) dispatch to the outer + // wall filament (GCode::process_layer()); their overhangs use that filament's flow. Known + // corner: a fully overhanging fragment of the ExtrusionMultiPath split below keeps the inner + // flow, but widths come from the Arachne junctions either way; only thick-bridge height differs. + const bool overhang_external = is_external || paths.empty(); extrusion_paths_append(paths, clip_extrusion(extrusion_path, lower_slices_paths, ClipperLib_Z::ctDifference), erOverhangPerimeter, - perimeter_generator.overhang_flow); + overhang_external ? perimeter_generator.ext_overhang_flow : perimeter_generator.overhang_flow); // Reapply the nearest point search for starting point. // We allow polyline reversal because Clipper may have randomly reversed polylines during clipping. @@ -1068,8 +1077,9 @@ void PerimeterGenerator::apply_extra_perimeters(ExPolygons &infill_area) if (!m_spiral_vase && this->lower_slices != nullptr && this->config->detect_overhang_wall && this->config->extra_perimeters_on_overhangs && this->config->wall_loops > 0 && this->layer_id > this->object_config->raft_layers) { // Generate extra perimeters on overhang areas, and cut them to these parts only, to save print time and material + // Pure erOverhangPerimeter entities dispatch to the outer wall filament, so use its flow. auto [extra_perimeters, filled_area] = generate_extra_perimeters_over_overhangs(infill_area, this->lower_slices_polygons(), - this->config->wall_loops, this->overhang_flow, + this->config->wall_loops, this->ext_overhang_flow, this->m_scaled_resolution, *this->object_config, *this->print_config); if (!extra_perimeters.empty()) { @@ -1143,7 +1153,8 @@ void PerimeterGenerator::process_classic() // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); - + m_ext_mm3_per_mm_overhang = this->ext_overhang_flow.mm3_per_mm(); + // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); @@ -1589,7 +1600,8 @@ void PerimeterGenerator::process_classic() if (! polylines.empty()) { ExtrusionEntityCollection gap_fill; - variable_width(polylines, erGapFill, this->solid_infill_flow, gap_fill.entities); + // Gap fill prints with the outer wall filament (LayerTools::extruder()). + variable_width(polylines, erGapFill, this->gap_fill_flow, gap_fill.entities); /* Make sure we don't infill narrow parts that are already gap-filled (we only consider this surface's gaps to reduce the diff() complexity). Growing actual extrusions ensures that gaps not filled by medial axis @@ -2092,6 +2104,7 @@ void PerimeterGenerator::process_arachne() coord_t ext_perimeter_spacing2 = scaled(0.5f * (this->ext_perimeter_flow.spacing() + this->perimeter_flow.spacing())); // overhang perimeters m_mm3_per_mm_overhang = this->overhang_flow.mm3_per_mm(); + m_ext_mm3_per_mm_overhang = this->ext_overhang_flow.mm3_per_mm(); // solid infill coord_t solid_infill_spacing = this->solid_infill_flow.scaled_spacing(); diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 74be04294eb..93346c33f3b 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -73,7 +73,11 @@ class PerimeterGenerator { Flow perimeter_flow; Flow ext_perimeter_flow; Flow overhang_flow; + // Overhangs of external / fully overhanging loops print with the outer wall filament (GCode::process_layer() splits mixed perimeters by filament), whose nozzle may differ. + Flow ext_overhang_flow; Flow solid_infill_flow; + // Gap fill prints with the outer wall filament (LayerTools::extruder()), whose nozzle may differ from the internal solid infill filament's. + Flow gap_fill_flow; const PrintRegionConfig *config; const PrintObjectConfig *object_config; const PrintConfig *print_config; @@ -116,12 +120,12 @@ class PerimeterGenerator { ExPolygons* fill_no_overlap) : slices(slices), compatible_regions(compatible_regions), upper_slices(nullptr), lower_slices(nullptr), layer_height(layer_height), slice_z(slice_z), layer_id(-1), perimeter_flow(flow), ext_perimeter_flow(flow), - overhang_flow(flow), solid_infill_flow(flow), + overhang_flow(flow), ext_overhang_flow(flow), solid_infill_flow(flow), gap_fill_flow(flow), config(config), object_config(object_config), print_config(print_config), m_spiral_vase(spiral_mode), m_scaled_resolution(scaled(print_config->resolution.value > EPSILON ? print_config->resolution.value : EPSILON)), loops(loops), gap_fill(gap_fill), fill_surfaces(fill_surfaces), fill_no_overlap(fill_no_overlap), - m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1) + m_ext_mm3_per_mm(-1), m_mm3_per_mm(-1), m_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_overhang(-1), m_ext_mm3_per_mm_smaller_width(-1) {} void process_classic(); @@ -132,6 +136,7 @@ class PerimeterGenerator { double ext_mm3_per_mm() const { return m_ext_mm3_per_mm; } double mm3_per_mm() const { return m_mm3_per_mm; } double mm3_per_mm_overhang() const { return m_mm3_per_mm_overhang; } + double ext_mm3_per_mm_overhang() const { return m_ext_mm3_per_mm_overhang; } //BBS double smaller_width_ext_mm3_per_mm() const { return m_ext_mm3_per_mm_smaller_width; } Polygons lower_slices_polygons() const { return m_lower_slices_polygons; } @@ -148,6 +153,7 @@ class PerimeterGenerator { double m_ext_mm3_per_mm; double m_mm3_per_mm; double m_mm3_per_mm_overhang; + double m_ext_mm3_per_mm_overhang; //BBS double m_ext_mm3_per_mm_smaller_width; Polygons m_lower_slices_polygons; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index f499f0c9627..a56fd6a435f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -893,7 +893,7 @@ static std::vector s_Preset_print_options { "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", "filename_format", "outer_wall_filament_id", "inner_wall_filament_id", "support_bottom_z_distance", - "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_interface_filament","support_interface_not_for_body", + "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_nozzle_diameter", "support_interface_filament","support_interface_not_for_body", "ooze_prevention", "standby_temperature_delta", "preheat_time","delta_temperature","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "skin_infill_line_width","skeleton_infill_line_width", @@ -906,6 +906,8 @@ static std::vector s_Preset_print_options { "tree_support_branch_diameter", "tree_support_branch_diameter_angle", "detect_narrow_internal_solid_infill", "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/ + // ORCA: per-extruder layer height ("extruder_layer_height"). + "extruder_layer_height_mode", "extruder_layer_height_tolerance", "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "initial_layer_infill_speed", "only_one_wall_top", "timelapse_type", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index bb87f706e13..b9d023ea701 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -697,6 +697,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_shrinkage_compensation_z" || opt_key == "resolution" || opt_key == "precise_z_height" + // ORCA: layer combining for thicker extruder layer heights happens at the slicing step. + || opt_key == "extruder_layer_height" || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_whole_objects" @@ -807,6 +809,11 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "enable_arc_fitting" || opt_key == "print_order" || opt_key == "wall_sequence") { + // The min / max layer height limits gate layer combining at the slicing step (see region_layer_height_multiplier()), so re-slice while per-extruder layer heights are active. + if ((opt_key == "min_layer_height" || opt_key == "max_layer_height") && + std::any_of(m_config.extruder_layer_height.values.begin(), m_config.extruder_layer_height.values.end(), + [](double h) { return h > 0.; })) + osteps.emplace_back(posSlice); osteps.emplace_back(posPerimeters); osteps.emplace_back(posEstimateCurledExtrusions); osteps.emplace_back(posInfill); @@ -911,17 +918,27 @@ std::vector Print::support_material_extruders() const for (PrintObject *object : m_objects) { if (object->has_support_material()) { + // Under a support nozzle diameter restriction a "default" (0) support filament resolves to a matching-nozzle filament: one of the object's own or the deterministic fallback. + const unsigned int resolved_default = object->resolved_default_support_filament(); assert(object->config().support_filament >= 0); - if (object->config().support_filament == 0) - support_uses_current_extruder = true; - else { + if (object->config().support_filament == 0) { + if (resolved_default > 0) { + unsigned int i = resolved_default - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } else + support_uses_current_extruder = true; + } else { unsigned int i = (unsigned int)object->config().support_filament - 1; extruders.emplace_back((i >= num_extruders) ? 0 : i); } assert(object->config().support_interface_filament >= 0); - if (object->config().support_interface_filament == 0) - support_uses_current_extruder = true; - else { + if (object->config().support_interface_filament == 0) { + if (resolved_default > 0) { + unsigned int i = resolved_default - 1; + extruders.emplace_back((i >= num_extruders) ? 0 : i); + } else + support_uses_current_extruder = true; + } else { unsigned int i = (unsigned int)object->config().support_interface_filament - 1; extruders.emplace_back((i >= num_extruders) ? 0 : i); } @@ -1803,6 +1820,19 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } return true; }; + // ORCA: append per-extruder layer height / support nozzle diagnostics to the single warning out-param. + auto warn = [warning](const std::string &msg, const std::string &opt_key, const PrintObject *object) { + if (warning) { + if (! warning->string.empty()) + warning->string += "\n"; + warning->string += msg; + warning->opt_key = opt_key; + warning->object = object; + } + }; + // Flags for the per-extruder layer height / support nozzle warnings below. + bool warned_unhonored_heights = false, warned_support_mixed_nozzles = false, + warned_below_min_heights = false, warned_pitch_fallbacks = false; for (PrintObject *object : m_objects) { if (object->has_support_material()) { // BBS: remove useless logics and L() @@ -1824,6 +1854,26 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } #endif + // ORCA: multi-nozzle support restriction ("support_nozzle_diameter"). + if (const double support_nozzle = object->config().support_nozzle_diameter.value; support_nozzle > 0.) { + if (std::none_of(m_config.nozzle_diameter.values.begin(), m_config.nozzle_diameter.values.end(), + [support_nozzle](double d) { return std::abs(d - support_nozzle) < EPSILON; })) + return {L("No extruder has a nozzle matching the support nozzle diameter."), object, "support_nozzle_diameter"}; + if (object->config().support_filament.value > 0 && + std::abs(m_config.nozzle_diameter.get_at(object->config().support_filament.value - 1) - support_nozzle) > EPSILON) + return {L("The support/raft base filament prints with a nozzle that does not match the support nozzle diameter."), object, "support_filament"}; + if (object->config().support_interface_filament.value > 0 && + std::abs(m_config.nozzle_diameter.get_at(object->config().support_interface_filament.value - 1) - support_nozzle) > EPSILON) + return {L("The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter."), object, "support_interface_filament"}; + } else if (! warned_support_mixed_nozzles && max_nozzle_diameter - min_nozzle_diameter > EPSILON && + (object->config().support_filament.value == 0 || object->config().support_interface_filament.value == 0)) { + // Supports left on the "default" filament follow the active extruder, mixing nozzle sizes. + warn(L("Support may print with extruders of differing nozzle diameters. Set the support " + "nozzle diameter (or explicit support and interface filaments) to keep the support " + "on one nozzle size."), "support_nozzle_diameter", object); + warned_support_mixed_nozzles = true; + } + // Prusa: Fixing crashes with invalid tip diameter or branch diameter // https://github.com/prusa3d/PrusaSlicer/commit/96b3ae85013ac363cd1c3e98ec6b7938aeacf46d if (is_tree(object->config().support_type.value) && (object->config().support_style == smsTreeOrganic || @@ -1862,9 +1912,13 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* size_t first_layer_extruder = object->config().raft_layers == 1 ? object->config().support_interface_filament-1 : object->config().support_filament-1; - first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? - min_nozzle_diameter : - m_config.nozzle_diameter.get_at(first_layer_extruder); + if (first_layer_extruder == size_t(-1) && object->config().support_nozzle_diameter.value > 0.) + // ORCA: a "default" support filament under the restriction prints the raft with the restricted nozzle. + first_layer_min_nozzle_diameter = object->config().support_nozzle_diameter.value; + else + first_layer_min_nozzle_diameter = (first_layer_extruder == size_t(-1)) ? + min_nozzle_diameter : + m_config.nozzle_diameter.get_at(first_layer_extruder); } else { // if we don't have raft layers, any nozzle diameter is potentially used in first layer first_layer_min_nozzle_diameter = min_nozzle_diameter; @@ -1889,6 +1943,209 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* for (const PrintRegion ®ion : object->all_regions()) if (!validate_extrusion_width(region.config(), opt_key, layer_height, err_msg)) return {err_msg, object, opt_key}; + + // ORCA: per-extruder layer height ("extruder_layer_height"). + if (layer_height > EPSILON) { + // Gates diagnostics that would otherwise fire for configurations not using the feature. + const bool heights_feature_active = std::any_of(m_config.extruder_layer_height.values.begin(), + m_config.extruder_layer_height.values.end(), [](double v) { return v > 0.; }); + bool object_has_combined_regions = false; + for (const PrintRegion ®ion : object->all_regions()) { + const PrintRegionConfig ®ion_config = region.config(); + // The wall filaments define the part's layer pitch and must satisfy the strict lattice constraints below; must match the gating of PrintObject::region_layer_height_multiplier(). + std::vector lattice_filaments; // 0-based + if (region_config.wall_loops.value > 0) { + const int num_filaments = (int)m_config.filament_diameter.size(); + auto emplace_filament = [num_filaments, &lattice_filaments](int filament_id) { + int i = std::max(0, filament_id - 1); + lattice_filaments.emplace_back(i >= num_filaments ? 0 : i); + }; + emplace_filament(region_config.outer_wall_filament_id.value); + if (region_config.wall_loops.value > 1) + emplace_filament(region_config.inner_wall_filament_id.value); + } else + PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, lattice_filaments); + unsigned int region_multiplier = 0; + for (const unsigned int filament : lattice_filaments) { + // The filament index is the extruder index on classic multi-tool printers. + const size_t extruder_idx = filament; + const double extruder_height = m_config.extruder_layer_height.get_at(extruder_idx); + unsigned int multiplier = 1; + if (extruder_height > 0.) { + if (extruder_height < layer_height - EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). " + "Lower the object layer height to the finest extruder layer height."), + extruder_idx + 1, extruder_height, layer_height), object, "extruder_layer_height" }; + const int n = int(std::lround(extruder_height / layer_height)); + if (std::abs(extruder_height - n * layer_height) > EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)."), + extruder_idx + 1, extruder_height, layer_height), object, "extruder_layer_height" }; + if (extruder_height > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter."), + extruder_idx + 1, extruder_height), object, "extruder_layer_height" }; + // 0 means "auto" (0.75 of the nozzle diameter for adaptive layer heights), not enforced here. + if (const double max_lh = m_config.max_layer_height.get_at(extruder_idx); max_lh > EPSILON && extruder_height > max_lh + EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its maximum layer height (%3% mm)."), + extruder_idx + 1, extruder_height, max_lh), object, "extruder_layer_height" }; + if (const double min_lh = m_config.min_layer_height.get_at(extruder_idx); min_lh > EPSILON && extruder_height < min_lh - EPSILON) + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is below its minimum layer height (%3% mm)."), + extruder_idx + 1, extruder_height, min_lh), object, "extruder_layer_height" }; + multiplier = (unsigned int)n; + } + if (region_multiplier == 0) + region_multiplier = multiplier; + else if (region_multiplier != multiplier) { + // Walls print together; differing preferences fall back to the object layer height (warned below). + region_multiplier = 1; + break; + } + } + if (region_multiplier == 0) + region_multiplier = 1; + // 0-based indices of all filaments printing this region's features. + std::vector used_filaments; + PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, used_filaments); + if (region_multiplier > 1) + // A feature filament that cannot print the walls' pitch makes the part fall back to the object layer height (mirrors PrintObject::region_layer_height_multiplier()). + for (const unsigned int filament : used_filaments) { + const size_t extruder_idx = filament; // filament index == extruder index here + const double pitch = region_multiplier * layer_height; + const double max_lh = m_config.max_layer_height.get_at(extruder_idx); + const double min_lh = m_config.min_layer_height.get_at(extruder_idx); + if (pitch > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON || + (max_lh > EPSILON && pitch > max_lh + EPSILON) || + (min_lh > EPSILON && pitch < min_lh - EPSILON)) { + if (! warned_pitch_fallbacks) { + warned_pitch_fallbacks = true; + warn(Slic3r::format(_u8L("The walls of some object parts prefer %1% mm layers, but filament %2% printing " + "other features of the part cannot print that height (nozzle diameter or layer " + "height limits). These parts print with the object layer height instead."), + pitch, filament + 1), + "extruder_layer_height", object); + } + region_multiplier = 1; + break; + } + } + const double region_pitch = region_multiplier * layer_height; + // Infill combines up to the preferred height of its printing filament (sparse, or internal solid at 100% density; mirrors PrintObject::combine_infill()); its width must cover the tallest group. + const bool solid_combined_infill = std::fabs(region_config.sparse_infill_density.value - 100.) < EPSILON; + double combined_infill_height = 0.; + unsigned int combined_infill_filament = 0; // 0-based, only valid while combining + if (region_multiplier == 1 && region_config.sparse_infill_density.value > 0) { + const int filament_id = solid_combined_infill ? region_config.internal_solid_filament_id.value : + region_config.sparse_infill_filament_id.value; + const FlowRole role = solid_combined_infill ? frSolidInfill : frInfill; + const char *width_key = solid_combined_infill ? "internal_solid_infill_line_width" : "sparse_infill_line_width"; + const double preferred = object->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)); + if (preferred > layer_height + EPSILON) { + const double infill_nozzle = m_config.nozzle_diameter.get_at(region.extruder(role) - 1); + // Mirrors PrintObject::combine_infill(): groups capped by both feature nozzles and the printing extruder's max layer height (0 = unset). + const unsigned int combine_filament0 = (unsigned int)std::max(1, filament_id) - 1; + double combine_cap = std::min({preferred, + m_config.nozzle_diameter.get_at(std::max(1, region_config.sparse_infill_filament_id.value) - 1), + m_config.nozzle_diameter.get_at(std::max(1, region_config.internal_solid_filament_id.value) - 1)}); + if (const double max_lh = m_config.max_layer_height.get_at(combine_filament0); max_lh > EPSILON) + combine_cap = std::min(combine_cap, max_lh); + combined_infill_height = std::floor(combine_cap / layer_height + EPSILON) * layer_height; + combined_infill_filament = combine_filament0; + auto width_opt = *region_config.option(width_key); + if (width_opt.value == 0.) + width_opt = object->config().line_width; + const double width = width_opt.value == 0. ? + double(Flow::auto_extrusion_width(role, float(infill_nozzle))) : + width_opt.get_abs_value(infill_nozzle); + if (combined_infill_height > layer_height + EPSILON && width <= combined_infill_height + EPSILON) + return { Slic3r::format(_u8L("The %1% mm infill line width is too small for infill combined to %2% mm high layers. " + "Increase the line width or lower the preferred layer height of the infill filament."), + width, combined_infill_height), object, width_key }; + } + } + // Warn once when a filament's minimum layer height is above the height its features print at (the part's pitch; the combined infill prints above the minimum by construction). + if (! warned_below_min_heights && heights_feature_active) + for (const unsigned int filament : used_filaments) { + const double min_lh = m_config.min_layer_height.get_at(filament); + if (min_lh <= EPSILON) + continue; + const bool pitch_below_min = region_pitch < min_lh - EPSILON; + // Areas of a combined region that cannot reach the pitch fall back to the object layer height (see apply_extruder_layer_heights()), possibly below the minimum. + const bool fallback_below_min = region_multiplier > 1 && layer_height < min_lh - EPSILON; + if (! pitch_below_min && ! fallback_below_min) + continue; + if (combined_infill_height > 0. && filament == combined_infill_filament && + combined_infill_height >= min_lh - EPSILON && ! fallback_below_min) + continue; + warned_below_min_heights = true; + warn(Slic3r::format(_u8L("Some object parts print %1% mm layers with filament %2% whose minimum layer " + "height is %3% mm. Raise the object layer height, use a filament with a finer " + "nozzle for these features, or accept printing below the extruder's minimum."), + pitch_below_min ? region_pitch : layer_height, filament + 1, min_lh), + "extruder_layer_height", object); + break; + } + // Warn once about preferred heights the part cannot honor: walls set the pitch, infill combines separately, everything else prints with the pitch. + if (! warned_unhonored_heights) + for (const unsigned int filament : used_filaments) { + const double preferred = object->extruder_preferred_layer_height(filament + 1); + if (preferred <= 0. || std::abs(preferred - region_pitch) < EPSILON) + continue; + if (combined_infill_height > 0. && filament == combined_infill_filament) + continue; + // At 100% density no sparse surfaces exist, the sparse filament's preference is moot. + if (solid_combined_infill && + filament == (unsigned int)std::max(1, region_config.sparse_infill_filament_id.value) - 1) + continue; + warned_unhonored_heights = true; + warn(_u8L("Some object parts use filaments whose preferred layer heights cannot all be honored: " + "the wall filaments set a part's layer pitch, the infill can combine to a thicker height, " + "and the remaining features print with the part's pitch."), + "extruder_layer_height", object); + break; + } + if (region_multiplier > 1) { + object_has_combined_regions = true; + // The combined extrusions are thicker, so line widths must stay above the combined + // height, resolved per role against the region's own nozzle (mirrors PrintRegion::flow()), + // unlike validate_extrusion_width() above which uses the object's smallest nozzle. + // (role, 1-based filament printing it, width key). Bottom surfaces print + // the solid infill width with the bottom surface filament (Fill.cpp). + const std::tuple width_roles[] = { + { frExternalPerimeter, region_config.outer_wall_filament_id.value, "outer_wall_line_width" }, + { frPerimeter, region_config.inner_wall_filament_id.value, "inner_wall_line_width" }, + { frInfill, region_config.sparse_infill_filament_id.value, "sparse_infill_line_width" }, + { frSolidInfill, region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width" }, + { frTopSolidInfill, region_config.top_surface_filament_id.value, "top_surface_line_width" }, + { frSolidInfill, region_config.bottom_surface_filament_id.value, "internal_solid_infill_line_width" }, + }; + for (const auto &[role, filament_id, width_key] : width_roles) { + const double nozzle_diameter = m_config.nozzle_diameter.get_at(std::max(1, filament_id) - 1); + auto width_opt = *region_config.option(width_key); + if (width_opt.value == 0.) + width_opt = object->config().line_width; + const double width = width_opt.value == 0. ? + double(Flow::auto_extrusion_width(role, float(nozzle_diameter))) : + width_opt.get_abs_value(nozzle_diameter); + if (width <= region_pitch + EPSILON) + return { Slic3r::format(_u8L("The %1% mm line width is too small for the %2% mm layer height of its extruder. " + "Increase the line width or lower the extruder layer height."), + width, region_pitch), object, width_key }; + } + } + } + if (object_has_combined_regions) { + if (m_config.spiral_mode) + return { _u8L("Per-extruder layer heights are not supported in spiral vase mode."), object, "extruder_layer_height" }; + if (object->config().interface_shells) + return { _u8L("Per-extruder layer heights are not supported together with interface shells."), object, "extruder_layer_height" }; + if (object->model_object()->has_custom_layering()) { + std::vector profile; + PrintObject::update_layer_height_profile(*object->model_object(), object->slicing_parameters(), profile, object); + if (! check_object_layers_fixed(object->slicing_parameters(), profile)) + return { _u8L("Per-extruder layer heights are not supported together with variable layer height."), + object, "extruder_layer_height" }; + } + } + } } } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index b618641c35e..6632506b945 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -154,7 +154,9 @@ class PrintRegion int print_object_region_id() const throw() { return m_print_object_region_id; } // 1-based extruder identifier for this region and role. unsigned int extruder(FlowRole role) const; - Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false) const; + // filament_id: 1-based filament actually printing this flow when it differs from the role's default + // mapping (top / bottom surface fills), 0 to resolve the filament from the role. + Flow flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer = false, unsigned int filament_id = 0) const; // Average diameter of nozzles participating on extruding this region. coordf_t nozzle_dmr_avg(const PrintConfig &print_config) const; // Average diameter of nozzles participating on extruding this region. @@ -486,6 +488,23 @@ class PrintObject : public PrintObjectBaseWithState object_extruders() const; + // ORCA: per-extruder layer height ("extruder_layer_height" printer option). + // Preferred layer height (mm) of the extruder printing the given 1-based filament id; 0 when unset or not reliably resolvable. + double extruder_preferred_layer_height(unsigned int filament_id) const; + // Object layers a region printing with the given 1-based filament id combines into one extrusion where its geometry allows it (1 = no combining). + unsigned int layer_height_multiplier_for_filament(unsigned int filament_id) const; + // Layer pitch multiplier of a region, driven by its wall filaments; 1 when no combining is requested or possible. + unsigned int region_layer_height_multiplier(const PrintRegion ®ion) const; + // Any region of this object printing with a layer height multiplier > 1? + bool has_combined_layer_regions() const; + // Multi-nozzle support restriction ("support_nozzle_diameter" print option): may the given 1-based filament print this object's support / raft? + // Always true when the restriction is disabled or for the "default" filament 0. + bool support_filament_allowed(unsigned int filament_id) const; + // 1-based filament a "default" (0) support filament resolves to under the support nozzle + // diameter restriction when none of a layer's own filaments match: the first non-soluble + // matching-nozzle filament, else the first matching one; 0 when the restriction is disabled or nothing matches. + unsigned int resolved_default_support_filament() const; + // Called by make_perimeters() void slice(); @@ -550,6 +569,8 @@ class PrintObject : public PrintObjectBaseWithStateadd("min_layer_height", coFloats); def->label = L("Min"); def->tooltip = L("The lowest printable layer height for the extruder. " - "Used to limit the minimum layer height when enable adaptive layer height."); + "Used to limit the minimum layer height when enable adaptive layer height. " + "Parts printed with a thicker preferred extruder layer height never fall back " + "below this height either (the first layer excepted)."); def->sidetext = "mm"; // milimeters, don't need translation def->min = 0; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloats { 0.07 }); + def = this->add("extruder_layer_height", coFloats); + def->label = L("Preferred layer height"); + def->tooltip = L("Layer height this extruder should print with, used for printers whose extruders have " + "different nozzle sizes. Object parts whose walls are assigned to this extruder are " + "printed only on every Nth layer with correspondingly thicker extrusions, wherever " + "their geometry allows it (near-vertical walls); elsewhere they fall back to the " + "object layer height. It must then be an integer multiple of the object layer height. " + "When only the sparse infill of a part uses this extruder, the infill is combined up " + "to this height instead. 0 means to use the object layer height."); + def->sidetext = "mm"; // milimeters, don't need translation + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloats { 0. }); + + def = this->add("extruder_layer_height_mode", coEnum); + def->label = L("Thick layer regions"); + def->category = L("Quality"); + def->tooltip = L("How aggressively object parts assigned to an extruder with a thicker preferred layer " + "height are combined into thick layers.\n" + "Consistent: parts print with at most two layer heights, the extruder layer height " + "wherever whole runs of layers fit and the object layer height everywhere else. This " + "gives the most uniform walls.\n" + "Adaptive: runs may also be combined at intermediate multiples of the object layer " + "height, so more of the part prints with thicker layers, at the price of bands of " + "varying layer heights on curved part boundaries."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("consistent"); + def->enum_values.push_back("adaptive"); + def->enum_labels.push_back(L("Consistent")); + def->enum_labels.push_back(L("Adaptive")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(elhmConsistent)); + + def = this->add("extruder_layer_height_tolerance", coPercent); + def->label = L("Thick layer tolerance"); + def->category = L("Quality"); + def->tooltip = L("How far the outline of an object part assigned to an extruder with a thicker preferred " + "layer height may drift sideways across the layers of one thick run and still be combined, " + "as a percentage of that extruder's nozzle diameter. Higher values combine more of curved " + "part boundaries into thick layers, at the price of rougher boundary walls: deviations up " + "to this fraction of the nozzle diameter are swallowed by the thick extrusions."); + def->sidetext = "%"; + def->min = 0; + def->max = 100; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionPercent(10)); + def = this->add("slow_down_min_speed", coFloats); def->label = L("Min print speed"); def->tooltip = L("The minimum print speed to which the printer slows down to maintain the minimum layer time defined above " @@ -5353,6 +5409,19 @@ void PrintConfigDef::init_fff_params() def->mode = comSimple; def->set_default_value(new ConfigOptionBool(true)); + def = this->add("support_nozzle_diameter", coFloat); + def->label = L("Support nozzle diameter"); + def->category = L("Support"); + def->tooltip = L("On printers whose extruders have different nozzle diameters, only filaments of this " + "nozzle diameter are used to print support, raft and support interface. This keeps " + "filaments of other nozzle sizes - with their different line widths and layer height " + "limits - out of the support. Support filaments set to a non-default value must match " + "this diameter. Value 0 allows any filament to print support."); + def->sidetext = "mm"; // milimeters, don't need translation + def->min = 0; + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("support_line_width", coFloatOrPercent); def->label = L("Support"); def->category = L("Quality"); @@ -6453,7 +6522,7 @@ void PrintConfigDef::init_extruder_option_keys() { // ConfigOptionFloats, ConfigOptionPercents, ConfigOptionBools, ConfigOptionStrings m_extruder_option_keys = { - "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_offset", + "nozzle_diameter", "min_layer_height", "max_layer_height", "extruder_layer_height", "extruder_offset", "retraction_length", "z_hop", "z_hop_types", "z_hop_when_prime", "travel_slope", "retract_lift_above", "retract_lift_below", "retract_lift_enforce", "retraction_speed", "deretraction_speed", "retract_before_wipe", "retract_restart_extra", "retraction_minimum_travel", "wipe", "wipe_distance", "retract_when_changing_layer", "retract_length_toolchange", "retract_restart_extra_toolchange", "extruder_colour", diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 102f1a7105e..b7aa97ea316 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -195,6 +195,12 @@ enum EnsureVerticalShellThickness { evstAll, }; +// ORCA: per-extruder layer height ("extruder_layer_height"). +enum ExtruderLayerHeightMode { + elhmConsistent, + elhmAdaptive, +}; + //Orca enum InternalBridgeFilter { ibfDisabled, ibfLimited, ibfNofilter @@ -460,6 +466,7 @@ static std::string get_bed_temp_1st_layer_key(const BedType type) template<> const t_config_enum_values& ConfigOptionEnum::get_enum_values(); CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ExtruderLayerHeightMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinMode) @@ -837,6 +844,9 @@ PRINT_CONFIG_CLASS_DEFINE( // Force the generation of solid shells between adjacent materials/volumes. ((ConfigOptionBool, interface_shells)) ((ConfigOptionFloat, layer_height)) + // ORCA: per-extruder layer height ("extruder_layer_height"). + ((ConfigOptionEnum, extruder_layer_height_mode)) + ((ConfigOptionPercent, extruder_layer_height_tolerance)) ((ConfigOptionFloat, mmu_segmented_region_max_width)) ((ConfigOptionFloat, mmu_segmented_region_interlocking_depth)) ((ConfigOptionFloat, raft_contact_distance)) @@ -860,6 +870,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, support_bottom_z_distance)) ((ConfigOptionInt, enforce_support_layers)) ((ConfigOptionInt, support_filament)) + // ORCA: restrict support/raft/interface printing to filaments of this nozzle diameter (0 = no restriction). + ((ConfigOptionFloat, support_nozzle_diameter)) ((ConfigOptionFloatOrPercent, support_line_width)) ((ConfigOptionBool, support_interface_not_for_body)) ((ConfigOptionBool, support_interface_loop_pattern)) @@ -1358,6 +1370,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionFloats, max_layer_height)) ((ConfigOptionFloats, fan_min_speed)) ((ConfigOptionFloats, min_layer_height)) + ((ConfigOptionFloats, extruder_layer_height)) ((ConfigOptionFloat, printable_height)) ((ConfigOptionPoint, best_object_pos)) ((ConfigOptionFloats, slow_down_min_speed)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index df7cbcdbcd9..990f7855de6 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -519,6 +519,18 @@ void PrintObject::prepare_infill() this->combine_infill(); m_print->throw_if_canceled(); + // Per-extruder layer height. Stamp the group height onto fill surfaces of combined LayerRegions (Fill uses Surface::thickness when set); must run last, earlier steps re-create surfaces. + if (this->has_combined_layer_regions()) { + for (Layer *layer : m_layers) + for (LayerRegion *layerm : layer->m_regions) + if (layerm->combined_layer_count() > 1) + for (Surface &surface : layerm->fill_surfaces.surfaces) { + surface.thickness = layerm->combined_height(); + surface.thickness_layers = layerm->combined_layer_count(); + } + m_print->throw_if_canceled(); + } + #ifdef SLIC3R_DEBUG_SLICE_PROCESSING for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { for (const Layer *layer : m_layers) { @@ -881,6 +893,18 @@ bool PrintObject::invalidate_state_by_config_options( std::vector steps; bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { + // Per-extruder layer height: these options feed region_layer_height_multiplier(), which decides layer combination at the slicing step. + if (opt_key == "outer_wall_filament_id" || opt_key == "inner_wall_filament_id" + || opt_key == "sparse_infill_filament_id" || opt_key == "internal_solid_filament_id" + || opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id" + || opt_key == "wall_loops" || opt_key == "sparse_infill_density" + || opt_key == "top_shell_layers" || opt_key == "bottom_shell_layers") { + for (double extruder_layer_height : m_print->config().extruder_layer_height.values) + if (extruder_layer_height > 0.) { + steps.emplace_back(posSlice); + break; + } + } if ( opt_key == "brim_width" || opt_key == "brim_object_gap" || opt_key == "brim_type" @@ -947,6 +971,9 @@ bool PrintObject::invalidate_state_by_config_options( steps.emplace_back(posPerimeters); } else if ( opt_key == "layer_height" + // ORCA: per-extruder layer height: the layer combining happens at the slicing step. + || opt_key == "extruder_layer_height_mode" + || opt_key == "extruder_layer_height_tolerance" || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_whole_objects" @@ -1019,6 +1046,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "support_remove_small_overhang" || opt_key == "enforce_support_layers" || opt_key == "support_filament" + || opt_key == "support_nozzle_diameter" || opt_key == "support_line_width" || opt_key == "support_interface_top_layers" || opt_key == "support_interface_bottom_layers" @@ -1707,11 +1735,17 @@ void PrintObject::process_external_surfaces() for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++ layer_idx) { m_print->throw_if_canceled(); // BOOST_LOG_TRIVIAL(trace) << "Processing external surface, layer" << m_layers[layer_idx]->print_z; - m_layers[layer_idx]->get_region(int(region_id))->process_external_surfaces( + LayerRegion *layerm = m_layers[layer_idx]->get_region(int(region_id)); + // Per-extruder layer height: a combined group's external surfaces relate to the layer below the whole group (lower_idx == layer_idx - 1 for regular regions; + // count == 0 on combined-away layers, hence the max(..., 1)). + const size_t combined_layers = std::max(layerm->combined_layer_count(), 1); + const bool has_lower = layer_idx >= combined_layers; + const size_t lower_idx = has_lower ? layer_idx - combined_layers : 0; + layerm->process_external_surfaces( // lower layer - (layer_idx == 0) ? nullptr : m_layers[layer_idx - 1], + has_lower ? m_layers[lower_idx] : nullptr, // lower layer polygons with density > 0% - (layer_idx == 0 || surfaces_covered.empty() || surfaces_covered[layer_idx - 1].empty()) ? nullptr : &surfaces_covered[layer_idx - 1]); + (! has_lower || surfaces_covered.empty() || surfaces_covered[lower_idx].empty()) ? nullptr : &surfaces_covered[lower_idx]); } } ); @@ -1828,6 +1862,9 @@ void PrintObject::discover_vertical_shells() if (region.config().ensure_vertical_shell_thickness.value != evstAll ) // This region will be handled by discover_horizontal_shells(). continue; + if (this->region_layer_height_multiplier(region) > 1) + // Per-extruder layer height. Combined regions extrude on group tops only; their shells are ensured by discover_horizontal_shells() stepping over the groups instead. + continue; //FIXME Improve the heuristics for a grain size. size_t grain_size = std::max(num_layers / 16, size_t(1)); @@ -3439,6 +3476,124 @@ std::vector PrintObject::object_extruders() const return extruders; } +// Per-extruder layer height ("extruder_layer_height" printer option). +// Raw preferred layer height (mm) of the extruder printing the given 1-based filament id, 0 when unset or not reliably resolvable. +double PrintObject::extruder_preferred_layer_height(unsigned int filament_id) const +{ + const PrintConfig &print_config = m_print->config(); + if (print_config.extruder_layer_height.empty()) + return 0.; + // Region filament ids are 1-based, 0 ("Default") falls back to the first filament. + if (filament_id == 0) + filament_id = 1; + // Classic multi-tool printers index per-extruder vectors by the filament id directly (like the min/max_layer_height and nozzle_diameter lookups elsewhere). + const size_t extruder_idx = filament_id - 1; + return std::max(0., print_config.extruder_layer_height.get_at(extruder_idx)); +} + +// Returns the number of object layers a region printing with the given 1-based filament id combines into one extrusion (1 = no combining). +// The configured height must be a (near) integer multiple of the object layer height, otherwise it is ignored here and rejected by Print::validate(). +unsigned int PrintObject::layer_height_multiplier_for_filament(unsigned int filament_id) const +{ + const double extruder_height = this->extruder_preferred_layer_height(filament_id); + if (extruder_height <= 0.) + return 1; + const double layer_height = m_config.layer_height.value; + if (layer_height <= EPSILON) + return 1; + const int multiplier = int(std::lround(extruder_height / layer_height)); + if (multiplier <= 1 || std::abs(extruder_height - multiplier * layer_height) > EPSILON) + // Not combining, or not an integer multiple (the latter is rejected by Print::validate()). + return 1; + // Same filament -> extruder mapping as extruder_preferred_layer_height() and Print::validate(). + const PrintConfig &print_config = m_print->config(); + const size_t extruder_idx = size_t((filament_id == 0 ? 1 : filament_id) - 1); + if (extruder_height > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + // Taller than the nozzle can print, rejected by Print::validate(). + return 1; + return (unsigned int)multiplier; +} + +unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®ion) const +{ + const PrintRegionConfig &config = region.config(); + // The wall filaments define a region's layer pitch (sparse infill preferring taller layers is combined separately in combine_infill()); wall-less regions need agreement of all their feature filaments. + // Disagreeing filaments keep the object layer height (Print::validate() warns). + std::vector filaments; // 0-based filament indices + if (config.wall_loops.value > 0) { + const int num_filaments = (int)m_print->config().filament_diameter.size(); + auto emplace_filament = [num_filaments, &filaments](int filament_id) { + int i = std::max(0, filament_id - 1); + filaments.emplace_back(i >= num_filaments ? 0 : i); + }; + emplace_filament(config.outer_wall_filament_id.value); + if (config.wall_loops.value > 1) + emplace_filament(config.inner_wall_filament_id.value); + } else + // The brim is not considered, it prints on the first layer which is never combined. + PrintRegion::collect_object_printing_extruders(m_print->config(), config, false /* has_brim */, filaments); + unsigned int multiplier = 0; + for (unsigned int filament : filaments) { + unsigned int m = this->layer_height_multiplier_for_filament(filament + 1); + if (multiplier == 0) + multiplier = m; + else if (m != multiplier) + return 1; + } + if (multiplier <= 1) + return 1; + // A feature filament whose nozzle or layer height limits cannot follow the walls' pitch makes the region fall back to the object layer height (Print::validate() warns about it). + const PrintConfig &print_config = m_print->config(); + const double pitch = multiplier * m_config.layer_height.value; + std::vector used_filaments; // 0-based filament indices + PrintRegion::collect_object_printing_extruders(print_config, config, false /* has_brim */, used_filaments); + for (unsigned int filament : used_filaments) { + // The filament index is the extruder index on classic multi-tool printers. + const size_t extruder_idx = size_t(filament); + if (pitch > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + return 1; + if (const double max_lh = print_config.max_layer_height.get_at(extruder_idx); max_lh > EPSILON && pitch > max_lh + EPSILON) + return 1; + if (const double min_lh = print_config.min_layer_height.get_at(extruder_idx); min_lh > EPSILON && pitch < min_lh - EPSILON) + return 1; + } + return multiplier; +} + +bool PrintObject::has_combined_layer_regions() const +{ + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) + if (this->region_layer_height_multiplier(this->printing_region(region_id)) > 1) + return true; + return false; +} + +bool PrintObject::support_filament_allowed(unsigned int filament_id) const +{ + const double restriction = m_config.support_nozzle_diameter.value; + if (restriction <= 0. || filament_id == 0) + return true; + return std::abs(m_print->config().nozzle_diameter.get_at(filament_id - 1) - restriction) < EPSILON; +} + +unsigned int PrintObject::resolved_default_support_filament() const +{ + if (m_config.support_nozzle_diameter.value <= 0.) + return 0; + const PrintConfig &print_config = m_print->config(); + const size_t num_filaments = std::max(print_config.nozzle_diameter.values.size(), + print_config.filament_diameter.values.size()); + unsigned int soluble_fallback = 0; + for (size_t i = 0; i < num_filaments; ++ i) + if (this->support_filament_allowed((unsigned int)(i + 1))) { + if (! print_config.filament_soluble.get_at(i)) + return (unsigned int)(i + 1); + if (soluble_fallback == 0) + soluble_fallback = (unsigned int)(i + 1); + } + return soluble_fallback; +} + namespace { struct LayerHeightRangeOverride { @@ -4122,6 +4277,9 @@ void PrintObject::clip_fill_surfaces() for (LayerRegion *layerm : lower_layer->m_regions) { if (layerm->region().config().sparse_infill_density.value == 0) continue; + if (layerm->combined_layer_count() > 1) + // Per-extruder layer height: a combined group's infill supports the whole group, don't clip it against the single layer above. + continue; Polygons internal; for (Surface &surface : layerm->fill_surfaces.surfaces) if (surface.surface_type == stInternal || surface.surface_type == stInternalVoid) @@ -4145,6 +4303,9 @@ void PrintObject::discover_horizontal_shells() BOOST_LOG_TRIVIAL(trace) << "discover_horizontal_shells()"; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + // ORCA: per-extruder layer height. Combined regions extrude only on the top layers of their + // groups; the empty layers in between must neither receive shells nor count as shell layers. + const bool region_combined = this->region_layer_height_multiplier(this->printing_region(region_id)) > 1; for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); Layer *layer = m_layers[i]; @@ -4160,7 +4321,8 @@ void PrintObject::discover_horizontal_shells() } // If ensure_vertical_shell_thickness, then the rest has already been performed by discover_vertical_shells(). - if (region_config.ensure_vertical_shell_thickness.value == evstAll) + // ORCA: combined regions are skipped by discover_vertical_shells() and handled here instead. + if (region_config.ensure_vertical_shell_thickness.value == evstAll && ! region_combined) continue; coordf_t print_z = layer->print_z; @@ -4197,17 +4359,26 @@ void PrintObject::discover_horizontal_shells() // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom'; // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. + // ORCA: per-extruder layer height. n_region_layers counts only layers the region + // prints on (equals the int(i) - n index distance when nothing is skipped), so + // shells of combined regions propagate across group tops. + int n_region_layers = 0; for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1; (type == stTop) ? - (n >= 0 && (int(i) - n < num_solid_layers || + (n >= 0 && (n_region_layers + 1 < num_solid_layers || print_z - m_layers[n]->print_z < region_config.top_shell_thickness.value - EPSILON)) : - (n < int(m_layers.size()) && (n - int(i) < num_solid_layers || + (n < int(m_layers.size()) && (n_region_layers + 1 < num_solid_layers || m_layers[n]->bottom_z() - bottom_z < region_config.bottom_shell_thickness.value - EPSILON)); (type == stTop) ? -- n : ++ n) { // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id]; + // ORCA: combined-away layers (count == 0) print nothing here: no shells, not + // counted. Genuinely absent geometry (count == 1, empty slices) still counts. + if (region_combined && neighbor_layerm->combined_layer_count() == 0) + continue; + ++ n_region_layers; // find intersection between neighbor and current layer's surfaces // intersections have contours and holes @@ -4358,7 +4529,20 @@ void PrintObject::combine_infill() const PrintRegion ®ion = this->printing_region(region_id); //BBS const bool enable_combine_infill = region.config().infill_combination.value; - if (enable_combine_infill == false || region.config().sparse_infill_density == 0.) + // Per-extruder layer height: infill whose filament prefers a taller layer height is combined up to it, with or without the infill_combination option. + // Only for regions printing at the object layer height; combined-wall regions already print everything over their runs. + if (this->region_layer_height_multiplier(region) > 1) + continue; + // At 100% density the combined surfaces are internal solid infill (see use_solid_infill + // below), so that filament's preference and limits decide. + const int combine_filament_id = std::max(1, std::fabs(region.config().sparse_infill_density.value - 100.) < EPSILON ? + region.config().internal_solid_filament_id.value : region.config().sparse_infill_filament_id.value); + // Per-extruder vector index: the filament index is the extruder index on classic multi-tool printers (like extruder_preferred_layer_height()). + const size_t combine_extruder_idx = size_t(combine_filament_id - 1); + double preferred_infill_height = this->extruder_preferred_layer_height((unsigned int)combine_filament_id); + if (preferred_infill_height <= m_config.layer_height.value + EPSILON) + preferred_infill_height = 0.; + if ((enable_combine_infill == false && preferred_infill_height == 0.) || region.config().sparse_infill_density == 0.) continue; // Support internal solid infill when sparse_infill_density is 100% @@ -4377,7 +4561,18 @@ void PrintObject::combine_infill() //Orca: Limit combination of infill to up to infill_combination_max_layer_height const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter); nozzle_diameter = infill_combination_max_layer_height > 0 ? std::min(infill_combination_max_layer_height, nozzle_diameter) : nozzle_diameter; - + + // Per-extruder layer height: the filament's preferred height is an explicit target that overrides the caps above, limited only by the physical nozzle diameters and the extruder's max layer height. + // Plain infill_combination keeps its historical cap untouched. + if (preferred_infill_height > 0.) { + nozzle_diameter = std::min({preferred_infill_height, + this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1), + this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1)}); + const double combine_max_layer_height = this->print()->config().max_layer_height.get_at(combine_extruder_idx); + if (combine_max_layer_height > EPSILON) + nozzle_diameter = std::min(nozzle_diameter, combine_max_layer_height); + } + // define the combinations std::vector combine(m_layers.size(), 0); { @@ -4472,6 +4667,107 @@ void PrintObject::combine_infill() } } } + + // Per-extruder layer height: leftovers the pass above could not combine to the full group height print at the object layer height. + // Where that is below the infill extruder's min layer height, re-group them over the smallest layer count reaching the minimum (isolated leftovers with no matching neighbors stay as-is; Print::validate() warns). + const double combine_min_layer_height = this->print()->config().min_layer_height.get_at(combine_extruder_idx); + const double min_window = combine_min_layer_height > 0. ? + std::ceil(combine_min_layer_height / m_config.layer_height.value - EPSILON) * m_config.layer_height.value : 0.; + // Re-grouped windows must stay within the main pass' caps (a min_layer_height above them is + // a misconfiguration). + if (preferred_infill_height > 0. && combine_min_layer_height > m_config.layer_height.value + EPSILON && + min_window <= nozzle_diameter + EPSILON) { + // Assign leftover windows exactly like the pass above, just with the shorter target. + std::vector recombine(m_layers.size(), 0); + { + double current_height = 0.; + size_t num_layers = 0; + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + m_print->throw_if_canceled(); + const Layer *layer = m_layers[layer_idx]; + if (layer->id() == 0) + // Skip first print layer (which may not be first layer in array because of raft). + continue; + if (current_height + layer->height >= min_window + EPSILON) { + recombine[layer_idx - 1] = num_layers; + current_height = 0.; + num_layers = 0; + } + current_height += layer->height; + ++ num_layers; + } + recombine[m_layers.size() - 1] = num_layers; + } + // Leftovers are surfaces of the combined type whose thickness is still unset. + auto leftover_expolygons = [surface_type](const LayerRegion *layerm) { + ExPolygons out; + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == surface_type && surface.thickness < 0.) + out.emplace_back(surface.expolygon); + return out; + }; + for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { + m_print->throw_if_canceled(); + size_t num_layers = recombine[layer_idx]; + if (num_layers <= 1) + continue; + std::vector layerms; + layerms.reserve(num_layers); + for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i) + layerms.emplace_back(m_layers[i]->regions()[region_id]); + ExPolygons intersection = leftover_expolygons(layerms.front()); + for (size_t i = 1; i < layerms.size() && ! intersection.empty(); ++ i) + intersection = intersection_ex(leftover_expolygons(layerms[i]), intersection); + double area_threshold = layerms.front()->infill_area_threshold(); + if (! intersection.empty() && area_threshold > 0.) + intersection.erase(std::remove_if(intersection.begin(), intersection.end(), + [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), + intersection.end()); + if (intersection.empty()) + continue; + // Same clearance as the pass above: the leftover fills also grow to overlap perimeters. + Polygons intersection_with_clearance; + intersection_with_clearance.reserve(intersection.size()); + float clearance_offset = + 0.5f * layerms.back()->flow(frPerimeter).scaled_width() + + ((infill_pattern == ipRectilinear || + infill_pattern == ipMonotonic || + infill_pattern == ipGrid || + infill_pattern == ipLateralLattice|| + infill_pattern == ipLine || + infill_pattern == ipHoneycomb || + infill_pattern == ipLateralHoneycomb) ? 1.5f : 0.5f) * + layerms.back()->flow(frSolidInfill).scaled_width(); + for (ExPolygon &expoly : intersection) + polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); + for (LayerRegion *layerm : layerms) { + // Take out only the leftover surfaces; surfaces combined above keep their heights. + Polygons leftover; + Surfaces kept; + kept.reserve(layerm->fill_surfaces.surfaces.size()); + for (Surface &surface : layerm->fill_surfaces.surfaces) { + if (surface.surface_type == surface_type && surface.thickness < 0.) + polygons_append(leftover, to_polygons(std::move(surface.expolygon))); + else + kept.emplace_back(std::move(surface)); + } + layerm->fill_surfaces.surfaces = std::move(kept); + layerm->fill_surfaces.append(diff_ex(leftover, intersection_with_clearance), surface_type); + if (layerm == layerms.back()) { + Surface templ(surface_type, ExPolygon()); + templ.thickness = 0.; + for (LayerRegion *layerm2 : layerms) + templ.thickness += layerm2->layer()->height; + templ.thickness_layers = (unsigned short)layerms.size(); + layerm->fill_surfaces.append(intersection, templ); + } else { + layerm->fill_surfaces.append( + intersection_ex(leftover, intersection_with_clearance), + stInternalVoid); + } + } + } + } } } diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 5a225bff655..686916bae39 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -848,6 +848,11 @@ void PrintObject::slice() // BBS: the actual first layer slices stored in layers are re-sorted by volume group and will be used to generate brim groupingVolumesForBrim(this, m_layers, firstLayerReplacedBy); + // Per-extruder layer height: combine region slices into every Nth layer where geometry allows. + // Must run before backup_untyped_slices() below so the combined slices survive the restore_untyped_slices*() calls in make_perimeters() / prepare_infill(). + this->apply_extruder_layer_heights(); + m_print->throw_if_canceled(); + // Update bounding boxes, back up raw slices of complex models. tbb::parallel_for( tbb::blocked_range(0, m_layers.size()), @@ -869,6 +874,191 @@ void PrintObject::slice() this->set_done(posSlice); } +// ORCA: per-extruder layer height ("extruder_layer_height" printer option). +// For every region whose extruder prefers an integer multiple N (> 1) of the object layer height, +// greedily combine bottom-up runs of up to N layers on which the region keeps a (nearly) identical +// shape, merging the run's slices into its top layer to be extruded once at the run's full height +// (see LayerRegion::combined_height()); combined-away layers carry no slices for the region and +// trigger no toolchange. A run requires near-identical slices, uniform layer heights and full support +// from below (no overhang / bridge hidden inside a run). Only full runs plus the clean cap of a +// column ending above are committed, so each region prints at two consistent heights rather than +// ever-changing intermediate bands. Region tops / bottoms and overhangs keep the finer base layers. +void PrintObject::apply_extruder_layer_heights() +{ + if (m_layers.size() < 2 || this->num_printing_regions() == 0) + return; + std::vector multipliers(this->num_printing_regions(), 1); + bool any_combined = false; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + multipliers[region_id] = this->region_layer_height_multiplier(this->printing_region(region_id)); + any_combined |= multipliers[region_id] > 1; + } + if (! any_combined) + return; + if (m_print->config().spiral_mode || m_config.interface_shells) + // These combinations are rejected by Print::validate(), fail safe here. + return; + + BOOST_LOG_TRIVIAL(debug) << "Combining region slices to extruder layer heights for " << this->model_object()->name; + + // Never combine the first printed layer: it keeps its own height for bed adhesion (mirrors the + // id() == 0 exclusion in PrintObject::combine_infill()), and with a raft detect_surfaces_type() + // needs its slices to seed the object's bottom surfaces. + const size_t first_idx = 1; + if (m_layers.size() <= first_idx + 1) + return; + + // Adaptive mode commits runs cut short by shape drift at intermediate heights instead of + // falling back to the object layer height. + const bool adaptive = m_config.extruder_layer_height_mode.value == elhmAdaptive; + // The filament index is the extruder index on classic multi-tool printers (same mapping as + // extruder_preferred_layer_height() and Print::validate()). + const PrintConfig &print_config = m_print->config(); + auto extruder_idx_of = [](int filament_id) { + return size_t((unsigned int)std::max(1, filament_id) - 1); + }; + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const size_t mult = multipliers[region_id]; + if (mult <= 1) + continue; + // Shapes deviating by less than this fraction of the region's nozzle diameter are considered + // identical; the deviations swallowed stay below what printing N layers at once causes anyway. + const double nozzle_diameter = print_config.nozzle_diameter.get_at( + extruder_idx_of(this->printing_region(region_id).config().outer_wall_filament_id.value)); + const float tolerance = float(scale_(m_config.extruder_layer_height_tolerance.get_abs_value(nozzle_diameter))); + // Where geometry would fall back below the extruders' minimum layer height, runs of at least + // min_run layers are forced instead, ignoring the shape tolerance (the nozzle cannot print + // finer). Both wall filaments print fallback runs, so the coarser of their minimums decides. + const PrintRegionConfig ®ion_config = this->printing_region(region_id).config(); + double min_layer_height = print_config.min_layer_height.get_at( + extruder_idx_of(region_config.outer_wall_filament_id.value)); + if (region_config.wall_loops.value > 1) + min_layer_height = std::max(min_layer_height, print_config.min_layer_height.get_at( + extruder_idx_of(region_config.inner_wall_filament_id.value))); + size_t min_run = 1; + if (min_layer_height > m_config.layer_height.value + EPSILON) + min_run = std::min(mult, (size_t)std::ceil(min_layer_height / m_config.layer_height.value - EPSILON)); + size_t idx = first_idx; + while (idx < m_layers.size()) { + m_print->throw_if_canceled(); + ExPolygons merged = to_expolygons(m_layers[idx]->regions()[region_id]->slices.surfaces); + if (merged.empty()) { + // The region does not exist at this layer. + ++ idx; + continue; + } + // Grow the run upwards while nothing sticks out of the run's common shape by more than + // the tolerance. Uniform layer heights only (variable heights are rejected by Print::validate()). + Polygons unioned = to_polygons(merged); + size_t top_idx = idx; + bool shape_drifted = false; + while (top_idx + 1 < m_layers.size() && top_idx + 1 - idx < mult) { + const size_t next = top_idx + 1; + if (std::abs(m_layers[next]->height - m_layers[idx]->height) > EPSILON) + break; + const ExPolygons expolys = to_expolygons(m_layers[next]->regions()[region_id]->slices.surfaces); + if (expolys.empty()) + // The region ends above, the run is the clean cap of its column. + break; + ExPolygons next_merged = intersection_ex(expolys, merged); + if (next_merged.empty()) + // Laterally displaced (a painted-boundary step): this column ends, the next anchors above. + break; + Polygons next_unioned = unioned; + polygons_append(next_unioned, to_polygons(expolys)); + if (! opening(diff(union_(next_unioned), offset(next_merged, tolerance)), 0.5f * tolerance).empty()) { + shape_drifted = true; + break; + } + merged = std::move(next_merged); + unioned = std::move(next_unioned); + top_idx = next; + } + // Full runs and clean caps commit as grown; a run cut short by shape drift falls back to + // the object layer height (unless adaptive) to avoid bands of ever-changing heights on + // curved boundaries. The fallback is raised to min_run where required. + const size_t grown = top_idx + 1 - idx; + size_t commit_length; + if (grown == mult || ! shape_drifted) + commit_length = grown; + else if (adaptive) + commit_length = std::max(grown, min_run); + else + commit_length = min_run; + if (commit_length >= 2 && min_run > 1) { + // Don't leave a remainder shorter than min_run above: shorten so the next run can reach it. + size_t above = 0; + for (size_t i = idx + commit_length; i < m_layers.size() && above < min_run; ++ i) { + if (m_layers[i]->regions()[region_id]->slices.empty()) + break; + ++ above; + } + if (above > 0 && above < min_run) { + const size_t shift = min_run - above; + if (commit_length >= min_run + shift && commit_length - shift >= 2) + commit_length -= shift; + } + } + if (commit_length < 2) { + // Print this layer with the object layer height. + ++ idx; + continue; + } + size_t commit_top = std::min(idx + commit_length - 1, m_layers.size() - 1); + if (commit_top != top_idx) { + // Forced or shortened run: recompute its shape without the tolerance check, + // shrinking the range where the region ends or jumps. + merged = to_expolygons(m_layers[idx]->regions()[region_id]->slices.surfaces); + for (size_t i = idx + 1; i <= commit_top; ++ i) { + if (std::abs(m_layers[i]->height - m_layers[idx]->height) > EPSILON) { + commit_top = i - 1; + break; + } + const ExPolygons expolys = to_expolygons(m_layers[i]->regions()[region_id]->slices.surfaces); + ExPolygons next_merged = expolys.empty() ? ExPolygons() : intersection_ex(expolys, merged); + if (next_merged.empty()) { + commit_top = i - 1; + break; + } + merged = std::move(next_merged); + } + if (commit_top + 1 - idx < 2) { + // Nothing to force here, the object layer height is the last resort. + ++ idx; + continue; + } + } + // The run must rest on the object below, else the finer per-layer bridge / overhang path + // is needed. Skipped when min_run > 1: no finer path exists then, overhangs are detected + // against the layer below the whole run instead. + if (min_run <= 1) + if (const Layer *below = m_layers[idx]->lower_layer; below != nullptr && + ! opening_ex(diff_ex(merged, below->lslices, ApplySafetyOffset::Yes), 0.5f * tolerance).empty()) { + ++ idx; + continue; + } + // Commit: move the common shape to the top layer of the run, drop the layers below. + double combined_height = 0.; + for (size_t i = idx; i <= commit_top; ++ i) + combined_height += m_layers[i]->height; + LayerRegion *top_layerm = m_layers[commit_top]->regions()[region_id]; + top_layerm->slices.set(std::move(merged), stInternal); + top_layerm->m_combined_layer_count = (unsigned short)(commit_top - idx + 1); + top_layerm->m_combined_height = combined_height; + for (size_t i = idx; i < commit_top; ++ i) { + LayerRegion *combined_away = m_layers[i]->regions()[region_id]; + combined_away->slices.clear(); + // 0 marks "extrudes at the run top above", as opposed to genuinely absent geometry. + combined_away->m_combined_layer_count = 0; + } + // Do not touch Layer::lslices here: they describe the final object and keep driving + // top / bottom detection of the other regions, brim, supports and overhang handling. + idx = commit_top + 1; + } + m_print->throw_if_canceled(); + } +} + static bool bool_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, bool fallback) { if (!full_cfg.has(key)) diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index 1d4e6a3017c..a6d842ed74c 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -33,7 +33,7 @@ unsigned int PrintRegion::extruder(FlowRole role) const return extruder; } -Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer) const +Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_height, bool first_layer, unsigned int filament_id) const { const PrintConfig &print_config = object.print()->config(); ConfigOptionFloatOrPercent config_width; @@ -58,9 +58,10 @@ Flow PrintRegion::flow(const PrintObject &object, FlowRole role, double layer_he if (config_width.value == 0) config_width = object.config().line_width; - // Get the configured nozzle_diameter for the extruder associated to the flow role requested. + // Get the configured nozzle_diameter for the extruder associated to the flow role requested, + // or for the explicitly given filament when it differs from the role's default mapping (top / bottom surface fills). // Here this->extruder(role) - 1 may underflow to MAX_INT, but then the get_at() will follback to zero'th element, so everything is all right. - auto nozzle_diameter = float(print_config.nozzle_diameter.get_at(this->extruder(role) - 1)); + auto nozzle_diameter = float(print_config.nozzle_diameter.get_at((filament_id > 0 ? filament_id : this->extruder(role)) - 1)); return Flow::new_from_config_width(role, config_width, nozzle_diameter, float(layer_height)); } diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index f3499b7d361..f8bc5600146 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -73,8 +73,16 @@ SlicingParameters SlicingParameters::create_from_config( // which is consistent with the requirement that if support_filament == 0 resp. support_interface_filament == 0, // support will not trigger tool change, but it will use the current nozzle instead. // In that case all the nozzles have to be of the same diameter. - coordf_t support_material_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_filament.value - 1); - coordf_t support_material_interface_extruder_dmr = print_config.nozzle_diameter.get_at(object_config.support_interface_filament.value - 1); + // Support_nozzle_diameter restricts which extruders / nozzle diameter may print support ("default" = no restriction) + auto restricted_default = [&object_config](int configured) { + return configured == 0 && object_config.support_nozzle_diameter.value > 0.; + }; + coordf_t support_material_extruder_dmr = restricted_default(object_config.support_filament.value) ? + object_config.support_nozzle_diameter.value : + print_config.nozzle_diameter.get_at(object_config.support_filament.value - 1); + coordf_t support_material_interface_extruder_dmr = restricted_default(object_config.support_interface_filament.value) ? + object_config.support_nozzle_diameter.value : + print_config.nozzle_diameter.get_at(object_config.support_interface_filament.value - 1); bool soluble_interface = object_config.support_top_z_distance.value == 0.; SlicingParameters params; @@ -94,12 +102,31 @@ SlicingParameters SlicingParameters::create_from_config( params.max_layer_height = std::numeric_limits::max(); if (object_config.enable_support.value || params.base_raft_layers > 0 || object_config.enforce_support_layers > 0) { // Has some form of support. Add the support layers to the minimum / maximum layer height limits. + auto support_layer_height_limit = [&print_config, &object_config, &restricted_default](int configured, bool min_limit) -> coordf_t { + auto from_nozzle = [&print_config, min_limit](int filament) { + return min_limit ? min_layer_height_from_nozzle(print_config, filament) : max_layer_height_from_nozzle(print_config, filament); + }; + if (restricted_default(configured)) { + // Combine the limits of all filaments the restriction allows. + coordf_t limit = min_limit ? 0. : std::numeric_limits::max(); + bool found = false; + for (size_t i = 0; i < print_config.nozzle_diameter.values.size(); ++ i) + if (std::abs(print_config.nozzle_diameter.values[i] - object_config.support_nozzle_diameter.value) < EPSILON) { + coordf_t l = from_nozzle(int(i + 1)); + limit = min_limit ? std::max(limit, l) : std::min(limit, l); + found = true; + } + if (found) + return limit; + } + return from_nozzle(configured); + }; params.min_layer_height = std::max( - min_layer_height_from_nozzle(print_config, object_config.support_filament), - min_layer_height_from_nozzle(print_config, object_config.support_interface_filament)); + support_layer_height_limit(object_config.support_filament.value, true), + support_layer_height_limit(object_config.support_interface_filament.value, true)); params.max_layer_height = std::min( - max_layer_height_from_nozzle(print_config, object_config.support_filament), - max_layer_height_from_nozzle(print_config, object_config.support_interface_filament)); + support_layer_height_limit(object_config.support_filament.value, false), + support_layer_height_limit(object_config.support_interface_filament.value, false)); params.max_suport_layer_height = params.max_layer_height; } if (object_extruders.empty()) { diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 1d04e954afa..e83981f9ac0 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -2149,7 +2149,7 @@ SupportGeneratorLayersPtr PrintObjectSupportMaterial::top_contact_layers( // check if the sharp tails should be extended higher bool detect_first_sharp_tail_only = false; - const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(object.print()->config().nozzle_diameter.get_at(object.config().support_interface_filament-1)); + const coordf_t extrusion_width = m_object_config->line_width.get_abs_value(support_material_nozzle_diameter(&object, object.config().support_interface_filament)); const coordf_t extrusion_width_scaled = scale_(extrusion_width); if (is_auto(m_object_config->support_type.value) && g_config_support_sharp_tails && !detect_first_sharp_tail_only) { for (size_t layer_nr = layer_id_start; layer_nr < num_layers; layer_nr++) { diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 655ee0d8060..8d34dee9413 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -158,7 +158,8 @@ struct SupportParameters { assert(slicing_params.raft_layers() == 0); } - const auto nozzle_diameter = print_config.nozzle_diameter.get_at(object_config.support_interface_filament - 1); + // ORCA: honors the support nozzle diameter restriction for "default" support filaments. + const auto nozzle_diameter = support_material_nozzle_diameter(&object, object_config.support_interface_filament); const coordf_t extrusion_width = object_config.line_width.get_abs_value(nozzle_diameter); support_extrusion_width = object_config.support_line_width.get_abs_value(nozzle_diameter); support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 17e7fb044f0..352e46b44b3 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -1319,7 +1319,7 @@ void TreeSupport::generate_toolpaths() { const PrintObjectConfig &object_config = m_object->config(); coordf_t support_extrusion_width = m_support_params.support_extrusion_width; - coordf_t nozzle_diameter = m_print_config->nozzle_diameter.get_at(object_config.support_filament - 1); + coordf_t nozzle_diameter = support_material_nozzle_diameter(m_object, object_config.support_filament); coordf_t layer_height = object_config.layer_height.value; const size_t wall_count = object_config.tree_support_wall_count.value; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index b0fc711f343..d9d106bea03 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -731,6 +731,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("inner_wall_line_width", have_perimeters || have_skirt || have_brim); toggle_field("support_filament", have_support_material || have_skirt); + // ORCA: support_nozzle_diameter only applies to printers whose extruders have differing nozzle diameters. + bool mixed_nozzle_sizes = false; + if (const auto *nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); + nozzle_diameters != nullptr && ! nozzle_diameters->values.empty()) + for (double d : nozzle_diameters->values) + mixed_nozzle_sizes |= std::abs(d - nozzle_diameters->values.front()) > EPSILON; + toggle_line("support_nozzle_diameter", have_support_material && mixed_nozzle_sizes); + toggle_line("raft_contact_distance", have_raft && !have_support_soluble); // Orca: Raft, grid, snug and organic supports use these two parameters to control the size & density of the "brim"/flange diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 6e5f86b52fe..ae87b7bbbd6 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2257,6 +2257,8 @@ void TabPrint::build() auto optgroup = page->new_optgroup(L("Layer height"), L"param_layer_height"); optgroup->append_single_option_line("layer_height","quality_settings_layer_height"); optgroup->append_single_option_line("initial_layer_print_height","quality_settings_layer_height"); + optgroup->append_single_option_line("extruder_layer_height_mode","quality_settings_layer_height"); + optgroup->append_single_option_line("extruder_layer_height_tolerance","quality_settings_layer_height"); optgroup = page->new_optgroup(L("Line width"), L"param_line_width"); optgroup->append_single_option_line("line_width","quality_settings_line_width"); @@ -2499,6 +2501,7 @@ void TabPrint::build() optgroup->append_single_option_line("raft_contact_distance", "support_settings_raft"); optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); + optgroup->append_single_option_line("support_nozzle_diameter", "support_settings_filament"); optgroup->append_single_option_line("support_filament", "support_settings_filament#base"); optgroup->append_single_option_line("support_interface_filament", "support_settings_filament#interface"); optgroup->append_single_option_line("support_interface_not_for_body", "support_settings_filament#avoid-interface-filament-for-base"); @@ -4862,6 +4865,7 @@ if (is_marlin_flavor) optgroup = page->new_optgroup(L("Layer height limits"), L"param_layer_height"); optgroup->append_single_option_line("min_layer_height", "", extruder_idx); optgroup->append_single_option_line("max_layer_height", "", extruder_idx); + optgroup->append_single_option_line("extruder_layer_height", "", extruder_idx); optgroup = page->new_optgroup(L("Position"), L"param_position"); optgroup->append_single_option_line("extruder_offset", "", extruder_idx); diff --git a/tests/fff_print/CMakeLists.txt b/tests/fff_print/CMakeLists.txt index 5e282b08a56..f4083b14835 100644 --- a/tests/fff_print/CMakeLists.txt +++ b/tests/fff_print/CMakeLists.txt @@ -9,6 +9,7 @@ add_executable(${_TEST_NAME}_tests test_gcode.cpp test_gcodewriter.cpp test_model.cpp + test_multi_nozzle_layer_height.cpp test_print.cpp test_printgcode.cpp test_printobject.cpp diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp new file mode 100644 index 00000000000..6b711a65fc0 --- /dev/null +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -0,0 +1,684 @@ +#include + +#include + +#include "libslic3r/libslic3r.h" +#include "libslic3r/Print.hpp" +#include "libslic3r/Layer.hpp" +#include "libslic3r/ExtrusionEntity.hpp" +#include "libslic3r/ExtrusionEntityCollection.hpp" +#include "libslic3r/Flow.hpp" +#include "libslic3r/Slicing.hpp" +#include "libslic3r/GCode/ToolOrdering.hpp" +#include + +#include "test_data.hpp" + +using namespace Slic3r; +using namespace Slic3r::Test; + +// ORCA: tests for the per-extruder layer height feature ("extruder_layer_height"). + +template +static void for_each_path(const ExtrusionEntityCollection &collection, const PathFn &fn) +{ + for (const ExtrusionEntity *entity : collection.entities) { + if (auto *sub_collection = dynamic_cast(entity)) + for_each_path(*sub_collection, fn); + else if (auto *loop = dynamic_cast(entity)) { + for (const ExtrusionPath &path : loop->paths) + fn(path); + } else if (auto *multi_path = dynamic_cast(entity)) { + for (const ExtrusionPath &path : multi_path->paths) + fn(path); + } else if (auto *path = dynamic_cast(entity)) + fn(*path); + } +} + +static void collect_path_heights(const ExtrusionEntityCollection &collection, std::vector &heights) +{ + for_each_path(collection, [&heights](const ExtrusionPath &path) { heights.emplace_back(path.height); }); +} + +static std::vector region_path_heights(const LayerRegion *layerm) +{ + std::vector heights; + collect_path_heights(layerm->perimeters, heights); + collect_path_heights(layerm->fills, heights); + return heights; +} + +static void collect_path_role_widths(const ExtrusionEntityCollection &collection, std::vector> &widths) +{ + for_each_path(collection, [&widths](const ExtrusionPath &path) { widths.emplace_back(path.role(), path.width); }); +} + +// Two extruders: a 0.4 mm nozzle printing with the 0.2 mm object layer height and a 0.6 mm nozzle +// with a configurable extruder layer height (0.4 => multiplier 2). +// On this fork's classic multi-tool printers a filament index is the extruder index, so no +// filament map configuration applies. +static DynamicPrintConfig two_extruder_config(double second_extruder_layer_height) +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_key_value("layer_height", new ConfigOptionFloat(0.2)); + config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(0.4)); + config.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); + config.set_key_value("enable_support", new ConfigOptionBool(false)); + + config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4, 0.6})); + config.set_key_value("extruder_layer_height", new ConfigOptionFloats({0., second_extruder_layer_height})); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.07})); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); + config.set_key_value("filament_diameter", new ConfigOptionFloats({1.75, 1.75})); + config.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00"})); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PLA"})); + config.set_key_value("default_filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00"})); + config.set_key_value("nozzle_temperature", new ConfigOptionInts({210, 210})); + config.set_key_value("nozzle_temperature_range_low", new ConfigOptionInts({190, 190})); + config.set_key_value("nozzle_temperature_range_high", new ConfigOptionInts({240, 240})); + // flush_volumes_matrix must be filament_count^2 entries. + config.set_key_value("flush_multiplier", new ConfigOptionFloat(1.)); + config.set_key_value("flush_volumes_matrix", new ConfigOptionFloats({0, 0, 0, 0})); + // Print::validate() reports motion-ability diagnostics by overwriting the single warning + // out-param; raise the machine limit so the default print accelerations do not clobber the + // layer height warnings under test. + config.set_key_value("machine_max_acceleration_extruding", new ConfigOptionFloats({100000., 100000.})); + return config; +} + +// One object made of two 20x20 mm cube parts side by side; the second part prints with filament 2. +// The parts are 20 * z_scale mm tall (10 mm by default). +static void init_two_part_print(Print &print, Model &model, const DynamicPrintConfig &config, float z_scale = 0.5f) +{ + TriangleMesh fine_mesh = mesh(TestMesh::cube_20x20x20); + fine_mesh.scale(Vec3f(1.f, 1.f, z_scale)); + TriangleMesh coarse_mesh = fine_mesh; + coarse_mesh.translate(30.f, 0.f, 0.f); + + ModelObject *object = model.add_object(); + object->name = "two_part_cube"; + object->add_volume(std::move(fine_mesh)); + ModelVolume *coarse_volume = object->add_volume(std::move(coarse_mesh)); + coarse_volume->config.set("extruder", 2); + object->add_instance(); + + arrange_objects(model, InfiniteBed{}, ArrangeParams{scaled(min_object_distance(config))}); + for (ModelObject *mo : model.objects) + mo->ensure_on_bed(); + print.apply(model, config); + print.set_status_silent(); +} + +static void find_regions(const PrintObject &object, int &fine_region, int &coarse_region) +{ + fine_region = coarse_region = -1; + for (size_t i = 0; i < object.num_printing_regions(); ++ i) { + if (object.printing_region(i).config().outer_wall_filament_id.value == 2) + coarse_region = int(i); + else + fine_region = int(i); + } +} + +SCENARIO("Per-extruder layer height combines region layers", "[MultiNozzleLayerHeight]") { + GIVEN("A two-part object, the second part on a 0.6 mm nozzle with a 0.4 mm extruder layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the configuration passes validation and slices as expected") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + // 0.4 mm first layer + 48 layers of 0.2 mm = 10 mm object height. + REQUIRE(object.layer_count() == 49); + + // The coarse region extrudes on the first layer and then only on every 2nd layer, + // always with 0.4 mm high paths; the layers in between print nothing for it. + size_t coarse_layers = 0, coarse_bad_heights = 0, coarse_unexpected = 0, coarse_missing = 0; + // The fine region extrudes on every layer with the base layer heights. + size_t fine_bad_heights = 0, fine_missing = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const Layer *layer = object.get_layer(int(idx)); + const std::vector coarse_heights = region_path_heights(layer->get_region(coarse_region)); + if (idx % 2 == 0) { + if (coarse_heights.empty()) + ++ coarse_missing; + else + ++ coarse_layers; + for (float height : coarse_heights) + if (std::abs(height - 0.4) > 1e-3) + ++ coarse_bad_heights; + } else if (! coarse_heights.empty()) + ++ coarse_unexpected; + + const std::vector fine_heights = region_path_heights(layer->get_region(fine_region)); + if (fine_heights.empty()) + ++ fine_missing; + const double fine_expected = idx == 0 ? 0.4 : 0.2; + for (float height : fine_heights) + if (std::abs(height - fine_expected) > 1e-3) + ++ fine_bad_heights; + } + CHECK(coarse_missing == 0); + CHECK(coarse_unexpected == 0); + CHECK(coarse_bad_heights == 0); + CHECK(coarse_layers == 25); // layer 0 + the 24 group tops + CHECK(fine_missing == 0); + CHECK(fine_bad_heights == 0); + } + } + + GIVEN("The same object with extruder_layer_height disabled") { + DynamicPrintConfig config = two_extruder_config(0.); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("both regions print on every layer with the base layer heights") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + + size_t missing = 0, bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const Layer *layer = object.get_layer(int(idx)); + const double expected = idx == 0 ? 0.4 : 0.2; + for (int region_id : { fine_region, coarse_region }) { + const std::vector heights = region_path_heights(layer->get_region(region_id)); + if (heights.empty()) + ++ missing; + for (float height : heights) + if (std::abs(height - expected) > 1e-3) + ++ bad_heights; + } + } + CHECK(missing == 0); + CHECK(bad_heights == 0); + } + } +} + +SCENARIO("Per-extruder layer height respects the extruder's minimum layer height", "[MultiNozzleLayerHeight]") { + GIVEN("A 0.6 mm preferred layer height with a 0.4 mm minimum, on a part height leaving a 1-layer tail") { + DynamicPrintConfig config = two_extruder_config(0.6); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.6})); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.4})); + Print print; + Model model; + // 10.2 mm parts: a 0.4 mm first layer + 49 layers of 0.2 mm. 49 is not divisible by the + // multiplier 3, so without the minimum the column would end in a single 0.2 mm layer. + init_two_part_print(print, model, config, 0.51f); + THEN("no layer of the coarse part above the first prints below 0.4 mm") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(coarse_region >= 0); + + size_t below_min = 0, full_runs = 0, forced_runs = 0; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) { + const std::vector heights = region_path_heights(object.get_layer(int(idx))->get_region(coarse_region)); + for (float height : heights) { + if (height < 0.4 - 1e-3) + ++ below_min; + else if (std::abs(height - 0.6) < 1e-3) + ++ full_runs; + else if (std::abs(height - 0.4) < 1e-3) + ++ forced_runs; + } + } + CHECK(below_min == 0); + CHECK(full_runs > 0); + CHECK(forced_runs > 0); + } + } + GIVEN("A preferred layer height below the extruder's minimum layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.45})); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation fails") { + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "extruder_layer_height"); + } + } +} + +// Shared check: the fine part's walls print on every layer with the base layer heights while +// combined 0.4 mm high infill appears on some layers above the first. +static void check_plain_walls_combined_infill(Print &print) +{ + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + + size_t wall_bad_heights = 0, wall_missing = 0, combined_infill_paths = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const LayerRegion *layerm = object.get_layer(int(idx))->get_region(fine_region); + std::vector wall_heights; + collect_path_heights(layerm->perimeters, wall_heights); + if (wall_heights.empty()) + ++ wall_missing; + const double expected = idx == 0 ? 0.4 : 0.2; + for (float height : wall_heights) + if (std::abs(height - expected) > 1e-3) + ++ wall_bad_heights; + if (idx > 0) { + std::vector fill_heights; + collect_path_heights(layerm->fills, fill_heights); + for (float height : fill_heights) + if (std::abs(height - 0.4) < 1e-3) + ++ combined_infill_paths; + } + } + CHECK(wall_missing == 0); + CHECK(wall_bad_heights == 0); + CHECK(combined_infill_paths > 0); +} + +SCENARIO("Per-extruder layer height honors feature filaments", "[MultiNozzleLayerHeight]") { + GIVEN("A part whose sparse infill uses the filament with a 0.4 mm preferred layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + // Both parts print their sparse infill with filament 2; the first part's walls stay on + // filament 1 with no preferred layer height. + config.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the first part prints per-layer walls with sparse infill combined to 0.4 mm") { + check_plain_walls_combined_infill(print); + } + } + + GIVEN("A part whose internal solid infill uses the 0.4 mm filament at 100% infill density") { + DynamicPrintConfig config = two_extruder_config(0.4); + // At 100% density the combined infill is internal solid infill: the preference of ITS + // filament must decide the combined height, not the sparse infill filament's. + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(100)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the first part prints per-layer walls with solid infill combined to 0.4 mm") { + check_plain_walls_combined_infill(print); + } + } + + GIVEN("A part whose outer walls use a filament with a different preferred layer height") { + DynamicPrintConfig config = two_extruder_config(0.4); + // The first part's outer walls print with filament 2 while its inner walls stay on + // filament 1: walls print together, so the part keeps the object layer height and the + // unhonored preference is warned about. + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation passes with a warning instead of an error") { + // This fork's validate() appends warnings to a single StringObjectException out-param. + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(! warning.string.empty()); + REQUIRE(warning.opt_key == "extruder_layer_height"); + } + } +} + +SCENARIO("Fill line width follows the filament that prints the surface", "[MultiNozzleLayerHeight]") { + GIVEN("Internal solid infill mapped to the 0.6 mm filament, bottom surfaces staying on the 0.4 mm filament") { + DynamicPrintConfig config = two_extruder_config(0.); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("initial_layer_line_width", new ConfigOptionFloatOrPercent(125., true)); + config.set_key_value("internal_solid_infill_line_width", new ConfigOptionFloatOrPercent(105., true)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("bottom surface widths resolve against their own filament's nozzle") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + + // Bottom surfaces print with filament 1, so their percent line width resolves against + // its 0.4 mm nozzle, while internal solid infill (filament 2) resolves against 0.6 mm. + // Solid fills may stretch line spacing up to 20% to fit a region evenly, so accept + // widths in [nominal, 1.2 * nominal]. + size_t bottom_paths = 0, bottom_bad_widths = 0, solid_paths = 0, solid_bad_widths = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector> widths; + collect_path_role_widths(object.get_layer(int(idx))->get_region(fine_region)->fills, widths); + for (const std::pair &role_width : widths) { + const bool bottom = role_width.first == erBottomSurface; + if (! bottom && role_width.first != erSolidInfill) + continue; + ++ (bottom ? bottom_paths : solid_paths); + const double expected = (idx == 0 ? 1.25 : 1.05) * (bottom ? 0.4 : 0.6); + if (role_width.second < expected - 1e-3 || role_width.second > expected * 1.2 + 1e-3) + ++ (bottom ? bottom_bad_widths : solid_bad_widths); + } + } + CHECK(bottom_paths > 0); + CHECK(bottom_bad_widths == 0); + CHECK(solid_paths > 0); + CHECK(solid_bad_widths == 0); + } + } +} + +SCENARIO("Combined infill respects the printing extruder's layer height limits", "[MultiNozzleLayerHeight]") { + GIVEN("Infill combining to a preferred height above the filament's maximum layer height") { + // Infill on filament 2: preferred layer height 0.6 exceeds its max_layer_height 0.45, + // so combining must stop at 0.4 mm groups instead of building 0.6 mm ones (3 x 0.2). + DynamicPrintConfig config = two_extruder_config(0.6); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); + config.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(2)); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); + Print print; + Model model; + // A single part: filament 2 prints only infill, so its preference skips the strict wall checks. + TriangleMesh cube = mesh(TestMesh::cube_20x20x20); + cube.scale(Vec3f(1.f, 1.f, 0.5f)); + ModelObject *object_model = model.add_object(); + object_model->name = "single_cube"; + object_model->add_volume(std::move(cube)); + object_model->add_instance(); + arrange_objects(model, InfiniteBed{}, ArrangeParams{scaled(min_object_distance(config))}); + for (ModelObject *mo : model.objects) + mo->ensure_on_bed(); + print.apply(model, config); + print.set_status_silent(); + THEN("no combined infill group exceeds the maximum layer height") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + size_t over_max = 0, combined = 0; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) + for (const LayerRegion *layerm : object.get_layer(int(idx))->regions()) { + std::vector heights; + collect_path_heights(layerm->fills, heights); + for (float height : heights) { + if (height > 0.45 + 1e-3) + ++ over_max; + else if (height > 0.2 + 1e-3) + ++ combined; + } + } + CHECK(over_max == 0); + CHECK(combined > 0); + } + } + + GIVEN("Walls combining to a pitch a feature filament of the part cannot print") { + // Both wall filaments map to filament 2 at a 0.4 mm pitch, but top/bottom/solid features + // stay on filament 1 whose max_layer_height (0.3) cannot print that pitch: the part + // falls back to the object layer height with a warning, like disagreeing wall filaments. + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(2)); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); + // Keep the line width checks out of the way, this test targets the height fallback. + config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation warns and the part prints with the object layer height") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("cannot print that height") != std::string::npos); + + print.process(); + // Only the first part falls back; the second is all filament 2 and keeps the pitch. + // Both parts' walls print with filament 2, so the parts are told apart by their + // top surface filament. + const PrintObject &object = *print.objects().front(); + int fine_region = -1; + for (size_t i = 0; i < object.num_printing_regions(); ++ i) + if (object.printing_region(i).config().top_surface_filament_id.value == 1) + fine_region = int(i); + REQUIRE(fine_region >= 0); + size_t wall_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const double expected = idx == 0 ? 0.4 : 0.2; + std::vector heights; + collect_path_heights(object.get_layer(int(idx))->get_region(fine_region)->perimeters, heights); + for (float height : heights) + if (std::abs(height - expected) > 1e-3) + ++ wall_bad_heights; + } + CHECK(wall_bad_heights == 0); + } + } + + GIVEN("A feature filament whose minimum layer height is above the pitch it prints at") { + // Internal solid infill on filament 2 (minimum layer height 0.3) prints with the 0.2 mm + // object layer height while filament 2's preference drives infill combining elsewhere. + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.3})); + // Keep the line width checks out of the way, this test targets the height warning. + config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation warns about printing below the minimum layer height") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("minimum layer") != std::string::npos); + } + } +} + +SCENARIO("Fill collections dispatch to the filament their flow was computed for", "[MultiNozzleLayerHeight]") { + GIVEN("A region with distinct per-feature filaments") { + PrintRegionConfig region_config; + region_config.outer_wall_filament_id.value = 1; + region_config.inner_wall_filament_id.value = 1; + region_config.sparse_infill_filament_id.value = 4; + region_config.internal_solid_filament_id.value = 3; + region_config.top_surface_filament_id.value = 1; + region_config.bottom_surface_filament_id.value = 1; + const PrintRegion region(region_config, region_config.hash(), 0); + LayerTools layer_tools(0.); + + auto collection_extruder = [&](std::initializer_list roles) { + ExtrusionEntityCollection eec; + for (ExtrusionRole role : roles) + eec.entities.push_back(new ExtrusionPath(role)); + return layer_tools.extruder(eec, region); + }; + + THEN("top and bottom surfaces keep their filament when gap fill is mixed in") { + CHECK(collection_extruder({erTopSolidInfill}) == 0); // 0 based filament 1 + CHECK(collection_extruder({erTopSolidInfill, erGapFill}) == 0); + CHECK(collection_extruder({erBottomSurface, erGapFill}) == 0); + CHECK(collection_extruder({erSolidInfill, erGapFill}) == 2); // filament 3 + } + THEN("external bridges print with the bottom surface filament, internal ones stay solid") { + CHECK(collection_extruder({erBridgeInfill}) == 0); // bottom filament 1 + CHECK(collection_extruder({erInternalBridgeInfill}) == 2); // internal solid filament 3 + } + THEN("gap fill with no sibling surface prints with the outer wall filament") { + PrintRegionConfig gap_region_config = region_config; + gap_region_config.outer_wall_filament_id.value = 2; + const PrintRegion gap_region(gap_region_config, gap_region_config.hash(), 0); + ExtrusionEntityCollection eec; + eec.entities.push_back(new ExtrusionPath(erGapFill)); + CHECK(layer_tools.extruder(eec, gap_region) == 1); // 0 based outer wall filament 2 + } + } +} + +SCENARIO("Support nozzle diameter restricts support printing", "[MultiNozzleLayerHeight]") { + // A raft makes the object require support handling without any overhang geometry: the raft + // layers below the object print as support-only layers. + auto raft_config = [](double support_nozzle_diameter) { + DynamicPrintConfig config = two_extruder_config(0.); + config.set_key_value("raft_layers", new ConfigOptionInt(2)); + config.option>("support_type", true)->value = stNormalAuto; + config.set_key_value("support_nozzle_diameter", new ConfigOptionFloat(support_nozzle_diameter)); + config.set_key_value("support_line_width", new ConfigOptionFloatOrPercent(105., true)); + return config; + }; + + GIVEN("A raft restricted to the 0.6 mm nozzle while the default filament prints with 0.4 mm") { + DynamicPrintConfig config = raft_config(0.6); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("support flows, layer height limits and tool ordering follow the 0.6 mm filament") { + REQUIRE(print.validate().string.empty()); + + const PrintObject &object = *print.objects().front(); + // Support / raft flows must resolve width against the restricted nozzle, not against + // extruder 1 that the "default" support filament falls back to. + REQUIRE(double(support_material_flow(&object).width()) == Approx(1.05 * 0.6).margin(1e-4)); + REQUIRE(double(support_material_interface_flow(&object).width()) == Approx(1.05 * 0.6).margin(1e-4)); + + // Support layer height limits follow the restricted nozzle (filament 2: max 0.45). + PrintConfig print_config; + print_config.apply(config, true); + PrintObjectConfig object_config; + object_config.apply(config, true); + const SlicingParameters params = SlicingParameters::create_from_config( + print_config, object_config, 10., std::vector{0, 1}, Vec3d(1., 1., 1.)); + REQUIRE(params.max_suport_layer_height == Approx(0.45).margin(1e-6)); + + // The raft layers below the object print with filament 2 only. + print.process(); + ToolOrdering tool_ordering(print, (unsigned int)-1, false); + size_t support_only_layers = 0, wrong_extruders = 0; + for (const LayerTools &layer_tools : tool_ordering.layer_tools()) + if (layer_tools.has_support && ! layer_tools.has_object) { + ++ support_only_layers; + for (unsigned int extruder_id : layer_tools.extruders) + if (extruder_id != 1) // 0 based: filament 2 prints with the 0.6 mm nozzle + ++ wrong_extruders; + } + CHECK(support_only_layers > 0); + CHECK(wrong_extruders == 0); + } + } + + GIVEN("A support nozzle diameter no extruder has") { + DynamicPrintConfig config = raft_config(0.5); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation fails") { + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "support_nozzle_diameter"); + } + } + + GIVEN("A support filament printing with a different nozzle than the support nozzle diameter") { + DynamicPrintConfig config = raft_config(0.6); + config.set_key_value("support_filament", new ConfigOptionInt(1)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation fails") { + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "support_filament"); + } + } +} + +SCENARIO("A raft keeps the bottom surfaces of combined regions", "[MultiNozzleLayerHeight]") { + GIVEN("A combined region printing on a raft") { + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("raft_layers", new ConfigOptionInt(2)); + config.option>("support_type", true)->value = stNormalAuto; + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the first layer above the raft prints uncombined and carries the bottom surfaces") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + + // The first layer above the raft must not be swallowed by a combined group: surface + // detection can only seed the object's bottom shells there. + const Layer *first_layer = object.get_layer(0); + size_t bottom_regions = 0; + for (int region_id : { fine_region, coarse_region }) { + const LayerRegion *layerm = first_layer->get_region(region_id); + const std::vector heights = region_path_heights(layerm); + CHECK(! heights.empty()); + for (float height : heights) + CHECK(double(height) == Approx(0.2).margin(1e-3)); + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.is_bottom()) { + ++ bottom_regions; + break; + } + } + CHECK(bottom_regions == 2); + + // Combining still happens above the first object layer. + size_t combined_layers = 0; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) + for (float height : region_path_heights(object.get_layer(int(idx))->get_region(coarse_region))) + if (std::abs(height - 0.4) < 1e-3) { + ++ combined_layers; + break; + } + CHECK(combined_layers > 0); + } + } +} + +SCENARIO("Per-extruder layer height validation rejects invalid configurations", "[MultiNozzleLayerHeight]") { + auto expect_error = [](double second_extruder_layer_height) { + DynamicPrintConfig config = two_extruder_config(second_extruder_layer_height); + Print print; + Model model; + init_two_part_print(print, model, config); + const StringObjectException err = print.validate(); + REQUIRE(! err.string.empty()); + REQUIRE(err.opt_key == "extruder_layer_height"); + }; + GIVEN("An extruder layer height that is no integer multiple of the object layer height") { + THEN("validation fails") { expect_error(0.5); } + } + GIVEN("An extruder layer height smaller than the object layer height") { + THEN("validation fails") { expect_error(0.1); } + } + GIVEN("An extruder layer height exceeding the nozzle diameter") { + THEN("validation fails") { expect_error(0.8); } + } + GIVEN("An extruder layer height exceeding the extruder's maximum layer height") { + // 0.6 is a multiple of 0.2 and fits the 0.6 mm nozzle, but exceeds max_layer_height 0.45. + THEN("validation fails") { expect_error(0.6); } + } +} From 6d21ad7bb8ea4eee57c6d57fb03911077a739910 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 12:51:47 +0000 Subject: [PATCH 07/43] fix: align fork-specific dispatchers with the ported per-feature filament logic Follow-up fixes from an adversarial review of the two ports: - GCode::process_layer's fork-local dispatch lambdas (configured_filament_id_1based / configured_extruder_id) now mirror ToolOrdering's classification: the role of a fill collection is derived from its first non-gap-fill entity, pure gap fill dispatches to the outer wall filament, and external bridges print with the bottom surface filament. Without this, gap fill and external bridges were grouped under a different extruder than ToolOrdering scheduled and than their flow widths were computed for (inert at default settings, wrong once the per-feature filaments diverge). - combine_infill() and its Print::validate() mirror pick the combining filament at 100% sparse density from the sparse infill filament, matching this fork's print-time dispatch (internal_solid_infill_uses_sparse_filament), instead of mainline's internal solid filament. - Per-extruder layer height combining ignores Snapmaker mixed (virtual) filament ids: mixed rows resolve to a different physical extruder per layer, so regions whose walls use them never combine and no preferred height is derived from an out-of-range id. - ToolOrdering::collect_extruders expands grouped manual-pattern mixed filaments for the inner wall exactly like the outer wall, so the wipe tower planner sees every extruder the grouped perimeter split will emit when the two wall filaments differ. --- src/libslic3r/GCode.cpp | 28 ++++++++++-- src/libslic3r/GCode/ToolOrdering.cpp | 65 ++++++++++++++-------------- src/libslic3r/Print.cpp | 20 ++++++--- src/libslic3r/PrintObject.cpp | 25 ++++++++--- 4 files changed, 92 insertions(+), 46 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index f4dc14bb12a..038d8728437 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5030,12 +5030,22 @@ LayerResult GCode::process_layer(const Print& print, if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { if (layer_tools.extruder_override != 0) return layer_tools.extruder_override; - const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); + // gap fill inherits the filament of the surface it fills; derive the role from the + // first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + ExtrusionRole role = erNone; + for (const ExtrusionEntity *fill_entity : entities.entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } + if (role == erNone && ! entities.entities.empty()) + // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. + return unsigned(region.config().outer_wall_filament_id.value); if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) return unsigned(region.config().sparse_infill_filament_id.value); if (role == erTopSolidInfill || role == erIroning) return unsigned(region.config().top_surface_filament_id.value); - if (role == erBottomSurface) + if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) return unsigned(region.config().bottom_surface_filament_id.value); if (is_solid_infill(role)) return unsigned(region.config().internal_solid_filament_id.value); @@ -5051,12 +5061,22 @@ LayerResult GCode::process_layer(const Print& print, const ExtrusionEntityCollection& entities, const PrintRegion& region) -> int { if (entity_type == GCode::ObjectByExtruder::Island::Region::INFILL) { - const ExtrusionRole role = entities.entities.empty() ? erNone : entities.entities.front()->role(); + // gap fill inherits the filament of the surface it fills; derive the role from the + // first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + ExtrusionRole role = erNone; + for (const ExtrusionEntity *fill_entity : entities.entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } + if (role == erNone && ! entities.entities.empty()) + // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. + return int(layer_tools.wall_extruder_id(region)); if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) return int(layer_tools.sparse_infill_filament_id(region)); if (role == erTopSolidInfill || role == erIroning) return int(layer_tools.top_surface_filament_id(region)); - if (role == erBottomSurface) + if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) return int(layer_tools.bottom_surface_filament_id(region)); if (is_solid_infill(role)) return int(layer_tools.internal_solid_filament_id(region)); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 547185789e7..46b9f931eaa 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -103,7 +103,7 @@ unsigned int infill_filament_id_1based(const LayerTools &layer_tools, const Prin return sparse_infill_filament_id_1based(region); if (role == erTopSolidInfill || role == erIroning) return region.config().top_surface_filament_id.value; - if (role == erBottomSurface) + if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) return region.config().bottom_surface_filament_id.value; return is_solid_infill(role) ? region.config().internal_solid_filament_id.value : sparse_infill_filament_id_1based(region); } @@ -746,6 +746,10 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto float(support_layer->height), &object); // support nozzle diameter restriction, resolve a "default" (0) support filament to a nozzle-matching one here, preferring a filament this layer already prints with to save toolchanges. + // Note: this fork collects support before the object's own layers, so the preference scan + // only sees filaments of previously collected objects and otherwise settles on the + // deterministic resolved_default_support_filament(); GCode::process_layer() re-prefers + // layer-scheduled extruders for "don't care" support when the G-code is emitted. if (object.config().support_nozzle_diameter.value > 0.) { auto restrict_default_filament = [&object, &layer_tools](unsigned int configured) -> unsigned int { if (configured != 0) @@ -811,36 +815,37 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } if (something_nonoverriddable){ - const unsigned int configured_wall = (extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override; - unsigned int wall_ext = resolve_mixed(configured_wall, layerCount, float(layer->print_z), float(layer->height), &object); - const unsigned int grouped_id = - grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall); - if (grouped_id != 0) { - const std::vector ordered = - m_mixed_mgr->ordered_perimeter_extruders(grouped_id, - m_num_physical, - layerCount, - float(layer->print_z), - float(layer->height)); - if (!ordered.empty()) { - if (ordered.size() >= 2) - layer_tools.preserve_extruder_order = true; - for (unsigned int extruder_id : ordered) { - layer_tools.extruders.emplace_back(extruder_id); - if (layerCount == 0 && - std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end()) - firstLayerExtruders.emplace_back(int(extruder_id)); + // Emplace a wall filament, expanding a grouped manual-pattern mixed filament + // into its per-layer ordered physical extruders (both walls need this so the + // wipe tower planner sees every extruder the grouped split will emit). + auto emplace_wall_filament = [&](unsigned int configured_wall, bool first_layer_candidate) { + unsigned int wall_ext = resolve_mixed(configured_wall, layerCount, float(layer->print_z), float(layer->height), &object); + const unsigned int grouped_id = + grouped_manual_pattern_mixed_filament_id_for_layer(layer_tools, configured_wall); + if (grouped_id != 0) { + const std::vector ordered = + m_mixed_mgr->ordered_perimeter_extruders(grouped_id, + m_num_physical, + layerCount, + float(layer->print_z), + float(layer->height)); + if (!ordered.empty()) { + if (ordered.size() >= 2) + layer_tools.preserve_extruder_order = true; + for (unsigned int extruder_id : ordered) { + layer_tools.extruders.emplace_back(extruder_id); + if (first_layer_candidate && layerCount == 0 && + std::find(firstLayerExtruders.begin(), firstLayerExtruders.end(), int(extruder_id)) == firstLayerExtruders.end()) + firstLayerExtruders.emplace_back(int(extruder_id)); + } + return; } - } else { - layer_tools.extruders.emplace_back(wall_ext); - if (layerCount == 0) - firstLayerExtruders.emplace_back(wall_ext); } - } else { layer_tools.extruders.emplace_back(wall_ext); - if (layerCount == 0) + if (first_layer_candidate && layerCount == 0) firstLayerExtruders.emplace_back(wall_ext); - } + }; + emplace_wall_filament((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override, true); // alternate_extra_wall should add an inner loop on odd layers (mirrors PerimeterGenerator's loop_number and the spiral gate of LayerRegion::make_perimeters()); layers dropping the loop again (only_one_wall_top / only_one_wall_first_layer) may reserve the filament unused, which merely costs a toolchange. const bool spiral_vase_layer = object.print()->config().spiral_mode.value && layer->id() >= size_t(region.config().bottom_shell_layers.value) && @@ -849,11 +854,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto (region.config().wall_loops.value > 1 || (region.config().alternate_extra_wall.value && layer->id() % 2 == 1 && region.config().sparse_infill_density.value > 0 && !spiral_vase_layer))) - layer_tools.extruders.emplace_back(resolve_mixed(region.config().inner_wall_filament_id.value, - layerCount, - float(layer->print_z), - float(layer->height), - &object)); + emplace_wall_filament(region.config().inner_wall_filament_id.value, false); } layer_tools.has_object = true; diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index b9d023ea701..aef944ca1f1 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1954,11 +1954,19 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* const PrintRegionConfig ®ion_config = region.config(); // The wall filaments define the part's layer pitch and must satisfy the strict lattice constraints below; must match the gating of PrintObject::region_layer_height_multiplier(). std::vector lattice_filaments; // 0-based + bool has_mixed_wall = false; if (region_config.wall_loops.value > 0) { const int num_filaments = (int)m_config.filament_diameter.size(); - auto emplace_filament = [num_filaments, &lattice_filaments](int filament_id) { + auto emplace_filament = [num_filaments, &lattice_filaments, &has_mixed_wall](int filament_id) { int i = std::max(0, filament_id - 1); - lattice_filaments.emplace_back(i >= num_filaments ? 0 : i); + if (i >= num_filaments) { + // Snapmaker mixed filament: virtual mixed ids resolve to a different + // physical extruder per layer; such walls never combine + // (matches PrintObject::region_layer_height_multiplier()). + has_mixed_wall = true; + return; + } + lattice_filaments.emplace_back(i); }; emplace_filament(region_config.outer_wall_filament_id.value); if (region_config.wall_loops.value > 1) @@ -2000,7 +2008,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* break; } } - if (region_multiplier == 0) + if (region_multiplier == 0 || has_mixed_wall) region_multiplier = 1; // 0-based indices of all filaments printing this region's features. std::vector used_filaments; @@ -2033,8 +2041,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* double combined_infill_height = 0.; unsigned int combined_infill_filament = 0; // 0-based, only valid while combining if (region_multiplier == 1 && region_config.sparse_infill_density.value > 0) { - const int filament_id = solid_combined_infill ? region_config.internal_solid_filament_id.value : - region_config.sparse_infill_filament_id.value; + // This fork prints internal solid infill at 100% density with the sparse + // filament (internal_solid_infill_uses_sparse_filament), so the sparse + // filament decides in both cases (mirrors PrintObject::combine_infill()). + const int filament_id = region_config.sparse_infill_filament_id.value; const FlowRole role = solid_combined_infill ? frSolidInfill : frInfill; const char *width_key = solid_combined_infill ? "internal_solid_infill_line_width" : "sparse_infill_line_width"; const double preferred = object->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)); diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 990f7855de6..b0476125a64 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -3486,6 +3486,10 @@ double PrintObject::extruder_preferred_layer_height(unsigned int filament_id) co // Region filament ids are 1-based, 0 ("Default") falls back to the first filament. if (filament_id == 0) filament_id = 1; + // Snapmaker mixed filament: virtual mixed ids (beyond the physical filaments) resolve to a + // different physical extruder on every layer, so no stable preferred height exists for them. + if (filament_id > print_config.filament_diameter.values.size()) + return 0.; // Classic multi-tool printers index per-extruder vectors by the filament id directly (like the min/max_layer_height and nozzle_diameter lookups elsewhere). const size_t extruder_idx = filament_id - 1; return std::max(0., print_config.extruder_layer_height.get_at(extruder_idx)); @@ -3522,13 +3526,23 @@ unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®i std::vector filaments; // 0-based filament indices if (config.wall_loops.value > 0) { const int num_filaments = (int)m_print->config().filament_diameter.size(); - auto emplace_filament = [num_filaments, &filaments](int filament_id) { + bool has_mixed_wall = false; + auto emplace_filament = [num_filaments, &filaments, &has_mixed_wall](int filament_id) { int i = std::max(0, filament_id - 1); - filaments.emplace_back(i >= num_filaments ? 0 : i); + if (i >= num_filaments) { + // Snapmaker mixed filament: virtual mixed ids resolve to a different physical + // extruder per layer, so such walls need every layer and must never combine + // (matches Print::validate() and extruder_preferred_layer_height()). + has_mixed_wall = true; + return; + } + filaments.emplace_back(i); }; emplace_filament(config.outer_wall_filament_id.value); if (config.wall_loops.value > 1) emplace_filament(config.inner_wall_filament_id.value); + if (has_mixed_wall) + return 1; } else // The brim is not considered, it prints on the first layer which is never combined. PrintRegion::collect_object_printing_extruders(m_print->config(), config, false /* has_brim */, filaments); @@ -4534,9 +4548,10 @@ void PrintObject::combine_infill() if (this->region_layer_height_multiplier(region) > 1) continue; // At 100% density the combined surfaces are internal solid infill (see use_solid_infill - // below), so that filament's preference and limits decide. - const int combine_filament_id = std::max(1, std::fabs(region.config().sparse_infill_density.value - 100.) < EPSILON ? - region.config().internal_solid_filament_id.value : region.config().sparse_infill_filament_id.value); + // below), but this fork prints them with the sparse infill filament + // (internal_solid_infill_uses_sparse_filament in PrintRegion::extruder()), so the sparse + // filament's preference and limits decide in both cases. + const int combine_filament_id = std::max(1, region.config().sparse_infill_filament_id.value); // Per-extruder vector index: the filament index is the extruder index on classic multi-tool printers (like extruder_preferred_layer_height()). const size_t combine_extruder_idx = size_t(combine_filament_id - 1); double preferred_infill_height = this->extruder_preferred_layer_height((unsigned int)combine_filament_id); From a3f490a039ccce38fa9d46cc9df4fc74ce426cc1 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 7 Jul 2026 19:01:39 +0200 Subject: [PATCH 08/43] fix: allow per-nozzle diameters in the Prepare sidebar (Snapmaker U1) --- src/slic3r/GUI/Plater.cpp | 112 +++++++++++++++++++++----------------- 1 file changed, 62 insertions(+), 50 deletions(-) diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 90e80661cac..1170745f642 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -8311,61 +8312,72 @@ void Sidebar::update_nozzle_settings(bool switch_machine) } diameter_combo->Bind(wxEVT_COMBOBOX, [this, diameter_combo, i](wxCommandEvent& event) { + // ORCA multi-nozzle-size: set ONLY this nozzle's diameter, mirroring the + // Printer Settings -> Extruder tab. Previously this switched the whole printer preset + // to a single-diameter variant (forcing all nozzles to the same size); that defeats + // per-nozzle sizes, which the slicer now supports. + + // Parse the selected diameter from the "0.4mm" combo item. + wxString sel_num = diameter_combo->GetValue(); + if (sel_num.EndsWith("mm")) + sel_num.RemoveLast(2); + double new_nd = 0.; + if (!sel_num.ToCDouble(&new_nd) || new_nd <= 0.) + return; - //auto* pNotice = p->plater->get_notification_manager(); - //if (pNotice) - //{ - // pNotice->close_notification_of_type(NotificationType::CustomNotification); - // pNotice->push_notification(_u8L("Note: Printing PLA Silk on the hot end of 0.6mm hardened steel is not recommended. 0.4mm or smaller specifications are suggested."), 0); - // pNotice->set_slicing_progress_hidden(); - //} - - auto printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; - auto printer_model_opt = printer_config.option("printer_model"); - if (printer_model_opt) { - std::string printer_model = printer_model_opt->value; - bool is_snapmaker_u1 = boost::icontains(printer_model, "Snapmaker") && boost::icontains(printer_model, "U1"); - - if (is_snapmaker_u1) - { - //check the config has flags to tips switch nozzle and all nozzle will be changed to the same type - auto notShow = wxGetApp().app_config->get("app", "sync_diameter_flags"); - if (notShow != "true") - { - RichMessageDialog dlg(static_cast(wxGetApp().mainframe), - _L("Note: Changing this will sync all other nozzles to the same diameter."), - _L("Set Nozzle Diameter"), - wxOK); - dlg.ShowCheckBox(_L("Don't show this again"), false); - auto res = dlg.ShowModal(); - bool isCheckBox = dlg.IsCheckBoxChecked(); - - if (wxID_OK == res) - wxGetApp().app_config->set("app", "sync_diameter_flags", isCheckBox); - } - } - } - - auto diameter = diameter_combo->GetValue().substr(0, 3); - auto preset = wxGetApp().preset_bundle->get_similar_printer_preset({}, diameter.ToStdString()); - if (preset == nullptr) { - BOOST_LOG_TRIVIAL(error) << "get the similar printer preset fail"; + Tab* printer_tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (printer_tab == nullptr) return; - } - preset->is_visible = true; // force visible - - for (size_t i = 0; i < p->m_nozzle_diameter_lists.size(); ++i) { - //set all nozzle use the diameter - p->m_nozzle_diameter_lists[i]->SetValue(diameter + "mm"); - } - wxGetApp().get_tab(Preset::TYPE_PRINTER)->select_preset(preset->name); - // Do not event.Skip(): select_preset rebuilds nozzle UI and can destroy this combo; skipping would let sidebar treat this as bed-type combo and use-after-free. + // Write nozzle_diameter[i] into the edited printer config, like Tab.cpp's extruder page. + DynamicPrintConfig new_conf = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const auto* nozzle_diam_opt = static_cast(new_conf.option("nozzle_diameter")); + if (nozzle_diam_opt == nullptr || i >= nozzle_diam_opt->values.size()) + return; + std::vector nozzle_diameters = nozzle_diam_opt->values; + if (std::abs(nozzle_diameters[i] - new_nd) < EPSILON) + return; // unchanged + nozzle_diameters[i] = new_nd; + new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + + // load_config marks the printer preset modified and propagates the change without + // rebuilding these combos or switching presets, so the other nozzles keep their sizes. + printer_tab->load_config(new_conf); + // Do not event.Skip(): this is a plain ComboBox; skipping would let the sidebar treat + // it as the bed-type combo (Plater::priv::on_combobox_select) and mishandle it. }); - auto diam_str = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_variant")->value; - - diameter_combo->SetValue(diam_str + "mm"); + // Show THIS nozzle's own diameter (per-nozzle sizes are supported), selecting the matching + // "x.xmm" item so the read-only combo accepts it. Falls back to the uniform printer_variant + // label only when the per-nozzle value is unavailable. + const double this_nd = (nozzle_diameter && i < nozzle_diameter->values.size()) ? nozzle_diameter->values[i] : 0.; + wxString this_label; + for (unsigned int n = 0; n < diameter_combo->GetCount(); ++n) { + wxString item = diameter_combo->GetString(n); + wxString num = item; + if (num.EndsWith("mm")) + num.RemoveLast(2); + double item_nd = 0.; + if (num.ToCDouble(&item_nd) && std::abs(item_nd - this_nd) < EPSILON) { + this_label = item; + break; + } + } + if (this_label.empty() && this_nd > 0.) { + // A per-nozzle diameter not among the printer's variant list (e.g. an imported config): + // add it so the combo can display the true value. Format with the C locale (period + // decimal) to match the other "x.xmm" items and the ToCDouble parsing above. + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << this_nd; + this_label = wxString(oss.str()) + "mm"; + diameter_combo->AppendString(this_label); + } + if (this_label.empty()) { + const auto *pv = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_variant"); + this_label = (pv ? wxString(pv->value) : wxString()) + "mm"; + } + diameter_combo->SetValue(this_label); p->m_nozzle_diameter_lists.push_back(diameter_combo); From 212b7da99a2c29b4c50e03eb44acdc0b484d3464 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Sun, 12 Jul 2026 08:17:23 +0000 Subject: [PATCH 09/43] Per-extruder preferred layer heights: combining engine, validation and per-nozzle sidebar Make the per-extruder preferred layer heights ("extruder_layer_height") actually take effect on mixed-nozzle printers and give them a first-class UI, so each feature of a part prints with the layer height of the extruder assigned to it wherever physics allows. Engine (libslic3r): - A part follows one layer pitch, defined by its wall filaments' explicit preferences, or by the agreement of its other pitch-bound feature filaments (top/bottom/internal solid) when no wall filament carries one. Runs of N base layers combine and extrude once at N times the object layer height; region tops/bottoms, overhangs and the first layer keep the finer base layers. - When the rest of the part vetoes the walls' pitch, the walls still combine on their own (walls-only pitch); disagreeing explicit wall preferences meet at the lower height instead of falling back. - Full-density top surfaces absorb the internal solid layers below them and extrude once at their filament's preferred height. - Sparse infill combines separately as before; at 100% density the combined solid interior is internal solid infill printed with the internal solid filament (PrintRegion::extruder()). - Only a nozzle that physically cannot extrude a pitch vetoes it; min/max layer heights are soft profile limits that warn instead. - Mixed (virtual) filaments rotate physical extruders per layer and never combine. - Tool ordering and G-code dispatch classify wall extrusions per entity, so overhang-first inner loops, split mixed perimeters and wipe overrides route to the correct filament. Validation (Print::validate()): - Strict errors for wall preferences the printer cannot honor (below the object layer height, not an integer multiple, above the nozzle bore) and for line widths below the combined heights. - Warnings when preferences are ignored, unhonorable, below an extruder's minimum or above its maximum, when disagreeing wall preferences meet at the lower height, and when a fine nozzle forces a part back to the object layer height. - Per-extruder layer heights are rejected with spiral vase, interface shells and variable layer height. GUI: - The Prepare sidebar shows one tab per nozzle with a Diameter combo (sets only that nozzle's diameter instead of switching the whole printer preset) and a Preferred layer height combo listing the valid multiples of the object layer height for that extruder. - Changing a nozzle's diameter adopts the layer height limits of the printer's profile for the new size when one exists and notifies the user; otherwise it warns to review the limits manually. The tabs follow the extruder count and refresh on config changes. - The preview attributes extrusions to the per-feature filaments. Tests: fff_print [MultiNozzleLayerHeight] scenarios cover region combining, walls-only pitch, top-surface absorption, bore vetoes, disagreeing wall preferences and validation errors. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01EniRUwSktuaiWwd11GH7FP --- src/libslic3r/Fill/FillBase.cpp | 9 +- src/libslic3r/GCode.cpp | 65 +-- src/libslic3r/GCode/ToolOrdering.cpp | 65 ++- src/libslic3r/GCode/ToolOrdering.hpp | 9 + src/libslic3r/Layer.cpp | 22 +- src/libslic3r/Layer.hpp | 15 + src/libslic3r/LayerRegion.cpp | 24 +- src/libslic3r/Print.cpp | 447 ++++++++++++----- src/libslic3r/Print.hpp | 19 +- src/libslic3r/PrintConfig.cpp | 60 +-- src/libslic3r/PrintObject.cpp | 339 +++++++++++-- src/libslic3r/PrintObjectSlice.cpp | 51 +- src/libslic3r/PrintRegion.cpp | 14 +- src/slic3r/GUI/GLCanvas3D.cpp | 64 ++- src/slic3r/GUI/GUI_ObjectList.cpp | 10 + src/slic3r/GUI/Plater.cpp | 380 +++++++++++--- src/slic3r/GUI/Plater.hpp | 2 + src/slic3r/GUI/Tab.cpp | 47 +- .../test_multi_nozzle_layer_height.cpp | 466 +++++++++++++++--- 19 files changed, 1662 insertions(+), 446 deletions(-) diff --git a/src/libslic3r/Fill/FillBase.cpp b/src/libslic3r/Fill/FillBase.cpp index f7c003cb2f9..91e56bc5152 100644 --- a/src/libslic3r/Fill/FillBase.cpp +++ b/src/libslic3r/Fill/FillBase.cpp @@ -307,8 +307,13 @@ std::pair Fill::_infill_direction(const Surface *surface) const } else if (this->layer_id != size_t(-1)) { // alternate fill direction //Orca: if template angle is not empty, don't apply layer angle - if(!is_using_template_angle) - out_angle += this->_layer_angle(this->layer_id / surface->thickness_layers); + if(!is_using_template_angle) + // Combined internal groups (thickness_layers > 1) alternate per group. External + // surfaces alternate per layer even when combined: an absorbed thick top surface + // (see PrintObject::combine_top_surfaces()) must fill in the same direction as the + // same layer's uncombined remainder of that top face. + out_angle += this->_layer_angle(surface->is_external() ? this->layer_id : + this->layer_id / surface->thickness_layers); } else { // printf("Layer_ID undefined!\n"); } diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 038d8728437..9865cdd943b 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1514,10 +1514,11 @@ std::vector GCode::collect_layers_to_print(const PrintObjec extra_gap = std::max(extra_gap, object.config().raft_contact_distance.value); } double layer_span = layer_to_print.layer()->height; - // ORCA: the top layer of a combined group prints the whole group at once, so tolerate a gap of combined_height() (equals the layer height when not combined). + // ORCA: the top layer of a combined group or walls-only run prints the whole group at + // once, so tolerate a gap of its height (equals the layer height when not combined). if (layer_to_print.object_layer != nullptr) for (const LayerRegion *layerm : layer_to_print.object_layer->regions()) - layer_span = std::max(layer_span, layerm->combined_height()); + layer_span = std::max(layer_span, layerm->wall_combined_height()); double maximal_print_z = (last_extrusion_layer ? last_extrusion_layer->print_z() : 0.) + layer_span + std::max(0., extra_gap); // Negative support_contact_z is not taken into account, it can result in false positives in cases @@ -5041,8 +5042,6 @@ LayerResult GCode::process_layer(const Print& print, if (role == erNone && ! entities.entities.empty()) // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. return unsigned(region.config().outer_wall_filament_id.value); - if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) - return unsigned(region.config().sparse_infill_filament_id.value); if (role == erTopSolidInfill || role == erIroning) return unsigned(region.config().top_surface_filament_id.value); if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) @@ -5053,8 +5052,10 @@ LayerResult GCode::process_layer(const Print& print, } if (layer_tools.extruder_override != 0) return layer_tools.extruder_override; - return entities.role() == erPerimeter ? unsigned(region.config().inner_wall_filament_id.value) - : unsigned(region.config().outer_wall_filament_id.value); + bool any_outer = false, any_inner = false; + classify_wall_filaments(entities, any_outer, any_inner); + return any_inner && ! any_outer ? unsigned(region.config().inner_wall_filament_id.value) + : unsigned(region.config().outer_wall_filament_id.value); }; auto configured_extruder_id = [&layer_tools](const GCode::ObjectByExtruder::Island::Region::Type entity_type, @@ -5072,8 +5073,6 @@ LayerResult GCode::process_layer(const Print& print, if (role == erNone && ! entities.entities.empty()) // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. return int(layer_tools.wall_extruder_id(region)); - if (role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON) - return int(layer_tools.sparse_infill_filament_id(region)); if (role == erTopSolidInfill || role == erIroning) return int(layer_tools.top_surface_filament_id(region)); if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) @@ -5082,8 +5081,10 @@ LayerResult GCode::process_layer(const Print& print, return int(layer_tools.internal_solid_filament_id(region)); return int(layer_tools.sparse_infill_filament_id(region)); } - return entities.role() == erPerimeter ? int(layer_tools.inner_wall_extruder_id(region)) - : int(layer_tools.wall_extruder_id(region)); + bool any_outer = false, any_inner = false; + classify_wall_filaments(entities, any_outer, any_inner); + return any_inner && ! any_outer ? int(layer_tools.inner_wall_extruder_id(region)) + : int(layer_tools.wall_extruder_id(region)); }; auto pointillism_sequence_for_filament = [&](unsigned int filament_id_1based) -> const std::vector* { @@ -5801,50 +5802,36 @@ LayerResult GCode::process_layer(const Print& print, } }; - bool split_mixed_perimeters = - entity_type == ObjectByExtruder::Island::Region::PERIMETERS && - region.config().outer_wall_filament_id.value != region.config().inner_wall_filament_id.value && - filtered_extrusions->role() == erMixed; + // ORCA: gate the split on the actual per-path classification: the collection + // role() is erMixed only when the loops' FIRST paths differ, missing e.g. a + // collection whose loops all start with an overhang path. + bool any_outer_wall = false, any_inner_wall = false; + if (entity_type == ObjectByExtruder::Island::Region::PERIMETERS && + region.config().outer_wall_filament_id.value != region.config().inner_wall_filament_id.value) + classify_wall_filaments(*filtered_extrusions, any_outer_wall, any_inner_wall); + const bool split_mixed_perimeters = any_outer_wall && any_inner_wall; if (split_mixed_perimeters) { auto outer_perimeters = std::make_unique(); auto inner_perimeters = std::make_unique(); for (const ExtrusionEntity* entity : filtered_extrusions->entities) { - // ORCA: chaining may put an overhang path first and fully overhanging loops - // have no plain perimeter path: classify by scanning every path; anything without - // an inner perimeter path uses the outer wall filament (matches PerimeterGenerator's - // overhang flows). - bool has_external = false, has_internal = false; - auto classify = [&](const ExtrusionPaths& paths) { - for (const ExtrusionPath& path : paths) { - if (path.role() == erExternalPerimeter) - has_external = true; - else if (path.role() == erPerimeter) - has_internal = true; - } - }; - if (const auto* loop = dynamic_cast(entity)) - classify(loop->paths); - else if (const auto* multi_path = dynamic_cast(entity)) - classify(multi_path->paths); - else { - const ExtrusionRole role = entity->role(); - has_external = role == erExternalPerimeter; - has_internal = role == erPerimeter; - } - if (has_external || !has_internal) + // Same classification as the wall filament dispatch (LayerTools::extruder()). + if (perimeter_entity_uses_outer_wall_filament(*entity)) outer_perimeters->append(*entity); else inner_perimeters->append(*entity); } + // Wiping-extrusion overrides were marked (and their purge volume + // credited) against the ORIGINAL collection - look them up under that + // key so a purge planned into these perimeters still happens. if (!outer_perimeters->entities.empty()) { split_perimeter_storage.emplace_back(std::move(outer_perimeters)); - process_extrusions(split_perimeter_storage.back().get(), nullptr, false); + process_extrusions(split_perimeter_storage.back().get(), filtered_extrusions, true); } if (!inner_perimeters->entities.empty()) { split_perimeter_storage.emplace_back(std::move(inner_perimeters)); - process_extrusions(split_perimeter_storage.back().get(), nullptr, false); + process_extrusions(split_perimeter_storage.back().get(), filtered_extrusions, true); } } else { process_extrusions(filtered_extrusions, filtered_extrusions, true); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 46b9f931eaa..9a7412851b2 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -87,20 +87,53 @@ void append_unique_preserve_order(std::vector &dst, unsigned int v dst.emplace_back(value); } -bool internal_solid_infill_uses_sparse_filament(const PrintRegion ®ion, ExtrusionRole role) +unsigned int sparse_infill_filament_id_1based(const PrintRegion ®ion) { - return role == erSolidInfill && std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON; + return region.config().sparse_infill_filament_id.value; } -unsigned int sparse_infill_filament_id_1based(const PrintRegion ®ion) +} // anonymous namespace + +bool perimeter_entity_uses_outer_wall_filament(const ExtrusionEntity &entity) { - return region.config().sparse_infill_filament_id.value; + // Chaining may put an overhang path first and fully overhanging loops have no plain + // perimeter path: classify by scanning every path (must match the mixed-perimeter split + // in GCode::process_layer()). + bool has_external = false, has_internal = false; + auto classify = [&](const ExtrusionPaths &paths) { + for (const ExtrusionPath &path : paths) { + if (path.role() == erExternalPerimeter) + has_external = true; + else if (path.role() == erPerimeter) + has_internal = true; + } + }; + if (const auto *loop = dynamic_cast(&entity)) + classify(loop->paths); + else if (const auto *multi_path = dynamic_cast(&entity)) + classify(multi_path->paths); + else { + const ExtrusionRole role = entity.role(); + has_external = role == erExternalPerimeter; + has_internal = role == erPerimeter; + } + return has_external || ! has_internal; +} + +void classify_wall_filaments(const ExtrusionEntityCollection &collection, bool &any_outer, bool &any_inner) +{ + any_outer = any_inner = false; + for (const ExtrusionEntity *entity : collection.entities) + (perimeter_entity_uses_outer_wall_filament(*entity) ? any_outer : any_inner) = true; } +namespace { + +// The internal solid filament owns internal solid infill at every density - including the solid +// interior at 100% sparse density (matches mainline Orca and PrintRegion::extruder(); this fork +// used to hand the 100% interior to the sparse filament, hiding the internal solid selector). unsigned int infill_filament_id_1based(const LayerTools &layer_tools, const PrintRegion ®ion, ExtrusionRole role) { - if (internal_solid_infill_uses_sparse_filament(region, role)) - return sparse_infill_filament_id_1based(region); if (role == erTopSolidInfill || role == erIroning) return region.config().top_surface_filament_id.value; if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces (internal bridges stay internal solid) @@ -352,8 +385,6 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c role = ee->role(); break; } - if (internal_solid_infill_uses_sparse_filament(region, role)) - return sparse_infill_filament_id(region); if (extrusions.has_solid_infill()) { ExtrusionRole solid_role = extrusions.role(); // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). @@ -371,7 +402,11 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c } return sparse_infill_filament_id(region); } - return extrusions.role() == erPerimeter ? inner_wall_extruder_id(region) : wall_extruder_id(region); + // Classify like the mixed-perimeter split: role() only reflects the first path of the first + // loop, which may be an overhang path of an inner loop. + bool any_outer = false, any_inner = false; + classify_wall_filaments(extrusions, any_outer, any_inner); + return any_inner && ! any_outer ? inner_wall_extruder_id(region) : wall_extruder_id(region); } static double calc_max_layer_height(const PrintConfig &config, double max_object_layer_height) @@ -448,10 +483,14 @@ bool ToolOrdering::insert_wipe_tower_extruder() for (LayerTools& lt : m_layer_tools) { if (lt.wipe_tower_partitions > 0) { lt.extruders.emplace_back(m_print_config_ptr->wipe_tower_filament - 1); - sort_remove_duplicates(lt.extruders); + if (lt.preserve_extruder_order) + // Mixed filament layers depend on their collected extruder order, sorting would destroy it. + remove_duplicates_preserve_order(lt.extruders); + else + sort_remove_duplicates(lt.extruders); changed = true; } - } + } } return changed; } @@ -877,9 +916,7 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto role = fill_entity->role(); break; } - if (internal_solid_infill_uses_sparse_filament(region, role)) - has_sparse_infill = true; - else if (role == erTopSolidInfill || role == erIroning) + if (role == erTopSolidInfill || role == erIroning) has_top_solid_surface = true; else if (role == erBottomSurface || role == erBridgeInfill) // ORCA: external bridges print as bottom surfaces has_bottom_surface = true; diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 996d4cdca58..032f9c7381f 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -15,6 +15,15 @@ namespace Slic3r { class Print; class PrintObject; class LayerTools; + +// ORCA: per-feature wall filaments. Classify a perimeter entity the way GCode::process_layer()'s +// mixed-perimeter split does: a loop carrying an external perimeter path - or a fully overhanging +// loop without any plain perimeter path - prints with the outer wall filament, everything else +// with the inner wall filament. The split and every wall filament dispatch must agree on this. +bool perimeter_entity_uses_outer_wall_filament(const ExtrusionEntity &entity); +// Do any of the collection's perimeter entities print with the outer / the inner wall filament? +void classify_wall_filaments(const ExtrusionEntityCollection &collection, bool &any_outer, bool &any_inner); + namespace CustomGCode { struct Item; } class PrintRegion; diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index 33665a525db..a40f7d2c9f1 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -89,6 +89,23 @@ const Layer* LayerRegion::combined_lower_layer() const return layer; } +// ORCA: walls-only pitch, see PrintObject::wall_layer_height_multiplier(). +double LayerRegion::wall_combined_height() const +{ + return m_wall_combined_height > 0. ? m_wall_combined_height : this->combined_height(); +} + +const Layer* LayerRegion::wall_combined_lower_layer() const +{ + if (m_wall_combined_count <= 1) + // count == 0 (walls extrude at the run top above) produces no wall extrusions anyway. + return this->combined_lower_layer(); + const Layer *layer = m_layer; + for (unsigned short i = 0; layer != nullptr && i < m_wall_combined_count; ++ i) + layer = layer->lower_layer; + return layer; +} + // merge all regions' slices to get islands void Layer::make_slices() { @@ -258,8 +275,11 @@ void Layer::make_perimeters() if (! (*it)->slices.empty()) { LayerRegion* other_layerm = *it; const PrintRegion &other_region = other_layerm->region(); - // Regions combined to different extruder layer heights extrude with different heights and must not share a make_perimeters() call. + // Regions combined to different extruder layer heights (whole-region groups or + // walls-only runs) extrude with different heights and must not share a make_perimeters() call. if ((*layerm)->combined_layer_count() == other_layerm->combined_layer_count() && + (*layerm)->wall_combined_count() == other_layerm->wall_combined_count() && + std::abs((*layerm)->wall_combined_height() - other_layerm->wall_combined_height()) < EPSILON && is_perimeter_compatible(this_region, other_region)) { other_layerm->perimeters.clear(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index cc3021f7dd5..cc14eca2a7a 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -121,6 +121,18 @@ class LayerRegion // Layer right below the covered group; replaces Layer::lower_layer for overhang / bridge // detection of combined regions. May be nullptr. const Layer* combined_lower_layer() const; + + // ORCA: walls-only pitch (PrintObject::wall_layer_height_multiplier()). Number of object + // layers whose walls this region's perimeters cover here: > 1 on the top layer of a wall run + // marked by PrintObject::apply_extruder_layer_heights(), 0 on the run layers below (they + // generate perimeters only to bound their fills and drop the wall extrusions), 1 otherwise. + unsigned short wall_combined_count() const { return m_wall_combined_count; } + // Height the walls are generated with at this layer: the wall run height on all its layers + // (the whole run's fill boundaries must line up with the walls printed at its top), else the + // region's combined height / layer height. + double wall_combined_height() const; + // Layer right below the wall run for overhang / bridge detection of the run's walls. + const Layer* wall_combined_lower_layer() const; private: void simplify_entity_collection(ExtrusionEntityCollection* entity_collection); void simplify_path(ExtrusionPath* path); @@ -140,6 +152,9 @@ class LayerRegion // ORCA: set by PrintObject::apply_extruder_layer_heights(), see combined_layer_count() / combined_height(). unsigned short m_combined_layer_count { 1 }; double m_combined_height { 0. }; + // ORCA: set by PrintObject::apply_extruder_layer_heights(), see wall_combined_count() / wall_combined_height(). + unsigned short m_wall_combined_count { 1 }; + double m_wall_combined_height { 0. }; }; class Layer diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 4aa45311918..583c5d45f18 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -85,9 +85,9 @@ unsigned int LayerRegion::extruder(FlowRole role) const unsigned int filament_id = 0; if (role == frInfill) filament_id = config.sparse_infill_filament_id.value; - else if (role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON) - filament_id = config.sparse_infill_filament_id.value; else + // Internal solid infill resolves to the internal solid filament at every density, + // including the 100% dense interior (PrintRegion::extruder()). filament_id = this->region().extruder(role); return (role == frInfill || role == frSolidInfill) ? @@ -205,8 +205,11 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe (this->layer()->id() >= size_t(region_config.bottom_shell_layers.value) && this->layer()->print_z >= region_config.bottom_shell_thickness - EPSILON); - // ORCA: on the top layer of a combined group all perimeters extrude with the whole group's height. - const double perimeter_height = this->combined_height(); + // ORCA: on the top layer of a combined group all perimeters extrude with the whole group's + // height. On layers of a walls-only run (wall_combined_count()) the walls are generated with + // the run height on every run layer - so the fill boundaries line up with the walls printed + // once at the run top - and the wall extrusions of the layers below the top are dropped below. + const double perimeter_height = this->wall_combined_height(); PerimeterGenerator g( // input: @@ -228,8 +231,9 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe fill_no_overlap ); - // Detect overhangs / bridges against the layer below the whole combined group (combined_lower_layer() == lower_layer for regular regions). - if (const Layer *lower_layer = this->combined_lower_layer(); lower_layer != nullptr) + // Detect overhangs / bridges against the layer below the whole combined group or wall run + // (wall_combined_lower_layer() == lower_layer for regular regions). + if (const Layer *lower_layer = this->wall_combined_lower_layer(); lower_layer != nullptr) // Cummulative sum of polygons over all the regions. g.lower_slices = &lower_layer->lslices; if (this->layer()->upper_layer != NULL) @@ -254,6 +258,14 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe g.process_arachne(); else g.process_classic(); + + // ORCA: walls-only pitch. The run layers below the top only generated their perimeters to + // carve fill boundaries consistent with the run; the walls themselves (and their thin fills / + // gap fills, which print with the walls) extrude once at the run top. + if (this->wall_combined_count() == 0) { + this->perimeters.clear(); + this->thin_fills.clear(); + } } #if 1 diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index aef944ca1f1..1151d7771c9 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1831,8 +1831,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } }; // Flags for the per-extruder layer height / support nozzle warnings below. + bool warned_wall_pref_conflicts = false; bool warned_unhonored_heights = false, warned_support_mixed_nozzles = false, - warned_below_min_heights = false, warned_pitch_fallbacks = false; + warned_below_min_heights = false, warned_pitch_fallbacks = false, + warned_above_max_heights = false, warned_ignored_feature_prefs = false; for (PrintObject *object : m_objects) { if (object->has_support_material()) { // BBS: remove useless logics and L() @@ -1952,193 +1954,406 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* bool object_has_combined_regions = false; for (const PrintRegion ®ion : object->all_regions()) { const PrintRegionConfig ®ion_config = region.config(); - // The wall filaments define the part's layer pitch and must satisfy the strict lattice constraints below; must match the gating of PrintObject::region_layer_height_multiplier(). + // Filaments allowed to define the part's layer pitch: its walls, or - when no wall + // filament carries an explicit preference - the region's other pitch-bound feature + // filaments. Must match the gating of PrintObject::region_layer_height_multiplier(). std::vector lattice_filaments; // 0-based - bool has_mixed_wall = false; - if (region_config.wall_loops.value > 0) { - const int num_filaments = (int)m_config.filament_diameter.size(); - auto emplace_filament = [num_filaments, &lattice_filaments, &has_mixed_wall](int filament_id) { - int i = std::max(0, filament_id - 1); - if (i >= num_filaments) { - // Snapmaker mixed filament: virtual mixed ids resolve to a different - // physical extruder per layer; such walls never combine - // (matches PrintObject::region_layer_height_multiplier()). - has_mixed_wall = true; - return; - } - lattice_filaments.emplace_back(i); - }; - emplace_filament(region_config.outer_wall_filament_id.value); - if (region_config.wall_loops.value > 1) - emplace_filament(region_config.inner_wall_filament_id.value); - } else - PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, lattice_filaments); + bool pitch_from_features = false; + bool wall_prefs_disagree = false; + // false = a mixed (virtual) filament rotates physical extruders per layer, no pitch applies. + const bool no_mixed = object->collect_region_pitch_filaments(region_config, lattice_filaments, pitch_from_features); + // The filament index is the extruder index on classic multi-tool printers. + auto extruder_of = [](unsigned int filament) { return size_t(filament); }; + // Multiple of the object layer height a preferred height conforms to, 0 when it does + // not conform (not an integer multiple, below the object layer height, or too tall + // for the extruder's bore). Min/max layer heights are soft profile limits - exceeding + // them keeps the pitch and warns below; with stock profiles the max layer height + // would otherwise reject every legal preference (the smallest being 2 * layer height). + auto conforming_multiplier = [&](double height, size_t extruder_idx) -> unsigned int { + const int n = int(std::lround(height / layer_height)); + return height >= layer_height - EPSILON && std::abs(height - n * layer_height) <= EPSILON && + height <= m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON ? + (unsigned int)std::max(1, n) : 0; + }; unsigned int region_multiplier = 0; for (const unsigned int filament : lattice_filaments) { - // The filament index is the extruder index on classic multi-tool printers. - const size_t extruder_idx = filament; - const double extruder_height = m_config.extruder_layer_height.get_at(extruder_idx); - unsigned int multiplier = 1; - if (extruder_height > 0.) { + const size_t extruder_idx = extruder_of(filament); + const double extruder_height = m_config.extruder_layer_height.get_at(extruder_idx); + if (extruder_height <= 0.) + continue; // no preference (0 = object layer height): follows the others + const unsigned int multiplier = conforming_multiplier(extruder_height, extruder_idx); + if (multiplier == 0) { + if (pitch_from_features) { + // Feature filaments only offer a pitch: a preference the part cannot print + // is skipped by the engine; tell the user why it has no effect. + if (! warned_ignored_feature_prefs) { + warned_ignored_feature_prefs = true; + warn(Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an " + "integer multiple of the object layer height (%3% mm), not below it, and must not exceed the " + "extruder's nozzle diameter."), + extruder_idx + 1, extruder_height, layer_height), + "extruder_layer_height", object); + } + continue; + } + // The wall filaments dictate the part's layer pitch, their preferences are strict. if (extruder_height < layer_height - EPSILON) return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). " "Lower the object layer height to the finest extruder layer height."), extruder_idx + 1, extruder_height, layer_height), object, "extruder_layer_height" }; - const int n = int(std::lround(extruder_height / layer_height)); - if (std::abs(extruder_height - n * layer_height) > EPSILON) + if (std::abs(extruder_height - std::lround(extruder_height / layer_height) * layer_height) > EPSILON) return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)."), extruder_idx + 1, extruder_height, layer_height), object, "extruder_layer_height" }; - if (extruder_height > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) - return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter."), - extruder_idx + 1, extruder_height), object, "extruder_layer_height" }; - // 0 means "auto" (0.75 of the nozzle diameter for adaptive layer heights), not enforced here. - if (const double max_lh = m_config.max_layer_height.get_at(extruder_idx); max_lh > EPSILON && extruder_height > max_lh + EPSILON) - return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its maximum layer height (%3% mm)."), - extruder_idx + 1, extruder_height, max_lh), object, "extruder_layer_height" }; - if (const double min_lh = m_config.min_layer_height.get_at(extruder_idx); min_lh > EPSILON && extruder_height < min_lh - EPSILON) - return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) is below its minimum layer height (%3% mm)."), - extruder_idx + 1, extruder_height, min_lh), object, "extruder_layer_height" }; - multiplier = (unsigned int)n; + return { Slic3r::format(_u8L("The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter."), + extruder_idx + 1, extruder_height), object, "extruder_layer_height" }; } + if (pitch_from_features && multiplier <= 1) + continue; // a preference equal to the object layer height offers nothing if (region_multiplier == 0) region_multiplier = multiplier; else if (region_multiplier != multiplier) { - // Walls print together; differing preferences fall back to the object layer height (warned below). + // These features print together; differing explicit preferences fall + // back from the whole-part pitch (the walls-only pitch below may still + // meet at the lower preference; warned after it is known). + if (! pitch_from_features) + wall_prefs_disagree = true; region_multiplier = 1; break; } } - if (region_multiplier == 0 || has_mixed_wall) + if (region_multiplier == 0 || ! no_mixed) region_multiplier = 1; + const bool solid_combined_infill = std::fabs(region_config.sparse_infill_density.value - 100.) < EPSILON; + auto selector_filament0 = [](int filament_id) { return (unsigned int)std::max(1, filament_id) - 1; }; + auto selector_extruder_idx = [&](int filament_id) { return extruder_of(selector_filament0(filament_id)); }; + // At 100% density there is no sparse infill: the solid interior is internal solid + // infill (PrintRegion::extruder()), so the sparse filament prints nothing - unless + // it also prints another feature here. + auto prints_nothing = [&](unsigned int filament) { + return solid_combined_infill && + filament == selector_filament0(region_config.sparse_infill_filament_id.value) && + filament != selector_filament0(region_config.internal_solid_filament_id.value) && + filament != selector_filament0(region_config.top_surface_filament_id.value) && + filament != selector_filament0(region_config.bottom_surface_filament_id.value) && + filament != selector_filament0(region_config.outer_wall_filament_id.value) && + filament != selector_filament0(region_config.inner_wall_filament_id.value); + }; + // Inner-wall loops can appear at a single wall loop too (alternate / extra walls, + // see PrintObject::region_prints_inner_walls()). + auto prints_walls = [&](unsigned int filament) { + return (region_config.wall_loops.value > 0 && filament == selector_filament0(region_config.outer_wall_filament_id.value)) || + (region_config.wall_loops.value > 0 && PrintObject::region_prints_inner_walls(region_config) && + filament == selector_filament0(region_config.inner_wall_filament_id.value)); + }; + auto prints_top = [&](unsigned int filament) { + return region_config.top_shell_layers.value > 0 && + filament == selector_filament0(region_config.top_surface_filament_id.value); + }; + auto prints_bottom_or_solid = [&](unsigned int filament) { + return (region_config.bottom_shell_layers.value > 0 && filament == selector_filament0(region_config.bottom_surface_filament_id.value)) || + (! solid_combined_infill && filament == selector_filament0(region_config.internal_solid_filament_id.value)); + }; + // Mirrors PrintObject::combine_top_surfaces(): a conforming preferred pitch of a + // full-density top surface absorbs the solid layers below whenever the region itself + // prints at the object layer height. Geometry decides per area, so this gates + // warnings only. Mixed (virtual) filament ids never combine. + unsigned int top_candidate = 1; + if (region_config.top_shell_layers.value > 0 && + region_config.top_surface_density.value >= 100. - EPSILON && + std::max(0, region_config.top_surface_filament_id.value - 1) < (int)m_config.filament_diameter.size()) { + const size_t top_extruder_idx = selector_extruder_idx(region_config.top_surface_filament_id.value); + const unsigned int m = conforming_multiplier(m_config.extruder_layer_height.get_at(top_extruder_idx), top_extruder_idx); + if (m > 1) + top_candidate = m; + } + // Walls-only pitch (mirrors PrintObject::wall_layer_height_multiplier()): when the + // rest of the part vetoes the walls' pitch, the walls still combine to it on their + // own. Disagreeing explicit wall preferences meet at the lower one; preference-less + // wall filaments follow, but every wall filament must fit the pitch through its bore. + unsigned int wall_candidate = 0; + if (region_config.wall_loops.value > 0) { + auto merge_wall = [&](int filament_id) -> bool { + if (std::max(0, filament_id - 1) >= (int)m_config.filament_diameter.size()) + // Mixed (virtual) filament ids never combine. + return false; + const size_t extruder_idx = selector_extruder_idx(filament_id); + const double preferred = m_config.extruder_layer_height.get_at(extruder_idx); + if (preferred <= 0.) + return true; + const unsigned int m = std::max(1u, conforming_multiplier(preferred, extruder_idx)); + wall_candidate = wall_candidate == 0 ? m : std::min(wall_candidate, m); + return true; + }; + auto wall_nozzle_fits = [&](unsigned int m) { + const double p = m * layer_height; + if (p > m_config.nozzle_diameter.get_at(selector_extruder_idx(region_config.outer_wall_filament_id.value)) + EPSILON) + return false; + return ! PrintObject::region_prints_inner_walls(region_config) || + p <= m_config.nozzle_diameter.get_at(selector_extruder_idx(region_config.inner_wall_filament_id.value)) + EPSILON; + }; + if (! merge_wall(region_config.outer_wall_filament_id.value) || + (PrintObject::region_prints_inner_walls(region_config) && ! merge_wall(region_config.inner_wall_filament_id.value))) + wall_candidate = 1; + else if (wall_candidate > 1 && ! wall_nozzle_fits(wall_candidate)) + wall_candidate = 1; + } + if (wall_candidate == 0) + wall_candidate = 1; // 0-based indices of all filaments printing this region's features. std::vector used_filaments; PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, used_filaments); if (region_multiplier > 1) - // A feature filament that cannot print the walls' pitch makes the part fall back to the object layer height (mirrors PrintObject::region_layer_height_multiplier()). + // Only a nozzle that physically cannot extrude the part's pitch vetoes it; + // exceeding a max layer height keeps the pitch and is warned about below + // (mirrors PrintObject::region_layer_height_multiplier()). for (const unsigned int filament : used_filaments) { - const size_t extruder_idx = filament; // filament index == extruder index here + if (prints_nothing(filament)) + // The inert 100%-density sparse selector must not veto the pitch. + continue; + const size_t extruder_idx = extruder_of(filament); const double pitch = region_multiplier * layer_height; - const double max_lh = m_config.max_layer_height.get_at(extruder_idx); - const double min_lh = m_config.min_layer_height.get_at(extruder_idx); - if (pitch > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON || - (max_lh > EPSILON && pitch > max_lh + EPSILON) || - (min_lh > EPSILON && pitch < min_lh - EPSILON)) { - if (! warned_pitch_fallbacks) { + if (pitch > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) { + if (! pitch_from_features && wall_candidate == region_multiplier) + // The wall filaments' own agreed pitch survives as a walls-only + // pitch: only the walls extrude it and nothing is lost. + ; + else if (! warned_pitch_fallbacks && + // Combining top surfaces still print a feature-derived + // pitch; the unhonored-heights warning covers the rest. + ! (pitch_from_features && top_candidate == region_multiplier)) { warned_pitch_fallbacks = true; - warn(Slic3r::format(_u8L("The walls of some object parts prefer %1% mm layers, but filament %2% printing " - "other features of the part cannot print that height (nozzle diameter or layer " - "height limits). These parts print with the object layer height instead."), - pitch, filament + 1), - "extruder_layer_height", object); + if (prints_walls(filament)) + // Blocked by the walls' own bore: outer and inner walls + // print together, both filaments must fit the pitch. + warn(Slic3r::format(_u8L("Some object parts prefer %1% mm layers, but the nozzle of wall " + "filament %2% is too small to extrude that height. Outer and inner " + "walls print together, so these walls keep the object layer height. " + "Assign both wall features to filaments with large enough nozzles."), + pitch, filament + 1), + "extruder_layer_height", object); + else + warn(Slic3r::format(_u8L("Some object parts prefer %1% mm layers, but the nozzle of filament %2% " + "printing other features of the part is too small to extrude that height. " + "These parts print with the object layer height instead."), + pitch, filament + 1), + "extruder_layer_height", object); } region_multiplier = 1; break; } } + // The walls combine on their own only while the region prints at the object layer height. + const unsigned int wall_multiplier = region_multiplier == 1 ? wall_candidate : 1; + // Disagreeing wall preferences: report what actually happens once the walls-only + // pitch is known - with every extruder carrying a preferred height, a wall + // selector left on "Default" resolves to the part's filament and its preference + // silently conflicts with the other wall's. + if (wall_prefs_disagree && ! warned_wall_pref_conflicts) { + warned_wall_pref_conflicts = true; + if (wall_multiplier > 1) + warn(Slic3r::format(_u8L("The wall filaments of some object parts prefer different layer heights. " + "Outer and inner walls print together, so these walls print with the lower " + "height (%1% mm)."), + wall_multiplier * layer_height), + "extruder_layer_height", object); + else + warn(_u8L("The wall filaments of some object parts prefer different layer heights. " + "Outer and inner walls print together, so these walls keep the object layer " + "height. Assign both wall features to filaments preferring the same height " + "to print thicker walls."), + "extruder_layer_height", object); + } + // A pitch above a filament's max layer height keeps the pitch: max is a soft + // profile limit that the explicit preference overrides. Warn once. + auto warn_above_max = [&](double pitch, unsigned int filament0, double max_lh) { + if (warned_above_max_heights || max_lh <= EPSILON || pitch <= max_lh + EPSILON) + return; + warned_above_max_heights = true; + warn(Slic3r::format(_u8L("Some object parts print %1% mm layers with filament %2% whose maximum layer " + "height is %3% mm. Assign the part's features to filaments of the coarser " + "nozzle, raise the filament's maximum layer height, or accept printing above it."), + pitch, filament0 + 1, max_lh), + "extruder_layer_height", object); + }; + if (region_multiplier > 1 || wall_multiplier > 1) + // With a walls-only pitch only the wall filaments print it, the others stay + // at the object layer height. + for (const unsigned int filament : used_filaments) { + double pitch = region_multiplier * layer_height; + if (wall_multiplier > 1) { + if (! prints_walls(filament)) + continue; + pitch = wall_multiplier * layer_height; + } + warn_above_max(pitch, filament, m_config.max_layer_height.get_at(extruder_of(filament))); + } const double region_pitch = region_multiplier * layer_height; - // Infill combines up to the preferred height of its printing filament (sparse, or internal solid at 100% density; mirrors PrintObject::combine_infill()); its width must cover the tallest group. - const bool solid_combined_infill = std::fabs(region_config.sparse_infill_density.value - 100.) < EPSILON; + auto filament_nozzle = [&](int filament_id) { + return m_config.nozzle_diameter.get_at(selector_extruder_idx(filament_id)); + }; + // Explicit line width of a role resolved against its filament's nozzle; 0 = auto + // width, always considered valid (see validate_extrusion_width() above; the top + // surface auto width equals the nozzle diameter exactly, which must not hard-error + // an equal pitch). + auto resolve_line_width = [&](const char *width_key, double nozzle) -> double { + auto width_opt = *region_config.option(width_key); + if (width_opt.value == 0.) + width_opt = object->config().line_width; + return width_opt.value == 0. ? 0. : width_opt.get_abs_value(nozzle); + }; + // Infill combines up to the preferred height of its printing filament (sparse, or + // internal solid at 100% density; mirrors PrintObject::combine_infill()): groups + // are capped only by the bore of the printing nozzle, the max layer height is a + // soft limit the preference overrides. The width must cover the tallest group. double combined_infill_height = 0.; unsigned int combined_infill_filament = 0; // 0-based, only valid while combining if (region_multiplier == 1 && region_config.sparse_infill_density.value > 0) { - // This fork prints internal solid infill at 100% density with the sparse - // filament (internal_solid_infill_uses_sparse_filament), so the sparse - // filament decides in both cases (mirrors PrintObject::combine_infill()). - const int filament_id = region_config.sparse_infill_filament_id.value; + const int filament_id = solid_combined_infill ? region_config.internal_solid_filament_id.value : + region_config.sparse_infill_filament_id.value; const FlowRole role = solid_combined_infill ? frSolidInfill : frInfill; const char *width_key = solid_combined_infill ? "internal_solid_infill_line_width" : "sparse_infill_line_width"; const double preferred = object->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)); if (preferred > layer_height + EPSILON) { - const double infill_nozzle = m_config.nozzle_diameter.get_at(region.extruder(role) - 1); - // Mirrors PrintObject::combine_infill(): groups capped by both feature nozzles and the printing extruder's max layer height (0 = unset). - const unsigned int combine_filament0 = (unsigned int)std::max(1, filament_id) - 1; - double combine_cap = std::min({preferred, - m_config.nozzle_diameter.get_at(std::max(1, region_config.sparse_infill_filament_id.value) - 1), - m_config.nozzle_diameter.get_at(std::max(1, region_config.internal_solid_filament_id.value) - 1)}); - if (const double max_lh = m_config.max_layer_height.get_at(combine_filament0); max_lh > EPSILON) - combine_cap = std::min(combine_cap, max_lh); + const unsigned int combine_filament0 = selector_filament0(filament_id); + const size_t combine_extruder_idx = extruder_of(combine_filament0); + const double combine_cap = std::min(preferred, m_config.nozzle_diameter.get_at(combine_extruder_idx)); combined_infill_height = std::floor(combine_cap / layer_height + EPSILON) * layer_height; combined_infill_filament = combine_filament0; - auto width_opt = *region_config.option(width_key); - if (width_opt.value == 0.) - width_opt = object->config().line_width; - const double width = width_opt.value == 0. ? - double(Flow::auto_extrusion_width(role, float(infill_nozzle))) : - width_opt.get_abs_value(infill_nozzle); + warn_above_max(combined_infill_height, combine_filament0, m_config.max_layer_height.get_at(combine_extruder_idx)); + const double infill_nozzle = filament_nozzle((int)region.extruder(role)); + double width = resolve_line_width(width_key, infill_nozzle); + if (width == 0.) + width = double(Flow::auto_extrusion_width(role, float(infill_nozzle))); if (combined_infill_height > layer_height + EPSILON && width <= combined_infill_height + EPSILON) return { Slic3r::format(_u8L("The %1% mm infill line width is too small for infill combined to %2% mm high layers. " "Increase the line width or lower the preferred layer height of the infill filament."), width, combined_infill_height), object, width_key }; } } - // Warn once when a filament's minimum layer height is above the height its features print at (the part's pitch; the combined infill prints above the minimum by construction). + // The combined infill is not pitch-bound: a filament printing it AND a pitch-bound + // feature is not fully covered by the combined height (at 100% density the internal + // solid infill IS the combined infill). + auto prints_pitch_features = [&](unsigned int filament) { + return prints_walls(filament) || prints_top(filament) || prints_bottom_or_solid(filament); + }; + // The top surfaces combine only while the region prints at the object layer height. + const unsigned int top_multiplier = region_multiplier == 1 ? top_candidate : 1; + if (top_multiplier > 1) + warn_above_max(top_multiplier * layer_height, + selector_filament0(region_config.top_surface_filament_id.value), + m_config.max_layer_height.get_at(selector_extruder_idx(region_config.top_surface_filament_id.value))); + // Heights the filament's pitch-bound features print at: walls at the walls-only + // pitch when active, top surfaces at the top pitch when active, everything else + // at the region pitch (also used by the unhonored-heights check below). + const double wall_print_height = wall_multiplier > 1 ? wall_multiplier * layer_height : region_pitch; + const double top_print_height = top_multiplier > 1 ? top_multiplier * layer_height : region_pitch; + auto lowest_feature_height = [&](unsigned int filament) { + double h = std::numeric_limits::max(); + if (prints_walls(filament)) + h = std::min(h, wall_print_height); + if (prints_top(filament)) + h = std::min(h, top_print_height); + if (prints_bottom_or_solid(filament)) + h = std::min(h, region_pitch); + // No pitch-bound feature (e.g. the sparse filament): prints at the region pitch. + return h == std::numeric_limits::max() ? region_pitch : h; + }; + // Warn once when a filament's minimum layer height is above the height its features + // print at (the combined infill prints above the minimum by construction). if (! warned_below_min_heights && heights_feature_active) for (const unsigned int filament : used_filaments) { - const double min_lh = m_config.min_layer_height.get_at(filament); - if (min_lh <= EPSILON) + const double min_lh = m_config.min_layer_height.get_at(extruder_of(filament)); + if (min_lh <= EPSILON || prints_nothing(filament)) continue; - const bool pitch_below_min = region_pitch < min_lh - EPSILON; - // Areas of a combined region that cannot reach the pitch fall back to the object layer height (see apply_extruder_layer_heights()), possibly below the minimum. - const bool fallback_below_min = region_multiplier > 1 && layer_height < min_lh - EPSILON; + const double lowest = lowest_feature_height(filament); + const bool pitch_below_min = lowest < min_lh - EPSILON; + // Areas of a combined region that cannot reach the pitch - and the first layer / + // fallback areas of a walls-only run or a combining top surface - fall back to + // the object layer height (see apply_extruder_layer_heights()), possibly below + // the minimum. + const bool fallback_below_min = (region_multiplier > 1 || + (wall_multiplier > 1 && prints_walls(filament)) || + (top_multiplier > 1 && prints_top(filament))) && + layer_height < min_lh - EPSILON; if (! pitch_below_min && ! fallback_below_min) continue; if (combined_infill_height > 0. && filament == combined_infill_filament && - combined_infill_height >= min_lh - EPSILON && ! fallback_below_min) + combined_infill_height >= min_lh - EPSILON && ! fallback_below_min && + ! prints_pitch_features(filament)) continue; warned_below_min_heights = true; warn(Slic3r::format(_u8L("Some object parts print %1% mm layers with filament %2% whose minimum layer " "height is %3% mm. Raise the object layer height, use a filament with a finer " "nozzle for these features, or accept printing below the extruder's minimum."), - pitch_below_min ? region_pitch : layer_height, filament + 1, min_lh), + pitch_below_min ? lowest : layer_height, + filament + 1, min_lh), "extruder_layer_height", object); break; } - // Warn once about preferred heights the part cannot honor: walls set the pitch, infill combines separately, everything else prints with the pitch. + // Warn once about preferred heights the part cannot honor: one pitch applies to the + // whole part, while walls, top surfaces and the infill can each combine to their own + // height. Walls and top surfaces rescued at their own pitches count as honored + // (geometry may still fall back per area; warned about is only what can never be + // honored). A filament also printing the combined infill is covered only when the + // infill reaches the preference too. if (! warned_unhonored_heights) for (const unsigned int filament : used_filaments) { const double preferred = object->extruder_preferred_layer_height(filament + 1); - if (preferred <= 0. || std::abs(preferred - region_pitch) < EPSILON) - continue; - if (combined_infill_height > 0. && filament == combined_infill_filament) + if (preferred <= 0. || prints_nothing(filament)) continue; - // At 100% density no sparse surfaces exist, the sparse filament's preference is moot. - if (solid_combined_infill && - filament == (unsigned int)std::max(1, region_config.sparse_infill_filament_id.value) - 1) + const bool combined_covered = ! (combined_infill_height > 0. && filament == combined_infill_filament) || + combined_infill_height >= preferred - EPSILON; + if (prints_pitch_features(filament)) { + bool honored = combined_covered; + if (prints_walls(filament)) + honored &= std::abs(wall_print_height - preferred) < EPSILON; + if (prints_top(filament)) + honored &= std::abs(top_print_height - preferred) < EPSILON; + if (prints_bottom_or_solid(filament)) + honored &= std::abs(region_pitch - preferred) < EPSILON; + if (honored) + continue; + } else if (std::abs(preferred - region_pitch) < EPSILON || + (combined_infill_height > 0. && filament == combined_infill_filament && + combined_infill_height >= preferred - EPSILON)) { + // Not pitch-bound (the sparse filament): honored at the region pitch or + // by the combined infill reaching the preference. continue; + } warned_unhonored_heights = true; warn(_u8L("Some object parts use filaments whose preferred layer heights cannot all be honored: " - "the wall filaments set a part's layer pitch, the infill can combine to a thicker height, " - "and the remaining features print with the part's pitch."), + "a part prints its features with one layer pitch (set by its wall filaments, or by the " + "other features' agreement when no wall filament has a preference); the walls, the top " + "surfaces and the infill can each combine to their own height when the rest of the part " + "cannot follow them, but the remaining features print with the part's pitch."), "extruder_layer_height", object); break; } - if (region_multiplier > 1) { + if (region_multiplier > 1 || wall_multiplier > 1 || top_multiplier > 1) { object_has_combined_regions = true; - // The combined extrusions are thicker, so line widths must stay above the combined - // height, resolved per role against the region's own nozzle (mirrors PrintRegion::flow()), - // unlike validate_extrusion_width() above which uses the object's smallest nozzle. - // (role, 1-based filament printing it, width key). Bottom surfaces print - // the solid infill width with the bottom surface filament (Fill.cpp). - const std::tuple width_roles[] = { - { frExternalPerimeter, region_config.outer_wall_filament_id.value, "outer_wall_line_width" }, - { frPerimeter, region_config.inner_wall_filament_id.value, "inner_wall_line_width" }, - { frInfill, region_config.sparse_infill_filament_id.value, "sparse_infill_line_width" }, - { frSolidInfill, region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width" }, - { frTopSolidInfill, region_config.top_surface_filament_id.value, "top_surface_line_width" }, - { frSolidInfill, region_config.bottom_surface_filament_id.value, "internal_solid_infill_line_width" }, + // Combined extrusions are thicker: explicit line widths must stay above the + // height each feature prints at, resolved per role against the region's own + // nozzle (mirrors PrintRegion::flow(); validate_extrusion_width() above uses + // the object's smallest nozzle instead). Bottom surfaces print the solid + // infill width (Fill.cpp). Features the region does not print (e.g. sparse + // infill at 100% density) must not hard-error the pitch; features printing + // at the object layer height are skipped by the pitch test. + const std::tuple width_checks[] = { + { region_config.outer_wall_filament_id.value, "outer_wall_line_width", wall_print_height, region_config.wall_loops.value > 0 }, + { region_config.inner_wall_filament_id.value, "inner_wall_line_width", wall_print_height, region_config.wall_loops.value > 0 }, + { region_config.sparse_infill_filament_id.value, "sparse_infill_line_width", region_pitch, region_config.sparse_infill_density.value > 0 && ! solid_combined_infill }, + { region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width", region_pitch, true }, + { region_config.top_surface_filament_id.value, "top_surface_line_width", top_print_height, region_config.top_shell_layers.value > 0 }, + { region_config.bottom_surface_filament_id.value, "internal_solid_infill_line_width", region_pitch, region_config.bottom_shell_layers.value > 0 }, }; - for (const auto &[role, filament_id, width_key] : width_roles) { - const double nozzle_diameter = m_config.nozzle_diameter.get_at(std::max(1, filament_id) - 1); - auto width_opt = *region_config.option(width_key); - if (width_opt.value == 0.) - width_opt = object->config().line_width; - const double width = width_opt.value == 0. ? - double(Flow::auto_extrusion_width(role, float(nozzle_diameter))) : - width_opt.get_abs_value(nozzle_diameter); - if (width <= region_pitch + EPSILON) + for (const auto &[filament_id, width_key, pitch, prints] : width_checks) { + if (! prints || pitch <= layer_height + EPSILON) + continue; + const double width = resolve_line_width(width_key, filament_nozzle(filament_id)); + if (width > 0. && width <= pitch + EPSILON) return { Slic3r::format(_u8L("The %1% mm line width is too small for the %2% mm layer height of its extruder. " "Increase the line width or lower the extruder layer height."), - width, region_pitch), object, width_key }; + width, pitch), object, width_key }; } } } @@ -2993,7 +3208,9 @@ std::string Print::export_gcode(const std::string& path_template, GCodeProcessor gcode.do_export(this, path.c_str(), result, thumbnail_cb); //BBS - result->conflict_result = m_conflict_result; + if (result != nullptr) + // The result is optional (the CLI and tests pass none, see Slic3r::Test::gcode()). + result->conflict_result = m_conflict_result; return path.c_str(); } diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 6632506b945..1c86f1fa68d 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -493,8 +493,20 @@ class PrintObject : public PrintObjectBaseWithState &filaments, bool &pitch_from_features) const; // Layer pitch multiplier of a region, driven by its wall filaments; 1 when no combining is requested or possible. unsigned int region_layer_height_multiplier(const PrintRegion ®ion) const; + // Do inner-wall loops print in this region? Besides wall_loops > 1, the alternating extra + // wall and the extra perimeters on overhangs add inner loops even at a single wall loop. + static bool region_prints_inner_walls(const PrintRegionConfig &config); + // Walls-only pitch multiplier: when the region as a whole cannot follow its wall filaments' + // preferred pitch (region_layer_height_multiplier() == 1, e.g. a finer-nozzle filament prints + // the region's other features), the walls alone combine to it while everything else keeps + // printing every layer. 1 when the walls print with the region's own pitch. + unsigned int wall_layer_height_multiplier(const PrintRegion ®ion) const; // Any region of this object printing with a layer height multiplier > 1? bool has_combined_layer_regions() const; // Multi-nozzle support restriction ("support_nozzle_diameter" print option): may the given 1-based filament print this object's support / raft? @@ -587,6 +599,9 @@ class PrintObject : public PrintObjectBaseWithState prepare_adaptive_infill_data( const std::vector>& surfaces_w_bottom_z) const; @@ -1123,7 +1138,9 @@ class Print : public PrintBaseWithState PrintRegionPtrs m_print_regions; //SoftFever - bool m_isBBLPrinter; + // Assigned by the GUI (BackgroundSlicingProcess) from the vendor; must not be read + // uninitialized by CLI/tests - BBL-only code paths key off it. + bool m_isBBLPrinter { false }; // Ordered collections of extrusion paths to build skirt loops and brim. ExtrusionEntityCollection m_skirt; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index e5a1b28e4aa..f98a5aa7649 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -3970,12 +3970,14 @@ void PrintConfigDef::init_fff_params() def = this->add("extruder_layer_height", coFloats); def->label = L("Preferred layer height"); def->tooltip = L("Layer height this extruder should print with, used for printers whose extruders have " - "different nozzle sizes. Object parts whose walls are assigned to this extruder are " - "printed only on every Nth layer with correspondingly thicker extrusions, wherever " - "their geometry allows it (near-vertical walls); elsewhere they fall back to the " - "object layer height. It must then be an integer multiple of the object layer height. " - "When only the sparse infill of a part uses this extruder, the infill is combined up " - "to this height instead. 0 means to use the object layer height."); + "different nozzle sizes. It must be an integer multiple of the object layer height. " + "A part whose features all follow this extruder prints only on every Nth layer with " + "correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it " + "falls back to the object layer height. When the rest of the part cannot follow, " + "walls assigned to this extruder still combine to this height on their own, " + "full-density top surfaces absorb the solid layers below them, and sparse or 100% " + "dense infill combines to this height independently. 0 means to use the object " + "layer height."); def->sidetext = "mm"; // milimeters, don't need translation def->min = 0; def->mode = comAdvanced; @@ -7546,24 +7548,11 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) this->option("wipe_tower_filament")->setInt(0); } - if (this->has("sparse_infill_filament_id")) { - int sparse_infill_filament_id = this->option("sparse_infill_filament_id")->getInt(); - if (sparse_infill_filament_id > 0 && (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0)) - this->option("internal_solid_filament_id", true)->setInt(sparse_infill_filament_id); - } - - const int internal_solid = this->has("internal_solid_filament_id") ? this->option("internal_solid_filament_id")->getInt() : 0; - const int top_surface = this->has("top_surface_filament_id") ? this->option("top_surface_filament_id")->getInt() : 0; - const int bottom_surface = this->has("bottom_surface_filament_id") ? this->option("bottom_surface_filament_id")->getInt() : 0; - - if (internal_solid == 0 && top_surface > 0) - this->option("internal_solid_filament_id", true)->setInt(top_surface); - if (internal_solid == 0 && bottom_surface > 0) - this->option("internal_solid_filament_id", true)->setInt(bottom_surface); - if (top_surface == 0 && internal_solid > 0) - this->option("top_surface_filament_id", true)->setInt(internal_solid); - if (bottom_surface == 0 && internal_solid > 0) - this->option("bottom_surface_filament_id", true)->setInt(internal_solid); + // Note: no cross-propagation between the per-feature filament selectors here. Filling one + // selector from another (sparse -> internal solid, internal solid <-> top/bottom) silently + // overwrote "Default" (0), which means "use the part's filament", with an unrelated feature's + // explicit filament - e.g. assigning internal solid infill dragged the top/bottom surfaces along. + // Each selector resolves its own "Default" at slicing time (PrintRegion::extruder()). if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { @@ -7643,24 +7632,11 @@ void DynamicPrintConfig::normalize_fdm_1() } } - if (this->has("sparse_infill_filament_id")) { - int sparse_infill_filament_id = this->option("sparse_infill_filament_id")->getInt(); - if (sparse_infill_filament_id > 0 && (!this->has("internal_solid_filament_id") || this->option("internal_solid_filament_id")->getInt() == 0)) - this->option("internal_solid_filament_id", true)->setInt(sparse_infill_filament_id); - } - - const int internal_solid = this->has("internal_solid_filament_id") ? this->option("internal_solid_filament_id")->getInt() : 0; - const int top_surface = this->has("top_surface_filament_id") ? this->option("top_surface_filament_id")->getInt() : 0; - const int bottom_surface = this->has("bottom_surface_filament_id") ? this->option("bottom_surface_filament_id")->getInt() : 0; - - if (internal_solid == 0 && top_surface > 0) - this->option("internal_solid_filament_id", true)->setInt(top_surface); - if (internal_solid == 0 && bottom_surface > 0) - this->option("internal_solid_filament_id", true)->setInt(bottom_surface); - if (top_surface == 0 && internal_solid > 0) - this->option("top_surface_filament_id", true)->setInt(internal_solid); - if (bottom_surface == 0 && internal_solid > 0) - this->option("bottom_surface_filament_id", true)->setInt(internal_solid); + // Note: no cross-propagation between the per-feature filament selectors here. Filling one + // selector from another (sparse -> internal solid, internal solid <-> top/bottom) silently + // overwrote "Default" (0), which means "use the part's filament", with an unrelated feature's + // explicit filament - e.g. assigning internal solid infill dragged the top/bottom surfaces along. + // Each selector resolves its own "Default" at slicing time (PrintRegion::extruder()). if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index b0476125a64..c50238a25ff 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -515,6 +515,12 @@ void PrintObject::prepare_infill() this->bridge_over_infill(); m_print->throw_if_canceled(); + // Per-extruder layer height: top surfaces absorb the solid layers right below them up to + // their filament's preferred height. Runs before combine_infill() so the interior can still + // combine around the absorbed areas. + this->combine_top_surfaces(); + m_print->throw_if_canceled(); + // combine fill surfaces to honor the "infill every N layers" option this->combine_infill(); m_print->throw_if_canceled(); @@ -894,6 +900,12 @@ bool PrintObject::invalidate_state_by_config_options( bool invalidated = false; for (const t_config_option_key &opt_key : opt_keys) { // Per-extruder layer height: these options feed region_layer_height_multiplier(), which decides layer combination at the slicing step. + if (opt_key == "support_nozzle_diameter" && m_config.raft_layers.value > 0) + // Feeds SlicingParameters::create_from_config(): the support / raft extruder bore sets + // the raft layer heights and object_print_z_min, which the object layers bake in at + // the slicing step - a raft must re-slice (plain supports regenerate via the support + // branch below). + steps.emplace_back(posSlice); if (opt_key == "outer_wall_filament_id" || opt_key == "inner_wall_filament_id" || opt_key == "sparse_infill_filament_id" || opt_key == "internal_solid_filament_id" || opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id" @@ -3273,11 +3285,15 @@ struct FeatureFilamentOverrideMask bool inner_wall_filament_id = false; }; -static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides) +static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPrintConfig &in, FeatureFilamentOverrideMask &feature_overrides, int &base_extruder_through_scopes) { // 1) Explicit feature filament values take precedence over base extruder fallback. auto *opt_extruder = in.opt(key_extruder); int base_extruder = (opt_extruder != nullptr) ? opt_extruder->value : 0; + if (base_extruder > 0) + // The innermost "extruder" assignment seen so far - the part's filament an explicit + // "Default" (0) selector resets to. + base_extruder_through_scopes = base_extruder; // 2) Copy the rest of the values. for (auto it = in.cbegin(); it != in.cend(); ++ it) @@ -3301,6 +3317,12 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr else if (it->first == "inner_wall_filament_id") feature_overrides.inner_wall_filament_id = true; } else { + // Reset the inherited value too: an explicit "Default" at this scope must + // undo an earlier scope's explicit filament, not silently keep it. It + // resets to the part's filament (the innermost "extruder" seen so far, or + // 0 for the first filament) - a literal 0 would clamp to filament 1 even + // when the part prints with another filament. + my_opt->setInt(base_extruder_through_scopes); if (it->first == "sparse_infill_filament_id") feature_overrides.sparse_infill_filament_id = false; else if (it->first == "internal_solid_filament_id") @@ -3350,20 +3372,27 @@ PrintRegionConfig region_config_from_model_volume(const PrintRegionConfig &defau feature_overrides.inner_wall_filament_id = (config.inner_wall_filament_id.value > 0); } + // The part's filament as resolved so far, for explicit "Default" (0) selector resets. Seeded + // from the object for modifiers too (their parent part's own extruder is already folded into + // default_or_parent_region_config and not recoverable here). + int base_extruder_through_scopes = 0; + if (const auto *opt = volume.get_object()->config.get().opt("extruder"); opt != nullptr && opt->value > 0) + base_extruder_through_scopes = opt->value; + if (volume.is_model_part()) { // default_or_parent_region_config contains the Print's PrintRegionConfig. // Override with ModelObject's PrintRegionConfig values. - apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides); + apply_to_print_region_config(config, volume.get_object()->config.get(), feature_overrides, base_extruder_through_scopes); } else { // default_or_parent_region_config contains parent PrintRegion config, which already contains ModelVolume's config. } - apply_to_print_region_config(config, volume.config.get(), feature_overrides); + apply_to_print_region_config(config, volume.config.get(), feature_overrides, base_extruder_through_scopes); if (! volume.material_id().empty()) - apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides); + apply_to_print_region_config(config, volume.material()->config.get(), feature_overrides, base_extruder_through_scopes); if (layer_range_config != nullptr) { // Not applicable to modifiers. assert(volume.is_model_part()); - apply_to_print_region_config(config, *layer_range_config, feature_overrides); + apply_to_print_region_config(config, *layer_range_config, feature_overrides, base_extruder_through_scopes); } // Resolve feature defaults and clamp invalid extruders to index 1. clamp_feature_filament_to_valid(config.sparse_infill_filament_id, num_extruders); @@ -3518,37 +3547,98 @@ unsigned int PrintObject::layer_height_multiplier_for_filament(unsigned int fila return (unsigned int)multiplier; } -unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®ion) const +// Inner-wall loops can also come from the alternating extra wall or from extra perimeters on +// overhangs, even at a single wall loop (mirrors PerimeterGenerator's loop count). +bool PrintObject::region_prints_inner_walls(const PrintRegionConfig &config) { - const PrintRegionConfig &config = region.config(); - // The wall filaments define a region's layer pitch (sparse infill preferring taller layers is combined separately in combine_infill()); wall-less regions need agreement of all their feature filaments. - // Disagreeing filaments keep the object layer height (Print::validate() warns). - std::vector filaments; // 0-based filament indices + return config.wall_loops.value > 1 || + (config.alternate_extra_wall.value && config.sparse_infill_density.value > 0) || + config.extra_perimeters_on_overhangs.value; +} + +// Collects the 0-based filaments whose explicit preferred layer heights define the region's +// pitch: the wall filaments when any of them carries a preference, otherwise the other +// pitch-bound feature filaments (top / bottom / internal solid; sparse infill always combines +// separately in combine_infill(), including at 100% density). pitch_from_features tells the +// caller which case applies. Returns false when a mixed (virtual) filament forbids combining +// altogether: mixed rows rotate physical extruders per layer and need every layer. +bool PrintObject::collect_region_pitch_filaments(const PrintRegionConfig &config, std::vector &filaments, bool &pitch_from_features) const +{ + pitch_from_features = false; + const int num_filaments = (int)m_print->config().filament_diameter.size(); + bool has_mixed = false; + auto emplace_filament = [num_filaments, &filaments, &has_mixed](int filament_id) { + int i = std::max(0, filament_id - 1); + if (i >= num_filaments) { + // Snapmaker mixed filament: virtual mixed ids resolve to a different physical + // extruder per layer (matches extruder_preferred_layer_height()). + has_mixed = true; + return; + } + filaments.emplace_back(i); + }; + auto emplace_feature_filaments = [&]() { + if (config.top_shell_layers.value > 0) + emplace_filament(config.top_surface_filament_id.value); + if (config.bottom_shell_layers.value > 0) + emplace_filament(config.bottom_surface_filament_id.value); + if ((config.sparse_infill_density.value > 0 || config.top_shell_layers.value > 0 || config.bottom_shell_layers.value > 0) && + std::abs(config.sparse_infill_density.value - 100.) >= EPSILON) + emplace_filament(config.internal_solid_filament_id.value); + }; if (config.wall_loops.value > 0) { - const int num_filaments = (int)m_print->config().filament_diameter.size(); - bool has_mixed_wall = false; - auto emplace_filament = [num_filaments, &filaments, &has_mixed_wall](int filament_id) { - int i = std::max(0, filament_id - 1); - if (i >= num_filaments) { - // Snapmaker mixed filament: virtual mixed ids resolve to a different physical - // extruder per layer, so such walls need every layer and must never combine - // (matches Print::validate() and extruder_preferred_layer_height()). - has_mixed_wall = true; - return; - } - filaments.emplace_back(i); - }; emplace_filament(config.outer_wall_filament_id.value); - if (config.wall_loops.value > 1) + if (region_prints_inner_walls(config)) emplace_filament(config.inner_wall_filament_id.value); - if (has_mixed_wall) - return 1; - } else - // The brim is not considered, it prints on the first layer which is never combined. - PrintRegion::collect_object_printing_extruders(m_print->config(), config, false /* has_brim */, filaments); + if (has_mixed) + return false; + const bool walls_explicit = std::any_of(filaments.begin(), filaments.end(), + [this](unsigned int f) { return this->extruder_preferred_layer_height(f + 1) > 0.; }); + if (! walls_explicit) { + // Walls without an explicit preference follow the agreement of the part's other + // pitch-bound features, so assigning e.g. the top surface to a coarse extruder + // takes effect even when the walls stay on the part's filament. + pitch_from_features = true; + emplace_feature_filaments(); + } else { + // Even a walls-driven pitch prints the region's other features on the group tops: a + // mixed (virtual) feature filament needs every layer and forbids combining, whether + // or not it participates in the lattice. + const size_t lattice_size = filaments.size(); + emplace_feature_filaments(); + filaments.resize(lattice_size); + if (has_mixed) + return false; + } + } else { + // No walls to dictate a pitch: the other pitch-bound features offer one. The brim is not + // considered, it prints on the first layer which is never combined. + pitch_from_features = true; + emplace_feature_filaments(); + } + return ! has_mixed; +} + +unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®ion) const +{ + const PrintRegionConfig &config = region.config(); + // Filaments whose explicit preferences define the pitch; disagreeing explicit preferences + // keep the object layer height (Print::validate() warns). + std::vector filaments; // 0-based filament indices + bool pitch_from_features = false; + if (! this->collect_region_pitch_filaments(config, filaments, pitch_from_features)) + return 1; unsigned int multiplier = 0; for (unsigned int filament : filaments) { + // A filament without a preference (0 = object layer height) follows the pitch of the + // filaments that have one instead of vetoing it; only explicit preferences must agree. + if (this->extruder_preferred_layer_height(filament + 1) <= 0.) + continue; unsigned int m = this->layer_height_multiplier_for_filament(filament + 1); + if (pitch_from_features && m <= 1) + // Feature filaments only OFFER a pitch: a non-conforming preference is skipped here + // (Print::validate() warns) instead of blocking the remaining features' agreement. + continue; if (multiplier == 0) multiplier = m; else if (m != multiplier) @@ -3556,24 +3646,88 @@ unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®i } if (multiplier <= 1) return 1; - // A feature filament whose nozzle or layer height limits cannot follow the walls' pitch makes the region fall back to the object layer height (Print::validate() warns about it). + // Only a nozzle that physically cannot extrude the pitch vetoes it; the softer per-extruder + // min/max layer height limits keep the pitch and Print::validate() warns instead - with stock + // profiles the fine extruder's max_layer_height would otherwise veto almost any thick pitch. const PrintConfig &print_config = m_print->config(); const double pitch = multiplier * m_config.layer_height.value; std::vector used_filaments; // 0-based filament indices PrintRegion::collect_object_printing_extruders(print_config, config, false /* has_brim */, used_filaments); + // At 100% density the sparse infill filament prints nothing (the solid interior belongs to + // the internal solid filament) - unless it also prints another feature, its nozzle must not + // veto the pitch (mirrors Print::validate()'s prints_nothing()). + auto selector0 = [](int filament_id) { return (unsigned int)std::max(1, filament_id) - 1; }; + auto prints_nothing = [&](unsigned int filament) { + return std::abs(config.sparse_infill_density.value - 100.) < EPSILON && + filament == selector0(config.sparse_infill_filament_id.value) && + filament != selector0(config.internal_solid_filament_id.value) && + filament != selector0(config.top_surface_filament_id.value) && + filament != selector0(config.bottom_surface_filament_id.value) && + filament != selector0(config.outer_wall_filament_id.value) && + filament != selector0(config.inner_wall_filament_id.value); + }; for (unsigned int filament : used_filaments) { + if (prints_nothing(filament)) + continue; // The filament index is the extruder index on classic multi-tool printers. const size_t extruder_idx = size_t(filament); if (pitch > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) return 1; - if (const double max_lh = print_config.max_layer_height.get_at(extruder_idx); max_lh > EPSILON && pitch > max_lh + EPSILON) - return 1; - if (const double min_lh = print_config.min_layer_height.get_at(extruder_idx); min_lh > EPSILON && pitch < min_lh - EPSILON) - return 1; } return multiplier; } +// When the region cannot follow its wall filaments' preferred pitch as a whole (vetoed in +// region_layer_height_multiplier()), the walls still combine on their own: they extrude once per +// group of N layers while the other features keep printing every layer. Only the wall filaments' +// nozzles matter here. +unsigned int PrintObject::wall_layer_height_multiplier(const PrintRegion ®ion) const +{ + const PrintRegionConfig &config = region.config(); + if (config.wall_loops.value <= 0) + return 1; + if (this->region_layer_height_multiplier(region) > 1) + // The whole region follows the pitch, walls included. + return 1; + const int num_filaments = (int)m_print->config().filament_diameter.size(); + const bool prints_inner_walls = region_prints_inner_walls(config); + unsigned int multiplier = 0; + auto merge_wall_filament = [this, num_filaments, &multiplier](int filament_id) -> bool { + if (std::max(0, filament_id - 1) >= num_filaments) + // Snapmaker mixed filament: virtual mixed ids resolve to a different physical + // extruder per layer, no stable pitch exists for them. + return false; + if (this->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)) <= 0.) + // No explicit preference: follows the other wall filament's pitch. + return true; + const unsigned int m = this->layer_height_multiplier_for_filament((unsigned int)std::max(0, filament_id)); + // Outer and inner walls print together at one height: disagreeing preferences meet at the + // LOWER one (Print::validate() reports the unhonored higher preference). + multiplier = multiplier == 0 ? m : std::min(multiplier, m); + return true; + }; + if (! merge_wall_filament(config.outer_wall_filament_id.value)) + return 1; + if (prints_inner_walls && ! merge_wall_filament(config.inner_wall_filament_id.value)) + return 1; + if (multiplier <= 1) + return 1; + // Every wall filament must fit the pitch through its own bore, including preference-less + // ones following the pitch - they print these very walls. + const PrintConfig &print_config = m_print->config(); + const double pitch = multiplier * m_config.layer_height.value; + auto nozzle_too_small = [&](int filament_id) { + // The filament index is the extruder index on classic multi-tool printers. + const size_t extruder_idx = size_t((unsigned int)std::max(1, filament_id) - 1); + return pitch > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON; + }; + if (nozzle_too_small(config.outer_wall_filament_id.value)) + return 1; + if (prints_inner_walls && nozzle_too_small(config.inner_wall_filament_id.value)) + return 1; + return multiplier; +} + bool PrintObject::has_combined_layer_regions() const { for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) @@ -4533,6 +4687,96 @@ void PrintObject::discover_horizontal_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // void PrintObject::discover_horizontal_shells() +// Per-extruder layer height: print top surfaces with the preferred layer height of their filament +// by absorbing the internal solid shell layers right below them. The top surface then extrudes +// once at N times the object layer height - replacing itself and N-1 solid layers underneath - +// while everything else keeps printing every layer. Only areas backed by plain internal solid +// infill through the whole group combine (no bridge, sparse infill or another surface may hide +// inside the group); the rest keeps the object layer height. Like combine_infill(), the removed +// areas stay behind as VOID surfaces to preserve the fill boundaries. +void PrintObject::combine_top_surfaces() +{ + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = this->printing_region(region_id); + const PrintRegionConfig &config = region.config(); + if (config.top_shell_layers.value <= 0) + continue; + // A reduced-density (textured) top surface relies on the solid layers below it; absorbing + // them would replace solid backing with a sparse pass. Combine full-density tops only. + if (config.top_surface_density.value < 100. - EPSILON) + continue; + // Only for regions printing at the object layer height: combined regions already extrude + // everything - top surfaces included - once per group over their group tops. + if (this->region_layer_height_multiplier(region) > 1) + continue; + const unsigned int mult = this->layer_height_multiplier_for_filament( + (unsigned int)std::max(0, config.top_surface_filament_id.value)); + if (mult <= 1) + continue; + // Top-down, so the uppermost (most visible) top surfaces absorb first where columns are + // less than a group apart. + for (size_t layer_idx = m_layers.size(); layer_idx-- > 0; ) { + m_print->throw_if_canceled(); + if (layer_idx + 1 < size_t(mult)) + break; + // Never absorb the first print layer: it keeps its own height for bed adhesion. + if (m_layers[layer_idx - mult + 1]->id() == 0) + continue; + LayerRegion *top_layerm = m_layers[layer_idx]->regions()[region_id]; + ExPolygons combined = to_expolygons(top_layerm->fill_surfaces.filter_by_type(stTop)); + if (combined.empty()) + continue; + // Uniform layer heights only (mirrors apply_extruder_layer_heights()). + bool uniform = true; + for (size_t i = layer_idx - mult + 1; uniform && i < layer_idx; ++ i) + uniform = std::abs(m_layers[i]->height - m_layers[layer_idx]->height) < EPSILON; + if (! uniform) + continue; + std::vector absorbed; // the mult-1 layers right below, bottom-up + for (size_t i = layer_idx - mult + 1; i < layer_idx; ++ i) + absorbed.emplace_back(m_layers[i]->regions()[region_id]); + for (LayerRegion *layerm : absorbed) { + combined = intersection_ex(layerm->fill_surfaces.filter_by_type(stInternalSolid), combined); + if (combined.empty()) + break; + } + if (double area_threshold = top_layerm->infill_area_threshold(); ! combined.empty() && area_threshold > 0.) + combined.erase(std::remove_if(combined.begin(), combined.end(), + [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), + combined.end()); + if (combined.empty()) + continue; + // Clearance against the absorbed layers' remaining solid infill, which is grown later + // to overlap perimeters (mirrors combine_infill()'s clearance). + Polygons combined_with_clearance; + combined_with_clearance.reserve(combined.size()); + const float clearance_offset = 0.5f * top_layerm->flow(frPerimeter).scaled_width() + + 1.5f * top_layerm->flow(frSolidInfill).scaled_width(); + for (const ExPolygon &expoly : combined) + polygons_append(combined_with_clearance, offset(expoly, clearance_offset)); + for (LayerRegion *layerm : absorbed) { + Polygons internal = to_polygons(std::move(layerm->fill_surfaces.filter_by_type(stInternalSolid))); + layerm->fill_surfaces.remove_type(stInternalSolid); + layerm->fill_surfaces.append(diff_ex(internal, combined_with_clearance), stInternalSolid); + layerm->fill_surfaces.append(intersection_ex(internal, combined_with_clearance), stInternalVoid); + } + { + Polygons top_polys = to_polygons(std::move(top_layerm->fill_surfaces.filter_by_type(stTop))); + top_layerm->fill_surfaces.remove_type(stTop); + top_layerm->fill_surfaces.append(diff_ex(top_polys, to_polygons(combined)), stTop); + // The absorbed areas extrude once with the whole group's thickness (Fill resolves + // the flow from Surface::thickness). + Surface templ(stTop, ExPolygon()); + templ.thickness = 0.; + for (size_t i = layer_idx - mult + 1; i <= layer_idx; ++ i) + templ.thickness += m_layers[i]->height; + templ.thickness_layers = (unsigned short)mult; + top_layerm->fill_surfaces.append(std::move(combined), templ); + } + } + } +} + // combine fill surfaces across layers to honor the "infill every N layers" option // Idempotence of this method is guaranteed by the fact that we don't remove things from // fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. @@ -4548,10 +4792,11 @@ void PrintObject::combine_infill() if (this->region_layer_height_multiplier(region) > 1) continue; // At 100% density the combined surfaces are internal solid infill (see use_solid_infill - // below), but this fork prints them with the sparse infill filament - // (internal_solid_infill_uses_sparse_filament in PrintRegion::extruder()), so the sparse - // filament's preference and limits decide in both cases. - const int combine_filament_id = std::max(1, region.config().sparse_infill_filament_id.value); + // below) and print with the internal solid filament (PrintRegion::extruder()), so that + // filament's preference and limits decide there; the sparse filament's below 100%. + const bool combine_solid = std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON; + const int combine_filament_id = std::max(1, combine_solid ? region.config().internal_solid_filament_id.value : + region.config().sparse_infill_filament_id.value); // Per-extruder vector index: the filament index is the extruder index on classic multi-tool printers (like extruder_preferred_layer_height()). const size_t combine_extruder_idx = size_t(combine_filament_id - 1); double preferred_infill_height = this->extruder_preferred_layer_height((unsigned int)combine_filament_id); @@ -4561,7 +4806,7 @@ void PrintObject::combine_infill() continue; // Support internal solid infill when sparse_infill_density is 100% - const bool use_solid_infill = fabs(region.config().sparse_infill_density.value - 100.) < EPSILON; + const bool use_solid_infill = combine_solid; const SurfaceType surface_type = use_solid_infill ? stInternalSolid : stInternal; const InfillPattern infill_pattern = use_solid_infill ? region.config().internal_solid_infill_pattern : region.config().sparse_infill_pattern; @@ -4577,16 +4822,12 @@ void PrintObject::combine_infill() const double infill_combination_max_layer_height = region.config().infill_combination_max_layer_height.get_abs_value(nozzle_diameter); nozzle_diameter = infill_combination_max_layer_height > 0 ? std::min(infill_combination_max_layer_height, nozzle_diameter) : nozzle_diameter; - // Per-extruder layer height: the filament's preferred height is an explicit target that overrides the caps above, limited only by the physical nozzle diameters and the extruder's max layer height. - // Plain infill_combination keeps its historical cap untouched. - if (preferred_infill_height > 0.) { - nozzle_diameter = std::min({preferred_infill_height, - this->print()->config().nozzle_diameter.get_at(region.config().sparse_infill_filament_id.value - 1), - this->print()->config().nozzle_diameter.get_at(region.config().internal_solid_filament_id.value - 1)}); - const double combine_max_layer_height = this->print()->config().max_layer_height.get_at(combine_extruder_idx); - if (combine_max_layer_height > EPSILON) - nozzle_diameter = std::min(nozzle_diameter, combine_max_layer_height); - } + // Per-extruder layer height: the preferred height is an explicit target overriding the + // caps above, limited only by the bore of the nozzle extruding it (max_layer_height is a + // soft limit, Print::validate() warns). Plain infill_combination keeps its own cap. + if (preferred_infill_height > 0.) + nozzle_diameter = std::min(preferred_infill_height, + this->print()->config().nozzle_diameter.get_at(combine_extruder_idx)); // define the combinations std::vector combine(m_layers.size(), 0); diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 686916bae39..a39125ec27e 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -888,10 +888,15 @@ void PrintObject::apply_extruder_layer_heights() if (m_layers.size() < 2 || this->num_printing_regions() == 0) return; std::vector multipliers(this->num_printing_regions(), 1); + // Walls-only pitch (see wall_layer_height_multiplier()): the region prints every layer, only + // its walls combine. Mutually exclusive with a whole-region multiplier > 1. + std::vector wall_multipliers(this->num_printing_regions(), 1); bool any_combined = false; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { multipliers[region_id] = this->region_layer_height_multiplier(this->printing_region(region_id)); - any_combined |= multipliers[region_id] > 1; + if (multipliers[region_id] <= 1) + wall_multipliers[region_id] = this->wall_layer_height_multiplier(this->printing_region(region_id)); + any_combined |= multipliers[region_id] > 1 || wall_multipliers[region_id] > 1; } if (! any_combined) return; @@ -918,7 +923,10 @@ void PrintObject::apply_extruder_layer_heights() return size_t((unsigned int)std::max(1, filament_id) - 1); }; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { - const size_t mult = multipliers[region_id]; + // Walls-only mode marks the runs on the LayerRegions for make_perimeters() instead of + // moving any slices: every layer keeps its geometry, fills and surfaces. + const bool walls_only = wall_multipliers[region_id] > 1; + const size_t mult = walls_only ? wall_multipliers[region_id] : multipliers[region_id]; if (mult <= 1) continue; // Shapes deviating by less than this fraction of the region's nozzle diameter are considered @@ -928,13 +936,24 @@ void PrintObject::apply_extruder_layer_heights() const float tolerance = float(scale_(m_config.extruder_layer_height_tolerance.get_abs_value(nozzle_diameter))); // Where geometry would fall back below the extruders' minimum layer height, runs of at least // min_run layers are forced instead, ignoring the shape tolerance (the nozzle cannot print - // finer). Both wall filaments print fallback runs, so the coarser of their minimums decides. + // finer). All the region's pitch filaments print fallback runs, so the coarsest minimum decides + // (with a feature-derived pitch the coarse feature filament matters, not just the walls). const PrintRegionConfig ®ion_config = this->printing_region(region_id).config(); - double min_layer_height = print_config.min_layer_height.get_at( - extruder_idx_of(region_config.outer_wall_filament_id.value)); - if (region_config.wall_loops.value > 1) - min_layer_height = std::max(min_layer_height, print_config.min_layer_height.get_at( - extruder_idx_of(region_config.inner_wall_filament_id.value))); + double min_layer_height = 0.; + { + std::vector pitch_filaments; // 0-based filament indices + if (walls_only) { + // Only the wall filaments print the combined runs here. + pitch_filaments.emplace_back((unsigned int)extruder_idx_of(region_config.outer_wall_filament_id.value)); + if (region_prints_inner_walls(region_config)) + pitch_filaments.emplace_back((unsigned int)extruder_idx_of(region_config.inner_wall_filament_id.value)); + } else { + bool pitch_from_features = false; + this->collect_region_pitch_filaments(region_config, pitch_filaments, pitch_from_features); + } + for (unsigned int filament : pitch_filaments) + min_layer_height = std::max(min_layer_height, print_config.min_layer_height.get_at(size_t(filament))); + } size_t min_run = 1; if (min_layer_height > m_config.layer_height.value + EPSILON) min_run = std::min(mult, (size_t)std::ceil(min_layer_height / m_config.layer_height.value - EPSILON)); @@ -1037,10 +1056,24 @@ void PrintObject::apply_extruder_layer_heights() ++ idx; continue; } - // Commit: move the common shape to the top layer of the run, drop the layers below. double combined_height = 0.; for (size_t i = idx; i <= commit_top; ++ i) combined_height += m_layers[i]->height; + if (walls_only) { + // Commit: mark the run for LayerRegion::make_perimeters(). The run's top layer + // extrudes all its walls at once at the full run height; the layers below keep + // their slices, fills and surfaces but drop their wall extrusions (count 0). All + // run layers carry the run height so their perimeters are generated with the same + // flow and the fill boundaries line up with the walls actually printed at the top. + for (size_t i = idx; i <= commit_top; ++ i) { + LayerRegion *layerm = m_layers[i]->regions()[region_id]; + layerm->m_wall_combined_count = i == commit_top ? (unsigned short)(commit_top - idx + 1) : 0; + layerm->m_wall_combined_height = combined_height; + } + idx = commit_top + 1; + continue; + } + // Commit: move the common shape to the top layer of the run, drop the layers below. LayerRegion *top_layerm = m_layers[commit_top]->regions()[region_id]; top_layerm->slices.set(std::move(merged), stInternal); top_layerm->m_combined_layer_count = (unsigned short)(commit_top - idx + 1); diff --git a/src/libslic3r/PrintRegion.cpp b/src/libslic3r/PrintRegion.cpp index a6d842ed74c..ddb4b450320 100644 --- a/src/libslic3r/PrintRegion.cpp +++ b/src/libslic3r/PrintRegion.cpp @@ -5,15 +5,6 @@ namespace Slic3r { -namespace { - -bool internal_solid_infill_uses_sparse_filament(const PrintRegionConfig &config, FlowRole role) -{ - return role == frSolidInfill && std::abs(config.sparse_infill_density.value - 100.) < EPSILON; -} - -} // namespace - // 1-based extruder identifier for this region and role. unsigned int PrintRegion::extruder(FlowRole role) const { @@ -25,7 +16,10 @@ unsigned int PrintRegion::extruder(FlowRole role) const else if (role == frInfill) extruder = m_config.sparse_infill_filament_id; else if (role == frSolidInfill) - extruder = internal_solid_infill_uses_sparse_filament(m_config, role) ? m_config.sparse_infill_filament_id : m_config.internal_solid_filament_id; + // The internal solid filament owns internal solid infill at every density, including the + // solid interior at 100% sparse density (matches mainline Orca; this fork used to hand + // the 100% interior to the sparse filament, hiding the internal solid selector entirely). + extruder = m_config.internal_solid_filament_id; else if (role == frTopSolidInfill) extruder = m_config.top_surface_filament_id; else diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index 3c4943628a0..69c479f66c1 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -9399,39 +9399,61 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c for (const PrintInstance &instance : *ctxt.shifted_copies) { const Point © = instance.shift; for (const LayerRegion *layerm : layer->regions()) { + // ORCA: per-feature filaments. Attribute every feature to the filament that + // actually prints it (must match LayerTools::extruder() and Fill.cpp). + const auto ®ion_config = layerm->region().config(); + const int filament_outer_wall = int(layerm->extruder(frExternalPerimeter)); + const int filament_inner_wall = int(layerm->extruder(frPerimeter)); + const int filament_sparse_infill = int(layerm->extruder(frInfill)); + const int filament_solid_infill = int(layerm->extruder(frSolidInfill)); + const int filament_top_surface = int(layerm->extruder(frTopSolidInfill)); + const int filament_bottom_surface = int(effective_layer_filament_id(*layer, + (unsigned int)std::max(0, region_config.bottom_surface_filament_id.value))); + auto fill_filament = [&](const ExtrusionEntityCollection &fill) -> int { + ExtrusionRole role = fill.entities.empty() ? erNone : fill.entities.front()->role(); + // gap fill inherits the filament of the surface it fills (matches ToolOrdering::collect_extruders()). + if (role == erGapFill) + for (const ExtrusionEntity *fill_entity : fill.entities) + if (fill_entity->role() != erGapFill) { + role = fill_entity->role(); + break; + } + if (role == erTopSolidInfill || role == erIroning) + return filament_top_surface; + if (role == erBottomSurface || role == erBridgeInfill) // external bridges print as bottom surfaces + return filament_bottom_surface; + if (role == erGapFill) + // perimeter-generated gap fill with no sibling surface prints with the outer wall filament. + return filament_outer_wall; + return is_solid_infill(role) ? filament_solid_infill : filament_sparse_infill; + }; if (is_selected_separate_extruder) { - const int effective_wall_filament = int(layerm->extruder(frPerimeter)); - const int effective_sparse_infill_filament = int(layerm->extruder(frInfill)); - const int effective_solid_infill_filament = int(layerm->extruder(frSolidInfill)); - if (effective_wall_filament != m_selected_extruder && - effective_sparse_infill_filament != m_selected_extruder && - effective_solid_infill_filament != m_selected_extruder) + if (filament_outer_wall != m_selected_extruder && + filament_inner_wall != m_selected_extruder && + filament_sparse_infill != m_selected_extruder && + filament_solid_infill != m_selected_extruder && + filament_top_surface != m_selected_extruder && + filament_bottom_surface != m_selected_extruder) continue; } if (ctxt.has_perimeters) { - const int effective_wall_filament = int(layerm->extruder(frPerimeter)); - _3DScene::extrusionentity_to_verts(layerm->perimeters, float(layer->print_z), copy, - select_geometry(idx_layer, effective_wall_filament, 0)); + // Outer and inner walls may print with different filaments; classify each + // entity the way the wall filament dispatch does. + for (const ExtrusionEntity *ee : layerm->perimeters.entities) + _3DScene::extrusionentity_to_verts(ee, float(layer->print_z), copy, + select_geometry(idx_layer, + perimeter_entity_uses_outer_wall_filament(*ee) ? + filament_outer_wall : filament_inner_wall, + 0)); } if (ctxt.has_infill) { for (const ExtrusionEntity *ee : layerm->fills.entities) { // fill represents infill extrusions of a single island. const auto *fill = dynamic_cast(ee); if (! fill->entities.empty()) - { - const int effective_sparse_infill_filament = int(layerm->extruder(frInfill)); - const int effective_solid_infill_filament = int(layerm->extruder(frSolidInfill)); _3DScene::extrusionentity_to_verts(*fill, float(layer->print_z), copy, - select_geometry(idx_layer, - (fill->entities.front()->role() == erSolidInfill && - std::abs(layerm->region().config().sparse_infill_density.value - 100.) < EPSILON) ? - effective_solid_infill_filament : - (is_solid_infill(fill->entities.front()->role()) ? - effective_solid_infill_filament : - effective_sparse_infill_filament), - 1)); - } + select_geometry(idx_layer, fill_filament(*fill), 1)); } } } diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index be251b4a450..c085d6cddf1 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -955,6 +955,16 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz extruder = wxString::Format("%d", new_extruder); layer_range_item.second.set("extruder", new_extruder); } + // Height ranges carry the per-feature selectors too (the engine reads them from + // layer_config_ranges); remap them like the object / volume configs above. + for (auto key : keys) { + if (layer_range_item.second.has(key)) { + if (layer_range_item.second.option(key)->getInt() == int(filament_id) + 1) + layer_range_item.second.erase(key); + else if (layer_range_item.second.option(key)->getInt() > int(filament_id)) + layer_range_item.second.set(key, layer_range_item.second.option(key)->getInt() - 1); + } + } m_objects_model->SetExtruder(extruder, layer_item); } } diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1170745f642..35efc7fe5eb 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1017,7 +1017,9 @@ struct Sidebar::priv // nozzle notebook and related controls CustomNotebook* m_nozzle_notebook{nullptr}; std::vector m_nozzle_diameter_lists; + std::vector m_nozzle_layer_height_lists; std::vector m_nozzle_edit_btns; + bool m_nozzle_rebuild_scheduled{false}; ObjectList *m_object_list{ nullptr }; ObjectSettings *object_settings{ nullptr }; @@ -2186,7 +2188,8 @@ Sidebar::Sidebar(Plater *parent) wxBoxSizer* nozzle_sizer = new wxBoxSizer(wxVERTICAL); nozzle_sizer->Add(p->m_nozzle_notebook, 1, wxEXPAND | wxALL, FromDIP(0)); nozzle_container->SetSizer(nozzle_sizer); - nozzle_container->SetMinSize(wxSize(-1, FromDIP(80))); + // Tall enough for the tab strip plus the Diameter and Preferred layer height rows. + nozzle_container->SetMinSize(wxSize(-1, FromDIP(112))); // 添加到主布局 vsizer_printer->Add(nozzle_container, 0, wxEXPAND | wxALL, FromDIP(4)); @@ -8255,6 +8258,95 @@ void Sidebar::update_dynamic_filament_list() dynamic_filament_list.update(); } +// ORCA multi-nozzle-size: label of a nozzle diameter / layer height combo item. Formatted with +// the C locale (period decimal) to match the other "x.xmm" items and the ToCDouble parsing of +// the selection handlers. +static wxString nozzle_combo_label(double value) +{ + std::ostringstream oss; + oss.imbue(std::locale::classic()); + oss << value; + return wxString(oss.str()) + "mm"; +} + +// Show THIS nozzle's own diameter (per-nozzle sizes are supported), selecting the matching +// "x.xmm" item so the read-only combo accepts it. Falls back to the uniform printer_variant +// label only when the per-nozzle value is unavailable. +static void select_nozzle_diameter_label(ComboBox *diameter_combo, double this_nd) +{ + wxString this_label; + for (unsigned int n = 0; n < diameter_combo->GetCount(); ++n) { + wxString item = diameter_combo->GetString(n); + wxString num = item; + if (num.EndsWith("mm")) + num.RemoveLast(2); + double item_nd = 0.; + if (num.ToCDouble(&item_nd) && std::abs(item_nd - this_nd) < EPSILON) { + this_label = item; + break; + } + } + if (this_label.empty() && this_nd > 0.) { + // A per-nozzle diameter not among the printer's variant list (e.g. an imported config): + // add it so the combo can display the true value. + this_label = nozzle_combo_label(this_nd); + diameter_combo->AppendString(this_label); + } + if (this_label.empty()) { + const auto *pv = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_variant"); + this_label = (pv ? wxString(pv->value) : wxString()) + "mm"; + } + diameter_combo->SetValue(this_label); +} + +// ORCA multi-nozzle-size: (re)fill one sidebar "Preferred layer height" combo with the target +// heights extruder `extruder_idx` may use: "Default" (= 0, follow the object layer height) plus +// every integer multiple of the object layer height that fits through the nozzle bore and lies +// within the extruder's layer height limits. +static void fill_nozzle_layer_height_combo(ComboBox *combo, size_t extruder_idx) +{ + const DynamicPrintConfig &printer_config = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + + const auto *nozzle_diameter = printer_config.option("nozzle_diameter"); + const auto *preferred = printer_config.option("extruder_layer_height"); + const auto *min_lh = printer_config.option("min_layer_height"); + const auto *max_lh = printer_config.option("max_layer_height"); + const auto *base_opt = print_config.option("layer_height"); + const double base_height = base_opt != nullptr ? base_opt->value : 0.; + + combo->Clear(); + combo->AppendString(_L("Default")); + + const double bore = (nozzle_diameter != nullptr && extruder_idx < nozzle_diameter->values.size()) ? + nozzle_diameter->values[extruder_idx] : 0.; + double cap = bore; + if (max_lh != nullptr && !max_lh->values.empty() && max_lh->get_at(extruder_idx) > EPSILON) + cap = std::min(cap, max_lh->get_at(extruder_idx)); + const double floor_lh = (min_lh != nullptr && !min_lh->values.empty()) ? min_lh->get_at(extruder_idx) : 0.; + const double current = (preferred != nullptr && !preferred->values.empty()) ? + std::max(0., preferred->get_at(extruder_idx)) : 0.; + + wxString current_label; + if (base_height > EPSILON) + for (int n = 1; n * base_height <= cap + EPSILON; ++n) { + const double height = n * base_height; + if (height + EPSILON < floor_lh) + continue; + const wxString label = nozzle_combo_label(height); + combo->AppendString(label); + if (current > 0. && std::abs(height - current) < EPSILON) + current_label = label; + } + if (current_label.empty() && current > 0.) { + // An explicit preference no longer among the valid values (the object layer height or + // the limits changed after it was set) is still displayed truthfully; slicing warns. + current_label = nozzle_combo_label(current); + combo->AppendString(current_label); + } + combo->SetValue(current_label.empty() ? _L("Default") : current_label); +} + void Sidebar::update_nozzle_settings(bool switch_machine) { if (!p->m_nozzle_notebook) @@ -8265,9 +8357,11 @@ void Sidebar::update_nozzle_settings(bool switch_machine) wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")); size_t new_nozzle_count = nozzle_diameter ? nozzle_diameter->values.size() : 1; - // Clear existing pages and controls + // Clear existing pages and controls, keeping the selected tab across the rebuild. + const int prev_page = p->m_nozzle_notebook->GetSelection(); p->m_nozzle_notebook->DeleteAllPages(); p->m_nozzle_diameter_lists.clear(); + p->m_nozzle_layer_height_lists.clear(); p->m_nozzle_edit_btns.clear(); // Recreate pages for new nozzle count @@ -8277,7 +8371,7 @@ void Sidebar::update_nozzle_settings(bool switch_machine) wxTAB_TRAVERSAL | wxBORDER_NONE); // nozzle_panel->SetBackgroundColour(wxColour(255, 255, 255)); - wxBoxSizer* tab_sizer = new wxBoxSizer(wxHORIZONTAL); + wxBoxSizer* tab_sizer = new wxBoxSizer(wxVERTICAL); // Add diameter label and combobox wxBoxSizer* diameter_sizer = new wxBoxSizer(wxHORIZONTAL); @@ -8340,44 +8434,70 @@ void Sidebar::update_nozzle_settings(bool switch_machine) nozzle_diameters[i] = new_nd; new_conf.set_key_value("nozzle_diameter", new ConfigOptionFloats(nozzle_diameters)); + // ORCA multi-nozzle-size: a different nozzle size usually means different layer + // height limits. Adopt them from the printer's profile for the new nozzle size when + // one exists, otherwise ask the user to review the limits manually. + std::string notice; + bool variant_found = false; + { + const PrinterPresetCollection &printers = wxGetApp().preset_bundle->printers; + const std::string model = new_conf.opt_string("printer_model"); + const std::string variant = sel_num.ToStdString(); + const Preset *variant_preset = printers.find_system_preset_by_model_and_variant(model, variant); + if (variant_preset == nullptr) + variant_preset = printers.find_custom_preset_by_model_and_variant(model, variant); + const auto *v_min = variant_preset == nullptr ? nullptr : variant_preset->config.option("min_layer_height"); + const auto *v_max = variant_preset == nullptr ? nullptr : variant_preset->config.option("max_layer_height"); + const auto *e_min = static_cast(new_conf.option("min_layer_height")); + const auto *e_max = static_cast(new_conf.option("max_layer_height")); + if (v_min != nullptr && !v_min->values.empty() && v_max != nullptr && !v_max->values.empty() && + e_min != nullptr && e_max != nullptr) { + variant_found = true; + std::vector mins = e_min->values, maxs = e_max->values; + mins.resize(nozzle_diameters.size(), mins.empty() ? 0. : mins.back()); + maxs.resize(nozzle_diameters.size(), maxs.empty() ? 0. : maxs.back()); + mins[i] = v_min->get_at(i); + maxs[i] = v_max->get_at(i); + new_conf.set_key_value("min_layer_height", new ConfigOptionFloats(mins)); + new_conf.set_key_value("max_layer_height", new ConfigOptionFloats(maxs)); + notice = GUI::format(_u8L("Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"."), + i + 1, mins[i], maxs[i], variant_preset->name); + } else { + notice = GUI::format(_u8L("This printer has no profile for a %1% mm nozzle. Please review the " + "layer height limits of nozzle %2% in the printer settings."), + variant, i + 1); + } + } + // A preferred layer height that no longer fits through the new nozzle cannot print; + // reset it to Default rather than leave a dead setting behind. + if (const auto *height_opt = static_cast(new_conf.option("extruder_layer_height")); + height_opt != nullptr && !height_opt->values.empty() && height_opt->get_at(i) > new_nd + EPSILON) { + std::vector heights = height_opt->values; + heights.resize(nozzle_diameters.size(), 0.); + heights[i] = 0.; + new_conf.set_key_value("extruder_layer_height", new ConfigOptionFloats(heights)); + notice += "\n"; + notice += GUI::format(_u8L("The preferred layer height of nozzle %1% no longer fits through it and was reset to Default."), i + 1); + } + // load_config marks the printer preset modified and propagates the change without // rebuilding these combos or switching presets, so the other nozzles keep their sizes. printer_tab->load_config(new_conf); + + wxGetApp().plater()->get_notification_manager()->push_notification( + NotificationType::CustomNotification, + variant_found ? NotificationManager::NotificationLevel::RegularNotificationLevel : + NotificationManager::NotificationLevel::WarningNotificationLevel, + notice); + // The valid preferred layer heights of this nozzle follow its bore and limits. + if (i < p->m_nozzle_layer_height_lists.size() && p->m_nozzle_layer_height_lists[i] != nullptr) + fill_nozzle_layer_height_combo(p->m_nozzle_layer_height_lists[i], i); // Do not event.Skip(): this is a plain ComboBox; skipping would let the sidebar treat // it as the bed-type combo (Plater::priv::on_combobox_select) and mishandle it. }); - // Show THIS nozzle's own diameter (per-nozzle sizes are supported), selecting the matching - // "x.xmm" item so the read-only combo accepts it. Falls back to the uniform printer_variant - // label only when the per-nozzle value is unavailable. const double this_nd = (nozzle_diameter && i < nozzle_diameter->values.size()) ? nozzle_diameter->values[i] : 0.; - wxString this_label; - for (unsigned int n = 0; n < diameter_combo->GetCount(); ++n) { - wxString item = diameter_combo->GetString(n); - wxString num = item; - if (num.EndsWith("mm")) - num.RemoveLast(2); - double item_nd = 0.; - if (num.ToCDouble(&item_nd) && std::abs(item_nd - this_nd) < EPSILON) { - this_label = item; - break; - } - } - if (this_label.empty() && this_nd > 0.) { - // A per-nozzle diameter not among the printer's variant list (e.g. an imported config): - // add it so the combo can display the true value. Format with the C locale (period - // decimal) to match the other "x.xmm" items and the ToCDouble parsing above. - std::ostringstream oss; - oss.imbue(std::locale::classic()); - oss << this_nd; - this_label = wxString(oss.str()) + "mm"; - diameter_combo->AppendString(this_label); - } - if (this_label.empty()) { - const auto *pv = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("printer_variant"); - this_label = (pv ? wxString(pv->value) : wxString()) + "mm"; - } - diameter_combo->SetValue(this_label); + select_nozzle_diameter_label(diameter_combo, this_nd); p->m_nozzle_diameter_lists.push_back(diameter_combo); @@ -8386,9 +8506,58 @@ void Sidebar::update_nozzle_settings(bool switch_machine) diameter_sizer->AddSpacer(10); diameter_sizer->Add(diameter_combo, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(15)); + // Preferred layer height row: which multiple of the object layer height this extruder + // should print with (the "extruder_layer_height" printer option). + wxBoxSizer* lh_sizer = new wxBoxSizer(wxHORIZONTAL); + wxStaticText* lh_label = new wxStaticText(nozzle_panel, wxID_ANY, _L("Preferred layer height")); + lh_label->SetForegroundColour(is_dark ? wxColor(194, 194, 194) : wxColor(0, 0, 0)); + lh_label->SetFont(Label::Body_14); + + ComboBox* lh_combo = new ComboBox(nozzle_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, {-1, FromDIP(32)}, 0, + nullptr, wxCB_READONLY); + lh_combo->SetToolTip(_L("Layer height this extruder should print with: an integer multiple of the object " + "layer height within this extruder's layer height limits. Default keeps the object " + "layer height.")); + fill_nozzle_layer_height_combo(lh_combo, i); + + lh_combo->Bind(wxEVT_COMBOBOX, [lh_combo, i](wxCommandEvent& event) { + // "Default" (or anything non-numeric) clears the preference: 0 = object layer height. + wxString sel = lh_combo->GetValue(); + if (sel.EndsWith("mm")) + sel.RemoveLast(2); + double new_height = 0.; + if (!sel.ToCDouble(&new_height) || new_height < 0.) + new_height = 0.; + + Tab* printer_tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); + if (printer_tab == nullptr) + return; + DynamicPrintConfig new_conf = wxGetApp().preset_bundle->printers.get_edited_preset().config; + const auto* height_opt = static_cast(new_conf.option("extruder_layer_height")); + const auto* nd_opt = static_cast(new_conf.option("nozzle_diameter")); + if (height_opt == nullptr || nd_opt == nullptr) + return; + std::vector heights = height_opt->values; + heights.resize(nd_opt->values.size(), 0.); + if (i >= heights.size() || std::abs(heights[i] - new_height) < EPSILON) + return; + heights[i] = new_height; + new_conf.set_key_value("extruder_layer_height", new ConfigOptionFloats(heights)); + // As with the diameter combo: marks the printer preset modified and propagates the + // change without switching presets. Do not event.Skip() (plain ComboBox, see above). + printer_tab->load_config(new_conf); + }); + p->m_nozzle_layer_height_lists.push_back(lh_combo); + + lh_sizer->AddSpacer(15); + lh_sizer->Add(lh_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(5)); + lh_sizer->AddSpacer(10); + lh_sizer->Add(lh_combo, 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, FromDIP(15)); + // 删除Flow相关控件 - tab_sizer->Add(diameter_sizer, 1, wxEXPAND | wxALIGN_CENTER_VERTICAL); + tab_sizer->Add(diameter_sizer, 0, wxEXPAND | wxTOP, FromDIP(6)); + tab_sizer->Add(lh_sizer, 0, wxEXPAND | wxTOP | wxBOTTOM, FromDIP(6)); nozzle_panel->SetSizer(tab_sizer); @@ -8419,6 +8588,9 @@ void Sidebar::update_nozzle_settings(bool switch_machine) p->m_nozzle_notebook->AddPage(nozzle_panel, tab_name); } + if (prev_page > 0 && prev_page < (int) p->m_nozzle_notebook->GetPageCount()) + p->m_nozzle_notebook->SetSelection(size_t(prev_page)); + p->m_nozzle_notebook->Layout(); if (switch_machine) { @@ -8428,6 +8600,40 @@ void Sidebar::update_nozzle_settings(bool switch_machine) } } +// ORCA multi-nozzle-size: refresh the values shown by the nozzle tabs in place, without +// rebuilding them (no focus or tab-selection changes). Called whenever the printer's nozzle +// sizes, layer height limits or preferred layer heights change, or the object layer height +// changes (it defines which preferred layer heights are valid). A change of the extruder +// count escalates to a deferred full rebuild of the tabs. +void Sidebar::update_nozzle_values() +{ + if (p->m_nozzle_notebook == nullptr) + return; + + const auto *nozzle_diameter = dynamic_cast( + wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter")); + const size_t nozzle_count = nozzle_diameter != nullptr ? nozzle_diameter->values.size() : 1; + if (nozzle_count != p->m_nozzle_notebook->GetPageCount()) { + // Defer the rebuild: this can be reached from an event handler of a control that lives + // on one of the pages about to be destroyed. + if (!p->m_nozzle_rebuild_scheduled) { + p->m_nozzle_rebuild_scheduled = true; + wxGetApp().CallAfter([this]() { + p->m_nozzle_rebuild_scheduled = false; + update_nozzle_settings(); + }); + } + return; + } + if (nozzle_diameter != nullptr) + for (size_t i = 0; i < p->m_nozzle_diameter_lists.size() && i < nozzle_diameter->values.size(); ++i) + if (p->m_nozzle_diameter_lists[i] != nullptr) + select_nozzle_diameter_label(p->m_nozzle_diameter_lists[i], nozzle_diameter->values[i]); + for (size_t i = 0; i < p->m_nozzle_layer_height_lists.size(); ++i) + if (p->m_nozzle_layer_height_lists[i] != nullptr) + fill_nozzle_layer_height_combo(p->m_nozzle_layer_height_lists[i], i); +} + ObjectList* Sidebar::obj_list() { // BBS @@ -9397,7 +9603,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "enable_prime_tower", "wipe_tower_x", "wipe_tower_y", "prime_tower_width", "prime_tower_brim_width", "prime_volume", "extruder_colour", "filament_colour", "material_colour", "printable_height", "printer_model", "printer_technology", // These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor. - "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", + "layer_height", "initial_layer_print_height", "min_layer_height", "max_layer_height", "extruder_layer_height", "brim_width", "wall_loops", "outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_density", "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "top_shell_layers", "enable_support", "support_filament", "support_interface_filament", @@ -20510,7 +20716,9 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r // update global feature filament selections static const char* keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", - "support_filament", "support_interface_filament"}; + "support_filament", "support_interface_filament", + // 0 = "auto" for the wipe tower, the same deleted -> 0 / shift-down rule applies. + "wipe_tower_filament"}; for (auto key : keys) if (p->config->has(key)) { if (p->config->opt_int(key) == filament_id + 1) @@ -20520,6 +20728,29 @@ void Plater::on_filaments_delete(size_t num_filaments, size_t filament_id, int r (*(p->config)).set_key_value(key, new ConfigOptionInt(new_value)); } } + // The slicer reads these selections from the edited print preset (preset_bundle->full_config()), + // not from the plater's cached config above - remap it too, or a deleted filament's id silently + // shifts onto the wrong physical filament. + { + DynamicPrintConfig &print_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; + bool print_config_changed = false; + for (auto key : keys) + if (print_config.has(key)) { + const int value = print_config.opt_int(key); + if (value == filament_id + 1) { + // Back to "Default" (0): use the part's filament. + print_config.set_key_value(key, new ConfigOptionInt(0)); + print_config_changed = true; + } else if (value > filament_id + 1) { + print_config.set_key_value(key, new ConfigOptionInt(value - 1)); + print_config_changed = true; + } + } + if (print_config_changed) { + wxGetApp().get_tab(Preset::TYPE_PRINT)->update_dirty(); + wxGetApp().get_tab(Preset::TYPE_PRINT)->reload_config(); + } + } // update object/volume/support(object and volume) filament id sidebar().obj_list()->update_objects_list_filament_column_when_delete_filament(filament_id, num_filaments, replace_filament_id); @@ -20752,7 +20983,17 @@ bool Plater::check_filament_temp_mixing(int plate_index) // Collect from ModelVolume painting extruders for objects on the // current plate. Also track whether any object relies on the global - // default extruder (extruder=0) so we can resolve it at the end. + // default extruder (extruder=0) so we can resolve it at the end, and + // per feature selector whether any part still follows the global value. + static const std::vector selector_keys = { + "outer_wall_filament_id", + "inner_wall_filament_id", + "sparse_infill_filament_id", + "internal_solid_filament_id", + "top_surface_filament_id", + "bottom_surface_filament_id" + }; + std::vector selector_overridden_everywhere(selector_keys.size(), true); bool uses_default_extruder = false; for (size_t obj_idx = 0; obj_idx < wxGetApp().model().objects.size(); ++obj_idx) { @@ -20771,17 +21012,30 @@ bool Plater::check_filament_temp_mixing(int plate_index) used_slots.insert(extruder_id - 1); } } + // A global selector reaches this object's regions unless the object (or each of its + // printed parts) explicitly overrides the same key. + for (size_t k = 0; k < selector_keys.size(); ++k) + { + if (!selector_overridden_everywhere[k] || model_object->config.has(selector_keys[k])) + continue; + bool all_parts_override = true; + for (const ModelVolume* model_volume : model_object->volumes) + if (model_volume->is_model_part() && !model_volume->config.has(selector_keys[k])) + { + all_parts_override = false; + break; + } + if (!all_parts_override) + selector_overridden_everywhere[k] = false; + } } - // Collect from the Plater working config. The approach balances - // sensitivity against false positives: - // - Global features (wipe tower, support) always apply → always collected. - // - Feature-specific keys (outer_wall_filament_id, infill) depend on the global - // process defaults. They are only collected when at least one object - // on the plate uses the default extruder (e=0), which means those - // defaults WILL affect the actual slicing output. + // Collect from the Plater working config. Global features (wipe tower, support) + // always apply. An explicit (non-zero) global feature selector is the region + // default regardless of the part's own extruder, so it is collected whenever at + // least one part on the plate does not override the same key per object/volume + // (per-object overrides were already collected from the model configs above). { - // Always collect: features that cannot be overridden per-object. static const std::vector always_collect = { "wipe_tower_filament", "support_filament", @@ -20793,25 +21047,13 @@ bool Plater::check_filament_temp_mixing(int plate_index) if (option != nullptr && option->value >= 1 && option->value <= num_filaments) used_slots.insert(option->value - 1); } - - // If any object uses e=0, the global process defaults for - // wall / infill extruders apply and must be collected. - if (uses_default_extruder) + for (size_t k = 0; k < selector_keys.size(); ++k) { - static const std::vector default_keys = { - "outer_wall_filament_id", - "inner_wall_filament_id", - "sparse_infill_filament_id", - "internal_solid_filament_id", - "top_surface_filament_id", - "bottom_surface_filament_id" - }; - for (const char* key : default_keys) - { - const ConfigOptionInt* option = this->config()->option(key); - if (option != nullptr && option->value >= 1 && option->value <= num_filaments) - used_slots.insert(option->value - 1); - } + if (selector_overridden_everywhere[k]) + continue; + const ConfigOptionInt* option = this->config()->option(selector_keys[k]); + if (option != nullptr && option->value >= 1 && option->value <= num_filaments) + used_slots.insert(option->value - 1); } } @@ -21036,6 +21278,7 @@ void Plater::on_config_change(const DynamicPrintConfig &config) { bool update_scheduled = false; bool bed_shape_changed = false; + bool nozzle_tabs_changed = false; //bool print_sequence_changed = false; t_config_option_keys diff_keys = p->config->diff(config); for (auto opt_key : diff_keys) { @@ -21108,8 +21351,19 @@ void Plater::on_config_change(const DynamicPrintConfig &config) opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id") { update_scheduled = true; } + // ORCA multi-nozzle-size: the sidebar nozzle tabs mirror the printer's extruder count, + // nozzle sizes, layer height limits and preferred layer heights, and the valid preferred + // heights follow the object layer height. update_nozzle_values() refreshes them in place + // (and defers a full tab rebuild when the extruder count changed). + else if (opt_key == "nozzle_diameter" || opt_key == "extruder_layer_height" || + opt_key == "layer_height" || opt_key == "min_layer_height" || opt_key == "max_layer_height") { + nozzle_tabs_changed = true; + } } + if (nozzle_tabs_changed && p->sidebar != nullptr) + p->sidebar->update_nozzle_values(); + if (bed_shape_changed) set_bed_shape(); diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp index 05683b8d5a5..ce57050b88c 100644 --- a/src/slic3r/GUI/Plater.hpp +++ b/src/slic3r/GUI/Plater.hpp @@ -176,6 +176,8 @@ class Sidebar : public wxPanel void update_dynamic_filament_list(); void update_nozzle_settings(bool switch_machine = false); + // Refresh the nozzle tabs' combo values in place; rebuilds (deferred) on extruder count change. + void update_nozzle_values(); ObjectList* obj_list(); ObjectSettings* obj_settings(); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index ae87b7bbbd6..a6b6620784a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2879,30 +2879,32 @@ static std::vector substruct(std::vector const& l, std return t; } -static DynamicPrintConfig resolved_model_config_for_tab(const DynamicPrintConfig& config) +static DynamicPrintConfig resolved_model_config_for_tab(const DynamicPrintConfig& config, const DynamicPrintConfig* parent_scope_config) { DynamicPrintConfig resolved(config); + // Mirror the slicing precedence (apply_to_print_region_config): explicit selectors of outer + // scopes - the process preset and, for the part/layer tabs, the parent object's own config - + // win over this scope's extruder; the extruder only fills selectors left on "Default" (0) by + // every outer scope. The gate reads the edited print preset directly: a parent TAB's m_config + // may carry values auto-filled from the parent object's extruder, which must NOT beat this + // scope's own extruder. No cross-propagation between the selectors either - the engine + // resolves each feature's "Default" independently. if (const auto* extruder_opt = config.option("extruder"); extruder_opt != nullptr && extruder_opt->value > 0) { const int extruder = extruder_opt->value; + const DynamicPrintConfig& preset_config = wxGetApp().preset_bundle->prints.get_edited_preset().config; for (const char* key : {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", - "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id"}) - if (!resolved.has(key)) - resolved.set_key_value(key, new ConfigOptionInt(extruder)); - } - - // 0 = "Default" (use the active object/part filament); only explicit selections propagate. - if (resolved.has("sparse_infill_filament_id") && !resolved.has("internal_solid_filament_id")) - if (const int sparse_infill_filament_id = resolved.opt_int("sparse_infill_filament_id"); sparse_infill_filament_id > 0) - resolved.set_key_value("internal_solid_filament_id", new ConfigOptionInt(sparse_infill_filament_id)); - - if (resolved.has("internal_solid_filament_id")) - if (const int internal_solid_filament_id = resolved.opt_int("internal_solid_filament_id"); internal_solid_filament_id > 0) { - if (!resolved.has("top_surface_filament_id")) - resolved.set_key_value("top_surface_filament_id", new ConfigOptionInt(internal_solid_filament_id)); - if (!resolved.has("bottom_surface_filament_id")) - resolved.set_key_value("bottom_surface_filament_id", new ConfigOptionInt(internal_solid_filament_id)); + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id"}) { + if (resolved.has(key)) + continue; + if (const auto* preset_opt = preset_config.option(key); preset_opt != nullptr && preset_opt->value > 0) + continue; + if (parent_scope_config != nullptr) + if (const auto* parent_opt = parent_scope_config->option(key); parent_opt != nullptr && parent_opt->value > 0) + continue; + resolved.set_key_value(key, new ConfigOptionInt(extruder)); } + } return resolved; } @@ -2989,15 +2991,22 @@ void TabPrintModel::update_model_config() } m_null_keys.clear(); if (!m_object_configs.empty()) { + // For the part/layer tabs, the parent object's own explicit selectors also beat this + // scope's extruder in the engine; the object tab holds that object's config while a + // part/layer is selected (GUI_ObjectSettings). + const DynamicPrintConfig *parent_object_config = nullptr; + if (auto *object_tab = dynamic_cast(wxGetApp().get_model_tab()); + object_tab != nullptr && object_tab != this && m_parent_tab == object_tab && object_tab->m_object_configs.size() == 1) + parent_object_config = &object_tab->m_object_configs.begin()->second->get(); DynamicPrintConfig const & global_config= *m_config; - const DynamicPrintConfig local_config = resolved_model_config_for_tab(m_object_configs.begin()->second->get()); + const DynamicPrintConfig local_config = resolved_model_config_for_tab(m_object_configs.begin()->second->get(), parent_object_config); DynamicPrintConfig diff_config; std::vector all_keys = local_config.keys(); // at least one has these keys std::vector local_keys = intersect(m_keys, all_keys); // all equal on these keys if (m_object_configs.size() > 1) { std::vector global_keys = m_keys; // all equal with global on these keys for (auto & config : m_object_configs) { - const DynamicPrintConfig resolved_config = resolved_model_config_for_tab(config.second->get()); + const DynamicPrintConfig resolved_config = resolved_model_config_for_tab(config.second->get(), parent_object_config); auto equals = global_config.equal(resolved_config); global_keys = intersect(global_keys, equals); diff_config.apply_only(resolved_config, substruct(resolved_config.keys(), equals)); diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index 6b711a65fc0..acb3d72bb3c 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -38,7 +38,12 @@ static void for_each_path(const ExtrusionEntityCollection &collection, const Pat static void collect_path_heights(const ExtrusionEntityCollection &collection, std::vector &heights) { - for_each_path(collection, [&heights](const ExtrusionPath &path) { heights.emplace_back(path.height); }); + for_each_path(collection, [&heights](const ExtrusionPath &path) { + // Bridges print with the bridge flow whose height derives from the nozzle, + // not from the layer height; they are not this feature's concern. + if (path.role() != erBridgeInfill && path.role() != erInternalBridgeInfill) + heights.emplace_back(path.height); + }); } static std::vector region_path_heights(const LayerRegion *layerm) @@ -84,6 +89,9 @@ static DynamicPrintConfig two_extruder_config(double second_extruder_layer_heigh // out-param; raise the machine limit so the default print accelerations do not clobber the // layer height warnings under test. config.set_key_value("machine_max_acceleration_extruding", new ConfigOptionFloats({100000., 100000.})); + // The default G-code flavor rejects relative extruder addressing without a G92 E0 layer-change + // reset; this suite does not exercise the G-code writer, keep validation quiet. + config.set_key_value("use_relative_e_distances", new ConfigOptionBool(false)); return config; } @@ -103,9 +111,13 @@ static void init_two_part_print(Print &print, Model &model, const DynamicPrintCo coarse_volume->config.set("extruder", 2); object->add_instance(); - arrange_objects(model, InfiniteBed{}, ArrangeParams{scaled(min_object_distance(config))}); - for (ModelObject *mo : model.objects) + // This fork's arrangement engine rejects positions outside the (unset) plate even for an + // InfiniteBed; the fixture geometry is already laid out, so place it at a fixed bed spot. + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); mo->ensure_on_bed(); + } print.apply(model, config); print.set_status_silent(); } @@ -251,10 +263,11 @@ SCENARIO("Per-extruder layer height respects the extruder's minimum layer height Print print; Model model; init_two_part_print(print, model, config); - THEN("validation fails") { - const StringObjectException err = print.validate(); - REQUIRE(! err.string.empty()); - REQUIRE(err.opt_key == "extruder_layer_height"); + THEN("validation warns instead of rejecting: the minimum is a soft profile limit") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("minimum layer") != std::string::npos); + REQUIRE(warning.opt_key == "extruder_layer_height"); } } } @@ -310,10 +323,11 @@ SCENARIO("Per-extruder layer height honors feature filaments", "[MultiNozzleLaye } } - GIVEN("A part whose internal solid infill uses the 0.4 mm filament at 100% infill density") { + GIVEN("A part whose 100% density solid infill prints with the preferred-height filament") { DynamicPrintConfig config = two_extruder_config(0.4); - // At 100% density the combined infill is internal solid infill: the preference of ITS - // filament must decide the combined height, not the sparse infill filament's. + // At 100% density the combined infill is internal solid infill printed with the INTERNAL + // SOLID filament (PrintRegion::extruder()), so that filament's preference must decide the + // combined height. config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); config.set_key_value("sparse_infill_density", new ConfigOptionPercent(100)); Print print; @@ -327,18 +341,98 @@ SCENARIO("Per-extruder layer height honors feature filaments", "[MultiNozzleLaye GIVEN("A part whose outer walls use a filament with a different preferred layer height") { DynamicPrintConfig config = two_extruder_config(0.4); // The first part's outer walls print with filament 2 while its inner walls stay on - // filament 1: walls print together, so the part keeps the object layer height and the - // unhonored preference is warned about. + // filament 1 ("Default", no preferred height): the explicit preference sets the part's + // pitch and the no-preference filaments follow it instead of vetoing it. config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2)); + // Keep the combined-region line width checks out of the way, this test targets heights. + config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); Print print; Model model; init_two_part_print(print, model, config); - THEN("validation passes with a warning instead of an error") { + THEN("the part combines to the outer wall filament's height, warning about limits") { + // Filament 1 prints the pitch above its max_layer_height (0.3 < 0.4): warned, not vetoed. // This fork's validate() appends warnings to a single StringObjectException out-param. StringObjectException warning; REQUIRE(print.validate(&warning).string.empty()); - REQUIRE(! warning.string.empty()); + REQUIRE(warning.string.find("maximum layer") != std::string::npos); REQUIRE(warning.opt_key == "extruder_layer_height"); + + print.process(); + // Both parts combine now; the first part's region is identified by its top surface + // filament staying on 1. Its walls print 0.4 mm on every 2nd layer. + const PrintObject &object = *print.objects().front(); + int fine_region = -1; + for (size_t i = 0; i < object.num_printing_regions(); ++ i) + if (object.printing_region(i).config().top_surface_filament_id.value == 1) + fine_region = int(i); + REQUIRE(fine_region >= 0); + size_t combined_layers = 0, bad_heights = 0, unexpected = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector heights; + collect_path_heights(object.get_layer(int(idx))->get_region(fine_region)->perimeters, heights); + if (idx % 2 == 0) { + if (! heights.empty()) + ++ combined_layers; + for (float height : heights) + if (std::abs(height - 0.4) > 1e-3) + ++ bad_heights; + } else if (! heights.empty()) + ++ unexpected; + } + CHECK(combined_layers > 20); + CHECK(bad_heights == 0); + CHECK(unexpected == 0); + } + } + + GIVEN("Feature filaments with disagreeing preferred layer heights and no wall preference") { + // Top surfaces on filament 2 (prefers 0.4 mm) and bottom surfaces on filament 3 (prefers + // 0.6 mm): the features cannot agree on one pitch, so the part keeps the object layer + // height and validation warns that not all preferences can be honored. + DynamicPrintConfig config = two_extruder_config(0.4); + config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.4, 0.6, 0.6})); + config.set_key_value("extruder_layer_height", new ConfigOptionFloats({0., 0.4, 0.6})); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.07, 0.07})); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.6, 0.6})); + config.set_key_value("filament_diameter", new ConfigOptionFloats({1.75, 1.75, 1.75})); + config.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00", "#0000FF"})); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PLA", "PLA"})); + config.set_key_value("default_filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00", "#0000FF"})); + config.set_key_value("nozzle_temperature", new ConfigOptionInts({210, 210, 210})); + config.set_key_value("nozzle_temperature_range_low", new ConfigOptionInts({190, 190, 190})); + config.set_key_value("nozzle_temperature_range_high", new ConfigOptionInts({240, 240, 240})); + config.set_key_value("flush_volumes_matrix", new ConfigOptionFloats(std::vector(9, 0.))); + config.set_key_value("machine_max_acceleration_extruding", new ConfigOptionFloats({100000., 100000., 100000.})); + config.set_key_value("top_surface_filament_id", new ConfigOptionInt(2)); + config.set_key_value("bottom_surface_filament_id", new ConfigOptionInt(3)); + // Keep the combined-region line width checks out of the way, this test targets heights. + config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("the part keeps the object layer height and validation warns") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("cannot all be honored") != std::string::npos); + + print.process(); + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + size_t missing = 0, bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector heights; + collect_path_heights(object.get_layer(int(idx))->get_region(fine_region)->perimeters, heights); + if (heights.empty()) + ++ missing; + const double expected = idx == 0 ? 0.4 : 0.2; + for (float height : heights) + if (std::abs(height - expected) > 1e-3) + ++ bad_heights; + } + CHECK(missing == 0); + CHECK(bad_heights == 0); } } } @@ -365,7 +459,14 @@ SCENARIO("Fill line width follows the filament that prints the surface", "[Multi // its 0.4 mm nozzle, while internal solid infill (filament 2) resolves against 0.6 mm. // Solid fills may stretch line spacing up to 20% to fit a region evenly, so accept // widths in [nominal, 1.2 * nominal]. - size_t bottom_paths = 0, bottom_bad_widths = 0, solid_paths = 0, solid_bad_widths = 0; + // Notes on the expected widths: + // - Bottom surface paths resolve their percent width against filament 1's 0.4 mm nozzle. + // - Internal solid infill proper resolves against filament 2's 0.6 mm nozzle - but the + // solid paths ADJACENT to top/bottom shells print with the surface's filament by + // design (Fill.cpp), so a filament-1-derived width band among erSolidInfill paths is + // correct, and individual lines may be spacing-adapted below the nominal width. + // Assert each band exists where it must and nothing exceeds its own band's ceiling. + size_t bottom_paths = 0, bottom_in_band = 0, solid_paths = 0, solid_in_band = 0, solid_above_band = 0; for (size_t idx = 0; idx < object.layer_count(); ++ idx) { std::vector> widths; collect_path_role_widths(object.get_layer(int(idx))->get_region(fine_region)->fills, widths); @@ -375,22 +476,27 @@ SCENARIO("Fill line width follows the filament that prints the surface", "[Multi continue; ++ (bottom ? bottom_paths : solid_paths); const double expected = (idx == 0 ? 1.25 : 1.05) * (bottom ? 0.4 : 0.6); - if (role_width.second < expected - 1e-3 || role_width.second > expected * 1.2 + 1e-3) - ++ (bottom ? bottom_bad_widths : solid_bad_widths); + if (role_width.second >= expected - 1e-3 && role_width.second <= expected * 1.2 + 1e-3) + ++ (bottom ? bottom_in_band : solid_in_band); + if (! bottom && role_width.second > expected * 1.2 + 1e-3) + ++ solid_above_band; } } CHECK(bottom_paths > 0); - CHECK(bottom_bad_widths == 0); + CHECK(bottom_in_band * 4 > bottom_paths * 3); CHECK(solid_paths > 0); - CHECK(solid_bad_widths == 0); + CHECK(solid_in_band > 0); + CHECK(solid_above_band == 0); } } } -SCENARIO("Combined infill respects the printing extruder's layer height limits", "[MultiNozzleLayerHeight]") { +SCENARIO("Combined infill is limited by the printing nozzle only", "[MultiNozzleLayerHeight]") { GIVEN("Infill combining to a preferred height above the filament's maximum layer height") { - // Infill on filament 2: preferred layer height 0.6 exceeds its max_layer_height 0.45, - // so combining must stop at 0.4 mm groups instead of building 0.6 mm ones (3 x 0.2). + // Infill on filament 2: preferred layer height 0.6 exceeds its max_layer_height 0.45. + // The maximum is a soft profile limit: the explicit preference wins (with stock profiles + // the maximum would otherwise silently veto every legal preference), only the physical + // 0.6 mm nozzle bore caps the combining, and validation warns about the exceeded maximum. DynamicPrintConfig config = two_extruder_config(0.6); config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); config.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(2)); @@ -398,96 +504,133 @@ SCENARIO("Combined infill respects the printing extruder's layer height limits", config.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); Print print; Model model; - // A single part: filament 2 prints only infill, so its preference skips the strict wall checks. + // A single part: filament 2 prints only infill (the derived 0.6 mm feature pitch is vetoed + // by the walls' physical 0.4 mm nozzle, so the part itself stays at the object layer height). TriangleMesh cube = mesh(TestMesh::cube_20x20x20); cube.scale(Vec3f(1.f, 1.f, 0.5f)); ModelObject *object_model = model.add_object(); object_model->name = "single_cube"; object_model->add_volume(std::move(cube)); object_model->add_instance(); - arrange_objects(model, InfiniteBed{}, ArrangeParams{scaled(min_object_distance(config))}); - for (ModelObject *mo : model.objects) + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); mo->ensure_on_bed(); + } print.apply(model, config); print.set_status_silent(); - THEN("no combined infill group exceeds the maximum layer height") { - REQUIRE(print.validate().string.empty()); + THEN("infill combines to the full preferred height and validation warns about the maximum") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("maximum layer") != std::string::npos); print.process(); const PrintObject &object = *print.objects().front(); - size_t over_max = 0, combined = 0; + size_t over_preferred = 0, full_height = 0; for (size_t idx = 1; idx < object.layer_count(); ++ idx) for (const LayerRegion *layerm : object.get_layer(int(idx))->regions()) { std::vector heights; collect_path_heights(layerm->fills, heights); for (float height : heights) { - if (height > 0.45 + 1e-3) - ++ over_max; - else if (height > 0.2 + 1e-3) - ++ combined; + if (height > 0.6 + 1e-3) + ++ over_preferred; + else if (std::abs(height - 0.6) < 1e-3) + ++ full_height; } } - CHECK(over_max == 0); - CHECK(combined > 0); + CHECK(over_preferred == 0); + CHECK(full_height > 0); } } - GIVEN("Walls combining to a pitch a feature filament of the part cannot print") { - // Both wall filaments map to filament 2 at a 0.4 mm pitch, but top/bottom/solid features - // stay on filament 1 whose max_layer_height (0.3) cannot print that pitch: the part - // falls back to the object layer height with a warning, like disagreeing wall filaments. + GIVEN("Walls combining to a pitch above a feature filament's maximum layer height") { + // Both wall filaments map to filament 2 at a 0.4 mm pitch while top/bottom/solid features + // stay on filament 1 whose max_layer_height is only 0.3: the explicit wall preference + // wins - the part combines to 0.4 mm and validation warns about the exceeded maximum + // (only a physically too small nozzle vetoes the pitch). DynamicPrintConfig config = two_extruder_config(0.4); config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(2)); config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(2)); config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.45})); - // Keep the line width checks out of the way, this test targets the height fallback. + // Keep the combined-region line width checks out of the way, this test targets heights. config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); Print print; Model model; init_two_part_print(print, model, config); - THEN("validation warns and the part prints with the object layer height") { + THEN("validation warns about the maximum and the part prints the walls' pitch") { StringObjectException warning; REQUIRE(print.validate(&warning).string.empty()); - REQUIRE(warning.string.find("cannot print that height") != std::string::npos); + REQUIRE(warning.string.find("maximum layer") != std::string::npos); print.process(); - // Only the first part falls back; the second is all filament 2 and keeps the pitch. // Both parts' walls print with filament 2, so the parts are told apart by their - // top surface filament. + // top surface filament; the first part now combines like the second. const PrintObject &object = *print.objects().front(); int fine_region = -1; for (size_t i = 0; i < object.num_printing_regions(); ++ i) if (object.printing_region(i).config().top_surface_filament_id.value == 1) fine_region = int(i); REQUIRE(fine_region >= 0); - size_t wall_bad_heights = 0; + size_t combined_layers = 0, wall_bad_heights = 0, unexpected = 0; for (size_t idx = 0; idx < object.layer_count(); ++ idx) { - const double expected = idx == 0 ? 0.4 : 0.2; std::vector heights; collect_path_heights(object.get_layer(int(idx))->get_region(fine_region)->perimeters, heights); - for (float height : heights) - if (std::abs(height - expected) > 1e-3) - ++ wall_bad_heights; + if (idx % 2 == 0) { + if (! heights.empty()) + ++ combined_layers; + for (float height : heights) + if (std::abs(height - 0.4) > 1e-3) + ++ wall_bad_heights; + } else if (! heights.empty()) + ++ unexpected; } + CHECK(combined_layers > 20); CHECK(wall_bad_heights == 0); + CHECK(unexpected == 0); } } - GIVEN("A feature filament whose minimum layer height is above the pitch it prints at") { - // Internal solid infill on filament 2 (minimum layer height 0.3) prints with the 0.2 mm - // object layer height while filament 2's preference drives infill combining elsewhere. + GIVEN("Internal solid infill on the coarse filament while the walls stay on Default") { + // The user-reported setup: only internal_solid_filament_id points at filament 2 (preferred + // layer height 0.4) and no wall filament carries a preference. The feature filament's + // preference must derive the part's pitch instead of being silently ignored; areas falling + // back to the object layer height print below filament 2's 0.3 mm minimum, which warns. DynamicPrintConfig config = two_extruder_config(0.4); config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(2)); config.set_key_value("min_layer_height", new ConfigOptionFloats({0.07, 0.3})); - // Keep the line width checks out of the way, this test targets the height warning. + // Keep the combined-region line width checks out of the way, this test targets heights. config.set_key_value("line_width", new ConfigOptionFloatOrPercent(0.5, false)); Print print; Model model; init_two_part_print(print, model, config); - THEN("validation warns about printing below the minimum layer height") { + THEN("the feature filament's preference drives the part's pitch, warning about the minimum") { StringObjectException warning; REQUIRE(print.validate(&warning).string.empty()); REQUIRE(warning.string.find("minimum layer") != std::string::npos); + + print.process(); + // The first part's walls have no preference of their own, yet the part prints 0.4 mm + // layers on every 2nd layer because its internal solid infill filament asks for them. + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + size_t combined_layers = 0, bad_heights = 0, unexpected = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector heights; + collect_path_heights(object.get_layer(int(idx))->get_region(fine_region)->perimeters, heights); + if (idx % 2 == 0) { + if (! heights.empty()) + ++ combined_layers; + for (float height : heights) + if (std::abs(height - 0.4) > 1e-3) + ++ bad_heights; + } else if (! heights.empty()) + ++ unexpected; + } + CHECK(combined_layers > 20); + CHECK(bad_heights == 0); + CHECK(unexpected == 0); } } } @@ -678,7 +821,220 @@ SCENARIO("Per-extruder layer height validation rejects invalid configurations", THEN("validation fails") { expect_error(0.8); } } GIVEN("An extruder layer height exceeding the extruder's maximum layer height") { - // 0.6 is a multiple of 0.2 and fits the 0.6 mm nozzle, but exceeds max_layer_height 0.45. - THEN("validation fails") { expect_error(0.6); } + // 0.6 is a multiple of 0.2 and fits the 0.6 mm nozzle; it exceeds max_layer_height 0.45, + // but that is a soft profile limit - the explicit preference prints and validation warns + // (with stock profiles the maximum would otherwise reject every legal preference). + DynamicPrintConfig config = two_extruder_config(0.6); + Print print; + Model model; + init_two_part_print(print, model, config); + THEN("validation warns instead of failing") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + REQUIRE(warning.string.find("maximum layer") != std::string::npos); + REQUIRE(warning.opt_key == "extruder_layer_height"); + } + } +} + +// Four extruders with different nozzles, mirroring a Snapmaker U1 customized to 0.2/0.4/0.6/0.8 mm +// nozzles where every extruder carries a preferred layer height (4 * the 0.12 mm object layer +// height on the largest). On such a machine no whole-part pitch is possible - the 0.2 mm nozzle +// prints the part's default features - so the per-feature combining paths must serve instead. +static DynamicPrintConfig four_nozzle_config() +{ + DynamicPrintConfig config = DynamicPrintConfig::full_print_config(); + config.set_key_value("layer_height", new ConfigOptionFloat(0.12)); + config.set_key_value("initial_layer_print_height", new ConfigOptionFloat(0.12)); + config.set_key_value("enable_prime_tower", new ConfigOptionBool(false)); + config.set_key_value("enable_support", new ConfigOptionBool(false)); + + config.set_key_value("nozzle_diameter", new ConfigOptionFloats({0.2, 0.4, 0.6, 0.8})); + config.set_key_value("extruder_layer_height", new ConfigOptionFloats({0.12, 0.24, 0.36, 0.48})); + config.set_key_value("min_layer_height", new ConfigOptionFloats({0.08, 0.08, 0.14, 0.16})); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.16, 0.32, 0.48, 0.64})); + config.set_key_value("filament_diameter", new ConfigOptionFloats({1.75, 1.75, 1.75, 1.75})); + config.set_key_value("filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00", "#0000FF", "#FFFF00"})); + config.set_key_value("filament_type", new ConfigOptionStrings({"ABS", "ABS", "ABS", "ABS"})); + config.set_key_value("default_filament_colour", new ConfigOptionStrings({"#FF0000", "#00FF00", "#0000FF", "#FFFF00"})); + config.set_key_value("nozzle_temperature", new ConfigOptionInts({240, 240, 240, 240})); + config.set_key_value("nozzle_temperature_range_low", new ConfigOptionInts({220, 220, 220, 220})); + config.set_key_value("nozzle_temperature_range_high", new ConfigOptionInts({270, 270, 270, 270})); + config.set_key_value("flush_multiplier", new ConfigOptionFloat(1.)); + config.set_key_value("flush_volumes_matrix", new ConfigOptionFloats(std::vector(16, 0.))); + config.set_key_value("machine_max_acceleration_extruding", new ConfigOptionFloats({100000., 100000.})); + config.set_key_value("use_relative_e_distances", new ConfigOptionBool(false)); + return config; +} + +// One 20x20x10 mm cube. +static void init_cube_print(Print &print, Model &model, const DynamicPrintConfig &config) +{ + TriangleMesh cube = mesh(TestMesh::cube_20x20x20); + cube.scale(Vec3f(1.f, 1.f, 0.5f)); + ModelObject *object = model.add_object(); + object->name = "cube"; + object->add_volume(std::move(cube)); + object->add_instance(); + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); + mo->ensure_on_bed(); + } + print.apply(model, config); + print.set_status_silent(); +} + +SCENARIO("Walls combine to their filament's pitch when the part cannot follow", "[MultiNozzleLayerHeight]") { + GIVEN("Both walls on the 0.8 mm nozzle filament preferring 0.48 mm, the rest on the 0.2 mm nozzle") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(4)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("walls print once per 4 layers at 0.48 mm while the fills keep 0.12 mm") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + // The whole-part pitch is impossible (0.2 mm nozzle prints the fills), but the walls + // combine on their own - no "parts print with the object layer height" fallback. + CHECK(warning.string.find("too small to extrude") == std::string::npos); + print.process(); + + const PrintObject &object = *print.objects().front(); + size_t tall_wall_layers = 0, plain_wall_layers = 0, wall_bad_heights = 0, fill_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const LayerRegion *layerm = object.get_layer(int(idx))->get_region(0); + std::vector wall_heights; + collect_path_heights(layerm->perimeters, wall_heights); + bool tall = false, plain = false; + for (float height : wall_heights) { + if (std::abs(height - 0.48) < 1e-3) + tall = true; + else if (std::abs(height - 0.12) < 1e-3 || std::abs(height - 0.24) < 1e-3) + // The first layer and the run capping the object top stay finer. + plain = true; + else + ++ wall_bad_heights; + } + if (tall) ++ tall_wall_layers; + if (plain) ++ plain_wall_layers; + std::vector fill_heights; + collect_path_heights(layerm->fills, fill_heights); + for (float height : fill_heights) + if (std::abs(height - 0.12) > 1e-3) + ++ fill_bad_heights; + } + // 83 layers: layer 0 plain, 20 full runs of 4, a 2-layer cap. + CHECK(tall_wall_layers >= 15); + CHECK(plain_wall_layers <= 4); + CHECK(wall_bad_heights == 0); + CHECK(fill_bad_heights == 0); + } + } +} + +SCENARIO("Top surfaces combine to their filament's pitch by absorbing the shells below", "[MultiNozzleLayerHeight]") { + GIVEN("Top surfaces on the 0.8 mm nozzle filament preferring 0.48 mm, the rest on the 0.2 mm nozzle") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("top_surface_filament_id", new ConfigOptionInt(4)); + config.set_key_value("top_shell_layers", new ConfigOptionInt(9)); + config.set_key_value("bottom_shell_layers", new ConfigOptionInt(7)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("the topmost surface prints once at 0.48 mm while everything else keeps 0.12 mm") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + size_t tall_fill_paths = 0, wall_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const LayerRegion *layerm = object.get_layer(int(idx))->get_region(0); + std::vector fill_heights; + collect_path_heights(layerm->fills, fill_heights); + for (float height : fill_heights) + if (std::abs(height - 0.48) < 1e-3) { + ++ tall_fill_paths; + // Only the topmost layer may carry the absorbed pass. + CHECK(idx == object.layer_count() - 1); + } + std::vector wall_heights; + collect_path_heights(layerm->perimeters, wall_heights); + for (float height : wall_heights) + if (std::abs(height - 0.12) > 1e-3) + ++ wall_bad_heights; + } + CHECK(tall_fill_paths > 0); + CHECK(wall_bad_heights == 0); + } + } +} + +SCENARIO("A preference-less fine-nozzle wall filament vetoes the walls-only pitch", "[MultiNozzleLayerHeight]") { + GIVEN("Outer walls on the 0.8 mm nozzle preferring 0.48 mm, inner walls on a 0.2 mm nozzle with no preference") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("extruder_layer_height", new ConfigOptionFloats({0., 0., 0., 0.48})); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(1)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("no wall combines: the 0.2 mm inner-wall nozzle cannot extrude 0.48 mm layers") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + size_t wall_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector wall_heights; + collect_path_heights(object.get_layer(int(idx))->get_region(0)->perimeters, wall_heights); + for (float height : wall_heights) + if (std::abs(height - 0.12f) > 1e-3f) + ++ wall_bad_heights; + } + CHECK(wall_bad_heights == 0); + } + } +} + +SCENARIO("Disagreeing wall preferences meet at the lower height", "[MultiNozzleLayerHeight]") { + GIVEN("Outer walls prefer 0.48 mm (0.8 mm nozzle) and inner walls prefer 0.36 mm (0.6 mm nozzle)") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(3)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("the walls combine to 0.36 mm - the lower preference both nozzles can print") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + print.process(); + + const PrintObject &object = *print.objects().front(); + size_t tall_wall_layers = 0, wall_bad_heights = 0, fill_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + const LayerRegion *layerm = object.get_layer(int(idx))->get_region(0); + std::vector wall_heights; + collect_path_heights(layerm->perimeters, wall_heights); + for (float height : wall_heights) { + if (std::abs(height - 0.36f) < 1e-3f) + ++ tall_wall_layers; + else if (std::abs(height - 0.12f) > 1e-3f && std::abs(height - 0.24f) > 1e-3f) + // The first layer and forced / capping runs stay finer. + ++ wall_bad_heights; + } + std::vector fill_heights; + collect_path_heights(layerm->fills, fill_heights); + for (float height : fill_heights) + if (std::abs(height - 0.12f) > 1e-3f) + ++ fill_bad_heights; + } + CHECK(tall_wall_layers >= 15); + CHECK(wall_bad_heights == 0); + CHECK(fill_bad_heights == 0); + } } } From 7b915943f9954ef57fa41c9eeb7bf7955d5402da Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 13 Jul 2026 05:53:07 +0000 Subject: [PATCH 10/43] feat: split wall layer heights with optional wall height adjustment Let the outer and inner walls print at their own layer heights when they are assigned to filaments with different preferred layer heights ("Filament for Features" wall selectors + per-extruder layer heights). Splitting is automatic whenever both wall filaments carry explicit conforming preferred heights and one is a whole multiple of the other (e.g. inner walls 0.24 mm, outer wall 0.48 mm over a 0.12 mm object layer height): the finer class prints every time its height is reached and the coarser class extrudes once per multiple at the full height, so the two classes' tops stay flush. Either wall class may be the coarse one, and the finer pitch may equal the object layer height itself. Where geometry prevents a coarse run (shape drift, caps, first layer), both classes fall back to printing together at the lower height. When the two preferred heights do NOT divide evenly, the new "Adjust wall layer height" option (split_wall_adjust, default off, in the Filament for Features group) adjusts the wall-only layer height of one wall filament to the nearest divisor or multiple of the other so the walls can still split; landing on the other filament's height merges the walls there instead. Two selections appear while the toggle is on: which wall filament is adjusted (split_wall_adjust_filament) and whether its height may only decrease or increase (split_wall_adjust_direction). Unlike the preferred heights themselves, adjusted heights are hard-bounded by the adjusted filament's min/max layer heights and nozzle bore; without a legal candidate in the chosen direction the walls keep merging at the lower height with the existing conflict warning. The adjustment applies only to that filament's walls, its other features keep the preferred height, and validation reports the adjusted height once as an info message. Implementation. The decision logic lives in one place, PrintObject::wall_effective_multipliers() - the raw conforming multipliers of the two wall classes plus the optional adjustment - consumed by wall_layer_height_multiplier() (min-merge = the fine cadence), wall_split_pitches() (unequal + divisible = split) and Print::validate(). The fine cadence is the existing walls-only run machinery unchanged. A second marking pass in apply_extruder_layer_heights() groups whole fine runs into coarse runs (same shape tolerance and rest-on-object tests, applied across the whole span) and marks them on the LayerRegions (wall_split_count/height). LayerRegion::make_perimeters() drops the coarse class from the fine pass inside committed coarse runs - classified with perimeter_entity_uses_outer_wall_filament(), the same predicate the G-code dispatch uses, so cadence and filament routing agree by construction - and on a coarse-run top regenerates the coarse class once at the full height with the layer below the run as overhang reference; both generator passes share one parameterized routine. Thin / gap fills and the fill boundaries stay with the fine pass. validate() detects adjusted classes by comparing effective against raw multipliers, keeps the min-merge conflict warning silent when a split or adjustment resolved the disagreement, checks per-class heights against line widths and max layer heights, and does not count an adjusted class's own preference as unhonored. Mixed (virtual) filament ids never combine and never split; the engine code stays textually aligned with the mainline-Orca fork except for the mixed guards and the direct filament-to-extruder mapping. Tests: scenarios for the automatic divisible split (including the fine-pitch-equals-object-layer-height case), the non-divisible fallback, and the three adjustment outcomes (decrease to a divisor -> split, increase onto the other wall's height -> merge there, no legal candidate -> unchanged fallback with the conflict warning). --- src/libslic3r/Layer.cpp | 13 ++ src/libslic3r/Layer.hpp | 15 ++ src/libslic3r/LayerRegion.cpp | 148 ++++++++----- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 135 +++++++----- src/libslic3r/Print.hpp | 12 ++ src/libslic3r/PrintConfig.cpp | 61 ++++++ src/libslic3r/PrintConfig.hpp | 16 ++ src/libslic3r/PrintObject.cpp | 120 +++++++++-- src/libslic3r/PrintObjectSlice.cpp | 79 ++++++- src/slic3r/GUI/ConfigManipulation.cpp | 6 + src/slic3r/GUI/Tab.cpp | 3 + .../test_multi_nozzle_layer_height.cpp | 201 ++++++++++++++++++ 13 files changed, 685 insertions(+), 126 deletions(-) diff --git a/src/libslic3r/Layer.cpp b/src/libslic3r/Layer.cpp index a40f7d2c9f1..4deebc6b4b2 100644 --- a/src/libslic3r/Layer.cpp +++ b/src/libslic3r/Layer.cpp @@ -106,6 +106,17 @@ const Layer* LayerRegion::wall_combined_lower_layer() const return layer; } +// ORCA: split wall layer heights, see PrintObject::wall_split_pitches(). +const Layer* LayerRegion::wall_split_lower_layer() const +{ + if (m_wall_split_count <= 1) + return this->wall_combined_lower_layer(); + const Layer *layer = m_layer; + for (unsigned short i = 0; layer != nullptr && i < m_wall_split_count; ++ i) + layer = layer->lower_layer; + return layer; +} + // merge all regions' slices to get islands void Layer::make_slices() { @@ -280,6 +291,8 @@ void Layer::make_perimeters() if ((*layerm)->combined_layer_count() == other_layerm->combined_layer_count() && (*layerm)->wall_combined_count() == other_layerm->wall_combined_count() && std::abs((*layerm)->wall_combined_height() - other_layerm->wall_combined_height()) < EPSILON && + (*layerm)->wall_split_count() == other_layerm->wall_split_count() && + std::abs((*layerm)->wall_split_height() - other_layerm->wall_split_height()) < EPSILON && is_perimeter_compatible(this_region, other_region)) { other_layerm->perimeters.clear(); diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index cc14eca2a7a..c699849eaac 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -133,6 +133,18 @@ class LayerRegion double wall_combined_height() const; // Layer right below the wall run for overhang / bridge detection of the run's walls. const Layer* wall_combined_lower_layer() const; + + // ORCA: split wall layer heights. Cadence of the COARSER wall class when the outer and + // inner walls print with their own heights: > 1 (in object layers) on the top layer of a + // coarse run, 0 on the other layers of a committed coarse run (the coarse class prints + // nothing there), 1 when no coarse run covers this layer (the coarse class follows the fine + // cadence). Which class is coarse follows from the two wall filaments' effective heights + // (see PrintObject::wall_split_pitches()). + unsigned short wall_split_count() const { return m_wall_split_count; } + // Extrusion height of the coarse class at a coarse-run top. + double wall_split_height() const { return m_wall_split_height; } + // Layer right below the coarse run for overhang / bridge detection of the coarse walls. + const Layer* wall_split_lower_layer() const; private: void simplify_entity_collection(ExtrusionEntityCollection* entity_collection); void simplify_path(ExtrusionPath* path); @@ -155,6 +167,9 @@ class LayerRegion // ORCA: set by PrintObject::apply_extruder_layer_heights(), see wall_combined_count() / wall_combined_height(). unsigned short m_wall_combined_count { 1 }; double m_wall_combined_height { 0. }; + // ORCA: set by PrintObject::apply_extruder_layer_heights(), see wall_split_count() / wall_split_height(). + unsigned short m_wall_split_count { 1 }; + double m_wall_split_height { 0. }; }; class Layer diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index 583c5d45f18..0d5bd6e429f 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -5,6 +5,7 @@ #include "Geometry.hpp" #include "PerimeterGenerator.hpp" #include "Print.hpp" +#include "GCode/ToolOrdering.hpp" #include "Surface.hpp" #include "BoundingBox.hpp" #include "SVG.hpp" @@ -184,6 +185,28 @@ void LayerRegion::slices_to_fill_surfaces_clipped() } } +// ORCA: split wall layer heights - drop one wall class from freshly generated perimeters. +// Classification matches the G-code dispatch (perimeter_entity_uses_outer_wall_filament()), so +// the class printing at a cadence is exactly the class dispatched to its wall filament. +static void remove_wall_class(ExtrusionEntityCollection &collection, bool outer_class) +{ + ExtrusionEntitiesPtr &entities = collection.entities; + for (size_t i = 0; i < entities.size(); ) { + if (auto *sub = dynamic_cast(entities[i])) { + remove_wall_class(*sub, outer_class); + if (! sub->entities.empty()) { + ++ i; + continue; + } + } else if (perimeter_entity_uses_outer_wall_filament(*entities[i]) != outer_class) { + ++ i; + continue; + } + delete entities[i]; + entities.erase(entities.begin() + i); + } +} + void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRegionPtrs &compatible_regions, SurfaceCollection* fill_surfaces, ExPolygons* fill_no_overlap) { this->perimeters.clear(); @@ -209,55 +232,58 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe // height. On layers of a walls-only run (wall_combined_count()) the walls are generated with // the run height on every run layer - so the fill boundaries line up with the walls printed // once at the run top - and the wall extrusions of the layers below the top are dropped below. - const double perimeter_height = this->wall_combined_height(); - - PerimeterGenerator g( - // input: - &slices, - &compatible_regions, - perimeter_height, - this->layer()->slice_z, - this->flow(frPerimeter, perimeter_height), - &perimeter_config, - &this->layer()->object()->config(), - &print_config, - spiral_mode, - - // output: - &this->perimeters, - &this->thin_fills, - fill_surfaces, - //BBS - fill_no_overlap - ); - - // Detect overhangs / bridges against the layer below the whole combined group or wall run - // (wall_combined_lower_layer() == lower_layer for regular regions). - if (const Layer *lower_layer = this->wall_combined_lower_layer(); lower_layer != nullptr) - // Cummulative sum of polygons over all the regions. - g.lower_slices = &lower_layer->lslices; - if (this->layer()->upper_layer != NULL) - g.upper_slices = &this->layer()->upper_layer->lslices; - - int region_id = this->region().print_object_region_id(); - if (this->layer()->upper_layer != NULL) - g.upper_slices_same_region = &this->layer()->upper_layer->get_region(region_id)->slices; - - g.layer_id = (int)this->layer()->id(); - g.ext_perimeter_flow = this->flow(frExternalPerimeter, perimeter_height); - g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, 0, perimeter_height); - // Overhangs of external / fully overhanging loops dispatch to the outer wall filament (GCode::process_layer() splits mixed perimeters); resolve their width against its nozzle. - // The filament id comes from the resolved perimeter_config (mixed-filament aware), not the raw region config. - g.ext_overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, - (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value), perimeter_height); - g.solid_infill_flow = this->flow(frSolidInfill, perimeter_height); - // Gap fill dispatches to the outer wall filament (LayerTools::extruder()); resolve its width against its nozzle. - g.gap_fill_flow = this->flow(frSolidInfill, perimeter_height, (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value)); - - if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) - g.process_arachne(); - else - g.process_classic(); + // Shared by the main pass and the coarse-wall pass of split wall layer heights below. + const int region_id = this->region().print_object_region_id(); + auto generate_perimeters = [&](double height, const Layer *lower_layer, ExtrusionEntityCollection *perimeters, + ExtrusionEntityCollection *thin_fills, SurfaceCollection *surfaces, ExPolygons *no_overlap) { + PerimeterGenerator g( + // input: + &slices, + &compatible_regions, + height, + this->layer()->slice_z, + this->flow(frPerimeter, height), + &perimeter_config, + &this->layer()->object()->config(), + &print_config, + spiral_mode, + + // output: + perimeters, + thin_fills, + surfaces, + //BBS + no_overlap + ); + + // Detect overhangs / bridges against the layer below the whole combined group or wall run + // (wall_combined_lower_layer() == lower_layer for regular regions). + if (lower_layer != nullptr) + // Cummulative sum of polygons over all the regions. + g.lower_slices = &lower_layer->lslices; + if (this->layer()->upper_layer != NULL) { + g.upper_slices = &this->layer()->upper_layer->lslices; + g.upper_slices_same_region = &this->layer()->upper_layer->get_region(region_id)->slices; + } + + g.layer_id = (int)this->layer()->id(); + g.ext_perimeter_flow = this->flow(frExternalPerimeter, height); + g.overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, 0, height); + // Overhangs of external / fully overhanging loops dispatch to the outer wall filament (GCode::process_layer() splits mixed perimeters); resolve their width against its nozzle. + // The filament id comes from the resolved perimeter_config (mixed-filament aware), not the raw region config. + g.ext_overhang_flow = this->bridging_flow(frPerimeter, object_config.thick_bridges, + (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value), height); + g.solid_infill_flow = this->flow(frSolidInfill, height); + // Gap fill dispatches to the outer wall filament (LayerTools::extruder()); resolve its width against its nozzle. + g.gap_fill_flow = this->flow(frSolidInfill, height, (unsigned int)std::max(0, perimeter_config.outer_wall_filament_id.value)); + + if (this->layer()->object()->config().wall_generator.value == PerimeterGeneratorType::Arachne && !spiral_mode) + g.process_arachne(); + else + g.process_classic(); + }; + generate_perimeters(this->wall_combined_height(), this->wall_combined_lower_layer(), + &this->perimeters, &this->thin_fills, fill_surfaces, fill_no_overlap); // ORCA: walls-only pitch. The run layers below the top only generated their perimeters to // carve fill boundaries consistent with the run; the walls themselves (and their thin fills / @@ -266,6 +292,30 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe this->perimeters.clear(); this->thin_fills.clear(); } + + // ORCA: split wall layer heights. Inside a coarse run the pass above laid out both wall + // classes at the fine cadence; the coarse class prints on its own runs instead: drop its + // loops here and, on the run top, regenerate them once at the full coarse height with the + // layer below the run as overhang reference (their space stays reserved - the fine pass + // placed the remaining loops around them). Thin / gap fills and the fill boundaries stay + // with the fine pass; the coarse pass's copies are scratch. Outside a committed coarse run + // the coarse class simply follows the fine cadence. + if (this->wall_split_height() > 0. && ! this->perimeters.empty()) { + unsigned int fine = 0, coarse = 0; + bool coarse_is_outer = false; + if (this->layer()->object()->wall_split_pitches(this->region(), fine, coarse, coarse_is_outer)) { + remove_wall_class(this->perimeters, coarse_is_outer); + if (this->wall_split_count() > 1) { + ExtrusionEntityCollection coarse_perimeters, scratch_thin_fills; + SurfaceCollection scratch_surfaces; + ExPolygons scratch_no_overlap; + generate_perimeters(this->wall_split_height(), this->wall_split_lower_layer(), + &coarse_perimeters, &scratch_thin_fills, &scratch_surfaces, &scratch_no_overlap); + remove_wall_class(coarse_perimeters, ! coarse_is_outer); + this->perimeters.append(std::move(coarse_perimeters.entities)); + } + } + } } #if 1 diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index a56fd6a435f..b4f8616bb9d 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -907,7 +907,7 @@ static std::vector s_Preset_print_options { "detect_narrow_internal_solid_infill", "gcode_add_line_number", "enable_arc_fitting", "precise_z_height", "infill_combination","infill_combination_max_layer_height", /*"adaptive_layer_height",*/ // ORCA: per-extruder layer height ("extruder_layer_height"). - "extruder_layer_height_mode", "extruder_layer_height_tolerance", + "extruder_layer_height_mode", "extruder_layer_height_tolerance", "split_wall_adjust", "split_wall_adjust_filament", "split_wall_adjust_direction", "support_bottom_interface_spacing", "enable_overhang_speed", "slowdown_for_curled_perimeters", "overhang_1_4_speed", "overhang_2_4_speed", "overhang_3_4_speed", "overhang_4_4_speed", "initial_layer_infill_speed", "only_one_wall_top", "timelapse_type", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 1151d7771c9..44832e806be 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -1831,7 +1831,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } }; // Flags for the per-extruder layer height / support nozzle warnings below. - bool warned_wall_pref_conflicts = false; + bool warned_wall_pref_conflicts = false, warned_wall_adjustments = false; bool warned_unhonored_heights = false, warned_support_mixed_nozzles = false, warned_below_min_heights = false, warned_pitch_fallbacks = false, warned_above_max_heights = false, warned_ignored_feature_prefs = false; @@ -2040,10 +2040,16 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* }; // Inner-wall loops can appear at a single wall loop too (alternate / extra walls, // see PrintObject::region_prints_inner_walls()). + auto prints_outer_wall = [&](unsigned int filament) { + return region_config.wall_loops.value > 0 && + filament == selector_filament0(region_config.outer_wall_filament_id.value); + }; + auto prints_inner_wall = [&](unsigned int filament) { + return region_config.wall_loops.value > 0 && PrintObject::region_prints_inner_walls(region_config) && + filament == selector_filament0(region_config.inner_wall_filament_id.value); + }; auto prints_walls = [&](unsigned int filament) { - return (region_config.wall_loops.value > 0 && filament == selector_filament0(region_config.outer_wall_filament_id.value)) || - (region_config.wall_loops.value > 0 && PrintObject::region_prints_inner_walls(region_config) && - filament == selector_filament0(region_config.inner_wall_filament_id.value)); + return prints_outer_wall(filament) || prints_inner_wall(filament); }; auto prints_top = [&](unsigned int filament) { return region_config.top_shell_layers.value > 0 && @@ -2068,37 +2074,40 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } // Walls-only pitch (mirrors PrintObject::wall_layer_height_multiplier()): when the // rest of the part vetoes the walls' pitch, the walls still combine to it on their - // own. Disagreeing explicit wall preferences meet at the lower one; preference-less + // own. Non-splittable effective preferences meet at the lower one; preference-less // wall filaments follow, but every wall filament must fit the pitch through its bore. - unsigned int wall_candidate = 0; - if (region_config.wall_loops.value > 0) { - auto merge_wall = [&](int filament_id) -> bool { - if (std::max(0, filament_id - 1) >= (int)m_config.filament_diameter.size()) - // Mixed (virtual) filament ids never combine. - return false; - const size_t extruder_idx = selector_extruder_idx(filament_id); - const double preferred = m_config.extruder_layer_height.get_at(extruder_idx); - if (preferred <= 0.) - return true; - const unsigned int m = std::max(1u, conforming_multiplier(preferred, extruder_idx)); - wall_candidate = wall_candidate == 0 ? m : std::min(wall_candidate, m); - return true; - }; - auto wall_nozzle_fits = [&](unsigned int m) { - const double p = m * layer_height; - if (p > m_config.nozzle_diameter.get_at(selector_extruder_idx(region_config.outer_wall_filament_id.value)) + EPSILON) - return false; - return ! PrintObject::region_prints_inner_walls(region_config) || - p <= m_config.nozzle_diameter.get_at(selector_extruder_idx(region_config.inner_wall_filament_id.value)) + EPSILON; - }; - if (! merge_wall(region_config.outer_wall_filament_id.value) || - (PrintObject::region_prints_inner_walls(region_config) && ! merge_wall(region_config.inner_wall_filament_id.value))) - wall_candidate = 1; - else if (wall_candidate > 1 && ! wall_nozzle_fits(wall_candidate)) + unsigned int eff_outer = 0, eff_inner = 0; + object->wall_effective_multipliers(region, eff_outer, eff_inner); + // Which wall class prints an adjusted height ("split_wall_adjust") instead of its + // filament's own preference - the adjustment is deliberate, so the conflict and + // unhonored-heights warnings below stay silent about it. + auto raw_wall_multiplier = [&](int filament_id) -> unsigned int { + const unsigned int filament = (unsigned int)std::max(0, filament_id); + return object->extruder_preferred_layer_height(filament) > 0. ? + object->layer_height_multiplier_for_filament(filament) : 0; + }; + const bool adjusted_outer = eff_outer != 0 && + eff_outer != raw_wall_multiplier(region_config.outer_wall_filament_id.value); + const bool adjusted_inner = eff_inner != 0 && + eff_inner != raw_wall_multiplier(region_config.inner_wall_filament_id.value); + unsigned int wall_candidate = eff_outer == 0 || eff_inner == 0 ? std::max(eff_outer, eff_inner) : + std::min(eff_outer, eff_inner); + if (wall_candidate > 1) { + const double p = wall_candidate * layer_height; + if (p > m_config.nozzle_diameter.get_at(selector_extruder_idx(region_config.outer_wall_filament_id.value)) + EPSILON || + (PrintObject::region_prints_inner_walls(region_config) && + p > m_config.nozzle_diameter.get_at(selector_extruder_idx(region_config.inner_wall_filament_id.value)) + EPSILON)) wall_candidate = 1; } if (wall_candidate == 0) wall_candidate = 1; + // Split wall layer heights (mirrors PrintObject::wall_split_pitches()): each + // wall class prints at its own pitch, so the min-merge conflict warning stays + // silent and the per-class checks below use each class's real height. + unsigned int split_fine = 0, split_coarse = 0; + bool split_coarse_outer = false; + const bool wall_split = region_multiplier == 1 && + object->wall_split_pitches(region, split_fine, split_coarse, split_coarse_outer); // 0-based indices of all filaments printing this region's features. std::vector used_filaments; PrintRegion::collect_object_printing_extruders(m_config, region_config, false /* has_brim */, used_filaments); @@ -2144,11 +2153,26 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* } // The walls combine on their own only while the region prints at the object layer height. const unsigned int wall_multiplier = region_multiplier == 1 ? wall_candidate : 1; + // Tell the user once what the "split_wall_adjust" adjustment settled on. + if ((adjusted_outer || adjusted_inner) && ! warned_wall_adjustments) { + warned_wall_adjustments = true; + const int adjusted_id = adjusted_outer ? region_config.outer_wall_filament_id.value : + region_config.inner_wall_filament_id.value; + warn(Slic3r::format(_u8L("The wall layer height of filament %1% was adjusted from the preferred %2% mm to " + "%3% mm so the outer and inner walls can print at compatible layer heights " + "(\"Adjust wall layer height\"). Only this filament's walls print the adjusted " + "height; its other features keep the preferred one."), + selector_filament0(adjusted_id) + 1, + object->extruder_preferred_layer_height((unsigned int)std::max(0, adjusted_id)), + (adjusted_outer ? eff_outer : eff_inner) * layer_height), + "split_wall_adjust", object); + } // Disagreeing wall preferences: report what actually happens once the walls-only // pitch is known - with every extruder carrying a preferred height, a wall // selector left on "Default" resolves to the part's filament and its preference - // silently conflicts with the other wall's. - if (wall_prefs_disagree && ! warned_wall_pref_conflicts) { + // silently conflicts with the other wall's. An adjustment that made the heights + // compatible resolved the disagreement (reported above). + if (wall_prefs_disagree && ! wall_split && ! adjusted_outer && ! adjusted_inner && ! warned_wall_pref_conflicts) { warned_wall_pref_conflicts = true; if (wall_multiplier > 1) warn(Slic3r::format(_u8L("The wall filaments of some object parts prefer different layer heights. " @@ -2175,19 +2199,25 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* pitch, filament0 + 1, max_lh), "extruder_layer_height", object); }; - if (region_multiplier > 1 || wall_multiplier > 1) - // With a walls-only pitch only the wall filaments print it, the others stay - // at the object layer height. + const double region_pitch = region_multiplier * layer_height; + // Heights the wall classes print at: the region pitch when the whole part + // combines, the walls-only pitch when the walls combine on their own, and + // each class's own pitch when the wall layer heights are split. + const double merged_wall_height = wall_multiplier > 1 ? wall_multiplier * layer_height : region_pitch; + const double outer_wall_height = wall_split ? (split_coarse_outer ? split_coarse : split_fine) * layer_height : merged_wall_height; + const double inner_wall_height = wall_split ? (split_coarse_outer ? split_fine : split_coarse) * layer_height : merged_wall_height; + if (region_multiplier > 1 || wall_multiplier > 1 || wall_split) + // With a walls-only or split pitch only the wall filaments print it, the + // others stay at the object layer height. for (const unsigned int filament : used_filaments) { - double pitch = region_multiplier * layer_height; - if (wall_multiplier > 1) { + double pitch = region_pitch; + if (region_multiplier == 1) { if (! prints_walls(filament)) continue; - pitch = wall_multiplier * layer_height; + pitch = prints_outer_wall(filament) ? outer_wall_height : inner_wall_height; } warn_above_max(pitch, filament, m_config.max_layer_height.get_at(extruder_of(filament))); } - const double region_pitch = region_multiplier * layer_height; auto filament_nozzle = [&](int filament_id) { return m_config.nozzle_diameter.get_at(selector_extruder_idx(filament_id)); }; @@ -2242,15 +2272,16 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* warn_above_max(top_multiplier * layer_height, selector_filament0(region_config.top_surface_filament_id.value), m_config.max_layer_height.get_at(selector_extruder_idx(region_config.top_surface_filament_id.value))); - // Heights the filament's pitch-bound features print at: walls at the walls-only + // Heights the filament's pitch-bound features print at: walls at their class's // pitch when active, top surfaces at the top pitch when active, everything else // at the region pitch (also used by the unhonored-heights check below). - const double wall_print_height = wall_multiplier > 1 ? wall_multiplier * layer_height : region_pitch; - const double top_print_height = top_multiplier > 1 ? top_multiplier * layer_height : region_pitch; + const double top_print_height = top_multiplier > 1 ? top_multiplier * layer_height : region_pitch; auto lowest_feature_height = [&](unsigned int filament) { double h = std::numeric_limits::max(); - if (prints_walls(filament)) - h = std::min(h, wall_print_height); + if (prints_outer_wall(filament)) + h = std::min(h, outer_wall_height); + if (prints_inner_wall(filament)) + h = std::min(h, inner_wall_height); if (prints_top(filament)) h = std::min(h, top_print_height); if (prints_bottom_or_solid(filament)) @@ -2272,7 +2303,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // the object layer height (see apply_extruder_layer_heights()), possibly below // the minimum. const bool fallback_below_min = (region_multiplier > 1 || - (wall_multiplier > 1 && prints_walls(filament)) || + ((wall_multiplier > 1 || wall_split) && prints_walls(filament)) || (top_multiplier > 1 && prints_top(filament))) && layer_height < min_lh - EPSILON; if (! pitch_below_min && ! fallback_below_min) @@ -2305,8 +2336,12 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* combined_infill_height >= preferred - EPSILON; if (prints_pitch_features(filament)) { bool honored = combined_covered; - if (prints_walls(filament)) - honored &= std::abs(wall_print_height - preferred) < EPSILON; + // An adjusted wall class prints its adjusted height on purpose + // (warned about above), not an unhonored preference. + if (prints_outer_wall(filament)) + honored &= adjusted_outer || std::abs(outer_wall_height - preferred) < EPSILON; + if (prints_inner_wall(filament)) + honored &= adjusted_inner || std::abs(inner_wall_height - preferred) < EPSILON; if (prints_top(filament)) honored &= std::abs(top_print_height - preferred) < EPSILON; if (prints_bottom_or_solid(filament)) @@ -2329,7 +2364,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* "extruder_layer_height", object); break; } - if (region_multiplier > 1 || wall_multiplier > 1 || top_multiplier > 1) { + if (region_multiplier > 1 || wall_multiplier > 1 || top_multiplier > 1 || wall_split) { object_has_combined_regions = true; // Combined extrusions are thicker: explicit line widths must stay above the // height each feature prints at, resolved per role against the region's own @@ -2339,8 +2374,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // infill at 100% density) must not hard-error the pitch; features printing // at the object layer height are skipped by the pitch test. const std::tuple width_checks[] = { - { region_config.outer_wall_filament_id.value, "outer_wall_line_width", wall_print_height, region_config.wall_loops.value > 0 }, - { region_config.inner_wall_filament_id.value, "inner_wall_line_width", wall_print_height, region_config.wall_loops.value > 0 }, + { region_config.outer_wall_filament_id.value, "outer_wall_line_width", outer_wall_height, region_config.wall_loops.value > 0 }, + { region_config.inner_wall_filament_id.value, "inner_wall_line_width", inner_wall_height, region_config.wall_loops.value > 0 }, { region_config.sparse_infill_filament_id.value, "sparse_infill_line_width", region_pitch, region_config.sparse_infill_density.value > 0 && ! solid_combined_infill }, { region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width", region_pitch, true }, { region_config.top_surface_filament_id.value, "top_surface_line_width", top_print_height, region_config.top_shell_layers.value > 0 }, diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 1c86f1fa68d..9333fc122da 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -507,6 +507,18 @@ class PrintObject : public PrintObjectBaseWithState 1? bool has_combined_layer_regions() const; // Multi-nozzle support restriction ("support_nozzle_diameter" print option): may the given 1-based filament print this object's support / raft? diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f98a5aa7649..c4a9df2a222 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -301,6 +301,19 @@ static t_config_enum_values s_keys_map_ExtruderLayerHeightMode{ }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderLayerHeightMode) +// ORCA: split wall layer heights ("split_wall_adjust"). +static t_config_enum_values s_keys_map_WallSplitFilament{ + { "outer_wall", int(WallSplitFilament::wsfOuterWall) }, + { "inner_wall", int(WallSplitFilament::wsfInnerWall) }, +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallSplitFilament) + +static t_config_enum_values s_keys_map_WallSplitDirection{ + { "decrease", int(WallSplitDirection::wsdDecrease) }, + { "increase", int(WallSplitDirection::wsdIncrease) }, +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(WallSplitDirection) + // Orca static t_config_enum_values s_keys_map_InternalBridgeFilter { { "disabled", ibfDisabled }, @@ -4215,6 +4228,54 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionInt(0)); + // ORCA: split wall layer heights. When the outer and inner wall filaments print at their own + // preferred layer heights and one is an integer multiple of the other, the walls always split: + // the finer walls print every time their height is reached and the coarser walls once per + // multiple, so their tops stay flush. The options below additionally allow adjusting one wall + // filament's wall-only layer height so the split also happens when the preferred heights do + // not divide evenly. + def = this->add("split_wall_adjust", coBool); + def->label = L("Adjust wall layer height"); + def->category = L("Extruders"); + def->tooltip = L("Outer and inner walls automatically print at their own preferred layer heights when " + "one height is an integer multiple of the other. When the heights do not divide evenly, " + "this option adjusts the wall layer height of one of the two wall filaments (chosen " + "below) to the nearest multiple or divisor of the other, so the walls can still split. " + "The adjusted height only applies to that filament's walls; other features keep the " + "preferred layer height. Adjustments never leave the filament's layer height limits: " + "if no allowed height exists in the chosen direction, the walls print together at the " + "lower height as usual."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + + def = this->add("split_wall_adjust_filament", coEnum); + def->label = L("Adjusted walls"); + def->category = L("Extruders"); + def->tooltip = L("Which of the two wall filaments gets its wall layer height adjusted when the " + "preferred layer heights do not divide evenly."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("outer_wall"); + def->enum_values.push_back("inner_wall"); + def->enum_labels.push_back(L("Outer walls")); + def->enum_labels.push_back(L("Inner walls")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(wsfOuterWall)); + + def = this->add("split_wall_adjust_direction", coEnum); + def->label = L("Adjustment direction"); + def->category = L("Extruders"); + def->tooltip = L("Whether the adjusted wall filament's wall layer height is decreased or increased to " + "reach a height compatible with the other wall filament. Heights outside the adjusted " + "filament's layer height limits are never used: if no allowed height exists in this " + "direction, the walls print together at the lower height as usual."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("decrease"); + def->enum_values.push_back("increase"); + def->enum_labels.push_back(L("Decrease")); + def->enum_labels.push_back(L("Increase")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(wsdDecrease)); + def = this->add("inner_wall_line_width", coFloatOrPercent); def->label = L("Inner wall"); def->category = L("Quality"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index b7aa97ea316..4ee333f92c1 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -201,6 +201,17 @@ enum ExtruderLayerHeightMode { elhmAdaptive, }; +// ORCA: split wall layer heights - which wall class gets its wall-only layer height adjusted +// when the two wall filaments' preferred heights do not divide evenly, and in which direction. +enum WallSplitFilament { + wsfOuterWall, + wsfInnerWall, +}; +enum WallSplitDirection { + wsdDecrease, + wsdIncrease, +}; + //Orca enum InternalBridgeFilter { ibfDisabled, ibfLimited, ibfNofilter @@ -467,6 +478,8 @@ static std::string get_bed_temp_1st_layer_key(const BedType type) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(PrinterTechnology) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(ExtruderLayerHeightMode) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WallSplitFilament) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(WallSplitDirection) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(GCodeFlavor) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(FuzzySkinMode) @@ -847,6 +860,9 @@ PRINT_CONFIG_CLASS_DEFINE( // ORCA: per-extruder layer height ("extruder_layer_height"). ((ConfigOptionEnum, extruder_layer_height_mode)) ((ConfigOptionPercent, extruder_layer_height_tolerance)) + ((ConfigOptionBool, split_wall_adjust)) + ((ConfigOptionEnum, split_wall_adjust_filament)) + ((ConfigOptionEnum, split_wall_adjust_direction)) ((ConfigOptionFloat, mmu_segmented_region_max_width)) ((ConfigOptionFloat, mmu_segmented_region_interlocking_depth)) ((ConfigOptionFloat, raft_contact_distance)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index c50238a25ff..4b57b99c2bc 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -986,6 +986,9 @@ bool PrintObject::invalidate_state_by_config_options( // ORCA: per-extruder layer height: the layer combining happens at the slicing step. || opt_key == "extruder_layer_height_mode" || opt_key == "extruder_layer_height_tolerance" + || opt_key == "split_wall_adjust" + || opt_key == "split_wall_adjust_filament" + || opt_key == "split_wall_adjust_direction" || opt_key == "dithering_z_step_size" || opt_key == "dithering_local_z_mode" || opt_key == "dithering_local_z_whole_objects" @@ -3689,27 +3692,17 @@ unsigned int PrintObject::wall_layer_height_multiplier(const PrintRegion ®ion if (this->region_layer_height_multiplier(region) > 1) // The whole region follows the pitch, walls included. return 1; - const int num_filaments = (int)m_print->config().filament_diameter.size(); - const bool prints_inner_walls = region_prints_inner_walls(config); - unsigned int multiplier = 0; - auto merge_wall_filament = [this, num_filaments, &multiplier](int filament_id) -> bool { - if (std::max(0, filament_id - 1) >= num_filaments) - // Snapmaker mixed filament: virtual mixed ids resolve to a different physical - // extruder per layer, no stable pitch exists for them. - return false; - if (this->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)) <= 0.) - // No explicit preference: follows the other wall filament's pitch. - return true; - const unsigned int m = this->layer_height_multiplier_for_filament((unsigned int)std::max(0, filament_id)); - // Outer and inner walls print together at one height: disagreeing preferences meet at the - // LOWER one (Print::validate() reports the unhonored higher preference). - multiplier = multiplier == 0 ? m : std::min(multiplier, m); - return true; - }; - if (! merge_wall_filament(config.outer_wall_filament_id.value)) - return 1; - if (prints_inner_walls && ! merge_wall_filament(config.inner_wall_filament_id.value)) + unsigned int outer_m = 0, inner_m = 0; + if (! this->wall_effective_multipliers(region, outer_m, inner_m)) + // Snapmaker mixed filament: virtual mixed ids resolve to a different physical + // extruder per layer, no stable pitch exists for them. return 1; + // A preference-less wall filament (0) follows the other one's pitch. Outer and inner walls + // that cannot split print together at one height: disagreeing effective preferences meet at + // the LOWER one (Print::validate() reports the unhonored higher preference). With split + // pitches this minimum is the finer class's pitch, the run cadence of both classes. + const unsigned int multiplier = outer_m == 0 || inner_m == 0 ? std::max(outer_m, inner_m) : + std::min(outer_m, inner_m); if (multiplier <= 1) return 1; // Every wall filament must fit the pitch through its own bore, including preference-less @@ -3723,11 +3716,96 @@ unsigned int PrintObject::wall_layer_height_multiplier(const PrintRegion ®ion }; if (nozzle_too_small(config.outer_wall_filament_id.value)) return 1; - if (prints_inner_walls && nozzle_too_small(config.inner_wall_filament_id.value)) + if (region_prints_inner_walls(config) && nozzle_too_small(config.inner_wall_filament_id.value)) return 1; return multiplier; } +// ORCA: split wall layer heights. Effective wall pitch multipliers of the two wall classes: the +// conforming multipliers of their filaments' explicit preferred heights (0 = no explicit +// preference; inner_m is 0 when the region prints no inner walls). When both are explicit but do +// not divide evenly, the "split_wall_adjust" option moves the selected class's wall-only height +// to the nearest divisor or multiple of the other one in the selected direction, so the walls +// can still split (or merge, when the adjustment lands on the other class's height). Unlike the +// preferred heights themselves, an adjusted height is hard-bounded by the adjusted filament's +// min/max layer heights and bore: without a legal candidate in the chosen direction the +// multipliers stay unadjusted and the walls merge at the lower height as usual. +// Returns false when a mixed (virtual) wall filament forbids wall combining altogether. +bool PrintObject::wall_effective_multipliers(const PrintRegion ®ion, unsigned int &outer_m, unsigned int &inner_m) const +{ + const PrintRegionConfig &config = region.config(); + outer_m = inner_m = 0; + const int num_filaments = (int)m_print->config().filament_diameter.size(); + if (config.wall_loops.value > 0 && + (std::max(0, config.outer_wall_filament_id.value - 1) >= num_filaments || + (region_prints_inner_walls(config) && std::max(0, config.inner_wall_filament_id.value - 1) >= num_filaments))) + // Snapmaker mixed filament: virtual mixed ids resolve to a different physical extruder + // per layer, no stable pitch exists for the walls. + return false; + auto conforming_multiplier = [this](int filament_id) -> unsigned int { + if (this->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)) <= 0.) + return 0; + return this->layer_height_multiplier_for_filament((unsigned int)std::max(0, filament_id)); + }; + outer_m = config.wall_loops.value > 0 ? conforming_multiplier(config.outer_wall_filament_id.value) : 0; + inner_m = config.wall_loops.value > 0 && region_prints_inner_walls(config) ? + conforming_multiplier(config.inner_wall_filament_id.value) : 0; + if (! m_config.split_wall_adjust.value || outer_m == 0 || inner_m == 0 || + std::max(outer_m, inner_m) % std::min(outer_m, inner_m) == 0) + return true; + const bool adjust_outer = m_config.split_wall_adjust_filament.value == wsfOuterWall; + unsigned int &adjusted = adjust_outer ? outer_m : inner_m; + const unsigned int other = adjust_outer ? inner_m : outer_m; + const int filament_id = adjust_outer ? config.outer_wall_filament_id.value : config.inner_wall_filament_id.value; + const PrintConfig &print_config = m_print->config(); + // The filament index is the extruder index on classic multi-tool printers. + const size_t extruder_idx = size_t((unsigned int)std::max(1, filament_id) - 1); + const double h = m_config.layer_height.value; + const double min_lh = print_config.min_layer_height.get_at(extruder_idx); + const double max_lh = print_config.max_layer_height.get_at(extruder_idx); + const double bore = print_config.nozzle_diameter.get_at(extruder_idx); + auto legal = [&](unsigned int m) { + // A candidate height divides the other class's pitch or is a whole multiple of it (equal + // merges the walls at that height), and it honors the adjusted filament's limits. + if (other % m != 0 && m % other != 0) + return false; + const double height = m * h; + return height <= bore + EPSILON && height >= min_lh - EPSILON && + (max_lh <= EPSILON || height <= max_lh + EPSILON); + }; + if (m_config.split_wall_adjust_direction.value == wsdIncrease) { + for (unsigned int m = adjusted + 1, limit = (unsigned int)std::floor(bore / h + EPSILON); m <= limit; ++m) + if (legal(m)) { adjusted = m; return true; } + } else { + for (unsigned int m = adjusted - 1; m >= 1; --m) + if (legal(m)) { adjusted = m; return true; } + } + // No legal height in the chosen direction: leave the preferences unadjusted. + return true; +} + +// ORCA: split wall layer heights. The finer wall class prints on every fine-run top, the coarser +// once per coarse/fine fine runs, so their tops stay flush. Splitting happens whenever both wall +// classes' effective heights are explicit, unequal and divide evenly; otherwise the walls keep +// printing together at the lower height (wall_layer_height_multiplier()'s min-merge). +bool PrintObject::wall_split_pitches(const PrintRegion ®ion, unsigned int &fine, unsigned int &coarse, bool &coarse_is_outer) const +{ + unsigned int outer_m = 0, inner_m = 0; + if (! this->wall_effective_multipliers(region, outer_m, inner_m)) + // Mixed (virtual) filament ids never combine. + return false; + // Split needs an explicit preference on both wall classes; a preference-less wall filament + // follows the other one at a single merged pitch as usual. + if (outer_m == 0 || inner_m == 0 || outer_m == inner_m) + return false; + fine = std::min(outer_m, inner_m); + coarse = std::max(outer_m, inner_m); + if (coarse % fine != 0) + return false; + coarse_is_outer = outer_m > inner_m; + return true; +} + bool PrintObject::has_combined_layer_regions() const { for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index a39125ec27e..8fb5b1c4bbe 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -891,12 +891,23 @@ void PrintObject::apply_extruder_layer_heights() // Walls-only pitch (see wall_layer_height_multiplier()): the region prints every layer, only // its walls combine. Mutually exclusive with a whole-region multiplier > 1. std::vector wall_multipliers(this->num_printing_regions(), 1); + // Split wall layer heights (see wall_split_pitches()): the wall min-merge above already + // equals the fine class's pitch; the coarse class additionally combines to its own runs. + std::vector split_coarses(this->num_printing_regions(), 1); + std::vector split_outers(this->num_printing_regions(), 0); bool any_combined = false; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { multipliers[region_id] = this->region_layer_height_multiplier(this->printing_region(region_id)); - if (multipliers[region_id] <= 1) + if (multipliers[region_id] <= 1) { wall_multipliers[region_id] = this->wall_layer_height_multiplier(this->printing_region(region_id)); - any_combined |= multipliers[region_id] > 1 || wall_multipliers[region_id] > 1; + unsigned int fine = 0, coarse = 0; + bool coarse_is_outer = false; + if (this->wall_split_pitches(this->printing_region(region_id), fine, coarse, coarse_is_outer)) { + split_coarses[region_id] = coarse; + split_outers[region_id] = coarse_is_outer; + } + } + any_combined |= multipliers[region_id] > 1 || wall_multipliers[region_id] > 1 || split_coarses[region_id] > 1; } if (! any_combined) return; @@ -925,9 +936,10 @@ void PrintObject::apply_extruder_layer_heights() for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { // Walls-only mode marks the runs on the LayerRegions for make_perimeters() instead of // moving any slices: every layer keeps its geometry, fills and surfaces. - const bool walls_only = wall_multipliers[region_id] > 1; + const bool split = split_coarses[region_id] > 1; + const bool walls_only = wall_multipliers[region_id] > 1 || split; const size_t mult = walls_only ? wall_multipliers[region_id] : multipliers[region_id]; - if (mult <= 1) + if (mult <= 1 && ! split) continue; // Shapes deviating by less than this fraction of the region's nozzle diameter are considered // identical; the deviations swallowed stay below what printing N layers at once causes anyway. @@ -957,7 +969,9 @@ void PrintObject::apply_extruder_layer_heights() size_t min_run = 1; if (min_layer_height > m_config.layer_height.value + EPSILON) min_run = std::min(mult, (size_t)std::ceil(min_layer_height / m_config.layer_height.value - EPSILON)); - size_t idx = first_idx; + // With a fine multiplier of 1 (split with the finer wall at the object layer height) + // there are no fine runs to walk; only the coarse pass below applies. + size_t idx = mult > 1 ? first_idx : m_layers.size(); while (idx < m_layers.size()) { m_print->throw_if_canceled(); ExPolygons merged = to_expolygons(m_layers[idx]->regions()[region_id]->slices.surfaces); @@ -1088,6 +1102,61 @@ void PrintObject::apply_extruder_layer_heights() // top / bottom detection of the other regions, brim, supports and overhang handling. idx = commit_top + 1; } + // ORCA: split wall layer heights - group the fine cadence into coarse runs of + // split_coarses[region_id] layers and mark them for LayerRegion::make_perimeters(): the + // coarse wall class extrudes once per coarse run at the full run height and follows the + // fine cadence wherever no coarse run forms (both walls then print at the lower pitch, + // like the min-merge fallback). + if (split) { + const size_t coarse = split_coarses[region_id]; + const size_t fine = std::max(1, mult); + size_t bottom = first_idx; + while (bottom + coarse <= m_layers.size()) { + m_print->throw_if_canceled(); + // When the fine class combines, a coarse run must span whole fine runs so both + // classes' tops stay flush: every expected fine-run top must carry a full run. + bool aligned = true; + if (fine > 1) + for (size_t top = bottom + fine - 1; aligned && top < bottom + coarse; top += fine) + aligned = m_layers[top]->regions()[region_id]->wall_combined_count() == fine; + if (! aligned) { + ++ bottom; + continue; + } + // Uniform layer heights, the region present everywhere, and the whole span's + // shape within the run tolerance (mirrors the fine walk above). + ExPolygons merged = to_expolygons(m_layers[bottom]->regions()[region_id]->slices.surfaces); + Polygons unioned = to_polygons(merged); + bool valid = ! merged.empty(); + for (size_t i = bottom + 1; valid && i < bottom + coarse; ++ i) { + const ExPolygons expolys = to_expolygons(m_layers[i]->regions()[region_id]->slices.surfaces); + merged = expolys.empty() ? ExPolygons() : intersection_ex(expolys, merged); + polygons_append(unioned, to_polygons(expolys)); + valid = ! merged.empty() && std::abs(m_layers[i]->height - m_layers[bottom]->height) <= EPSILON; + } + if (valid) + valid = opening(diff(union_(unioned), offset(merged, tolerance)), 0.5f * tolerance).empty(); + // The coarse walls must rest on the object below the whole run, like the fine walk. + if (valid) + if (const Layer *below = m_layers[bottom]->lower_layer; below != nullptr && + ! opening_ex(diff_ex(merged, below->lslices, ApplySafetyOffset::Yes), 0.5f * tolerance).empty()) + valid = false; + if (! valid) { + bottom += fine; + continue; + } + const size_t top = bottom + coarse - 1; + double split_height = 0.; + for (size_t i = bottom; i <= top; ++ i) + split_height += m_layers[i]->height; + for (size_t i = bottom; i <= top; ++ i) { + LayerRegion *layerm = m_layers[i]->regions()[region_id]; + layerm->m_wall_split_count = i == top ? (unsigned short)coarse : 0; + layerm->m_wall_split_height = split_height; + } + bottom = top + 1; + } + } m_print->throw_if_canceled(); } } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index d9d106bea03..28aa8bcf54f 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -556,6 +556,12 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co "inner_wall_speed", "outer_wall_speed", "small_perimeter_speed", "small_perimeter_threshold" }) toggle_field(el, have_perimeters); + // ORCA: split wall layer heights - the adjustment target and direction only matter while + // the adjustment itself is enabled. + const bool split_wall_adjust = config->opt_bool("split_wall_adjust"); + toggle_line("split_wall_adjust_filament", split_wall_adjust); + toggle_line("split_wall_adjust_direction", split_wall_adjust); + bool have_infill = config->option("sparse_infill_density")->value > 0; // sparse_infill_filament_id uses the same logic as in Print::extruders() for (auto el : { "sparse_infill_pattern", "infill_combination", diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a6b6620784a..5635353cec3 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2576,6 +2576,9 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Filament for Features"), L"param_filament_for_features"); optgroup->append_single_option_line("outer_wall_filament_id", "multimaterial_settings_filament_for_features#walls"); optgroup->append_single_option_line("inner_wall_filament_id", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("split_wall_adjust", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("split_wall_adjust_filament", "multimaterial_settings_filament_for_features#walls"); + optgroup->append_single_option_line("split_wall_adjust_direction", "multimaterial_settings_filament_for_features#walls"); optgroup->append_single_option_line("sparse_infill_filament_id", "multimaterial_settings_filament_for_features#infill"); optgroup->append_single_option_line("internal_solid_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); optgroup->append_single_option_line("top_surface_filament_id", "multimaterial_settings_filament_for_features#solid-infill"); diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index acb3d72bb3c..6cee541f29b 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -1038,3 +1038,204 @@ SCENARIO("Disagreeing wall preferences meet at the lower height", "[MultiNozzleL } } } + +// Heights of the wall extrusions per wall class, classified like the G-code dispatch +// (perimeter_entity_uses_outer_wall_filament()). Bridges keep their nozzle-derived flow height +// and are skipped, like collect_path_heights(). +static void collect_wall_class_heights(const ExtrusionEntityCollection &collection, + std::vector &outer_heights, std::vector &inner_heights) +{ + for (const ExtrusionEntity *entity : collection.entities) { + if (auto *sub = dynamic_cast(entity)) { + collect_wall_class_heights(*sub, outer_heights, inner_heights); + continue; + } + std::vector &dst = perimeter_entity_uses_outer_wall_filament(*entity) ? outer_heights : inner_heights; + if (auto *loop = dynamic_cast(entity)) { + for (const ExtrusionPath &path : loop->paths) + if (path.role() != erBridgeInfill && path.role() != erInternalBridgeInfill && path.role() != erOverhangPerimeter) + dst.emplace_back(path.height); + } else if (auto *multi_path = dynamic_cast(entity)) { + for (const ExtrusionPath &path : multi_path->paths) + dst.emplace_back(path.height); + } else if (auto *path = dynamic_cast(entity)) + dst.emplace_back(path->height); + } +} + +// Wall heights of the whole object bucketed per class: `tall` counts the layers where a class +// prints its own pitch (the first expected entry); any height outside the class's expected set +// (the finer entries: fine cadence, first-layer and cap fallbacks) counts as `bad`. +struct WallHeightCounts { size_t outer_tall = 0, inner_tall = 0, bad = 0; }; +static WallHeightCounts count_wall_heights(const PrintObject &object, + const std::vector &outer_expected, + const std::vector &inner_expected) +{ + auto tally = [](const std::vector &heights, const std::vector &expected, size_t &tall_layers, size_t &bad) { + bool tall = false; + for (float height : heights) { + if (std::abs(height - expected.front()) < 1e-3f) + tall = true; + else if (std::none_of(expected.begin() + 1, expected.end(), + [height](float e) { return std::abs(height - e) < 1e-3f; })) + ++ bad; + } + tall_layers += tall; + }; + WallHeightCounts counts; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector outer_heights, inner_heights; + collect_wall_class_heights(object.get_layer(int(idx))->get_region(0)->perimeters, outer_heights, inner_heights); + tally(outer_heights, outer_expected, counts.outer_tall, counts.bad); + tally(inner_heights, inner_expected, counts.inner_tall, counts.bad); + } + return counts; +} + +SCENARIO("Split wall layer heights print each wall class at its own pitch", "[MultiNozzleLayerHeight]") { + GIVEN("Outer walls prefer 0.48 mm and inner walls 0.24 mm - divisible heights split automatically") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(2)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("outer walls print once per 4 layers at 0.48 mm, inner walls once per 2 at 0.24 mm") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + // The disagreement is intentional here; no conflict warning. + CHECK(warning.string.find("prefer different layer heights") == std::string::npos); + print.process(); + + // Outside committed coarse runs the outer walls follow the fine cadence; the first + // layer and fallback areas keep the object layer height. 83 layers: ~20 coarse runs + // of 4 and ~40 fine runs of 2 above the first layer. + const PrintObject &object = *print.objects().front(); + const WallHeightCounts counts = count_wall_heights(object, {0.48f, 0.24f, 0.12f}, {0.24f, 0.12f}); + CHECK(counts.outer_tall >= 15); + CHECK(counts.inner_tall >= 30); + CHECK(counts.bad == 0); + size_t fill_bad_heights = 0; + for (size_t idx = 0; idx < object.layer_count(); ++ idx) { + std::vector fill_heights; + collect_path_heights(object.get_layer(int(idx))->get_region(0)->fills, fill_heights); + for (float height : fill_heights) + if (std::abs(height - 0.12f) > 1e-3f) + ++ fill_bad_heights; + } + CHECK(fill_bad_heights == 0); + } + } + GIVEN("The finer wall class at the object layer height itself") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(1)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("inner walls print every 0.12 mm layer while outer walls combine to 0.48 mm") { + REQUIRE(print.validate().string.empty()); + print.process(); + + const WallHeightCounts counts = count_wall_heights(*print.objects().front(), {0.48f, 0.12f}, {0.12f}); + CHECK(counts.outer_tall >= 15); + CHECK(counts.bad == 0); + } + } + GIVEN("Wall preferences that do not divide evenly (0.48 mm and 0.36 mm), no adjustment") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(3)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("the walls fall back to printing together at the lower height, with the conflict warning") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + CHECK(warning.string.find("prefer different layer heights") != std::string::npos); + print.process(); + + const WallHeightCounts counts = count_wall_heights(*print.objects().front(), + {0.36f, 0.24f, 0.12f}, {0.36f, 0.24f, 0.12f}); + CHECK(counts.outer_tall >= 15); + CHECK(counts.bad == 0); + } + } +} + +// Non-divisible wall preferences reconciled by "split_wall_adjust": outer walls prefer 0.48 mm +// (filament 4, multiplier 4) and inner walls 0.36 mm (filament 3, multiplier 3) over 0.12 mm +// object layers. The selected wall class moves to the nearest divisor / multiple of the other +// in the selected direction, hard-bounded by its filament's layer height limits. +static DynamicPrintConfig wall_adjust_config(WallSplitFilament filament, WallSplitDirection direction) +{ + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(4)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(3)); + config.set_key_value("split_wall_adjust", new ConfigOptionBool(true)); + config.set_key_value("split_wall_adjust_filament", new ConfigOptionEnum(filament)); + config.set_key_value("split_wall_adjust_direction", new ConfigOptionEnum(direction)); + return config; +} + +SCENARIO("Adjusting a wall layer height reconciles non-divisible wall preferences", "[MultiNozzleLayerHeight]") { + GIVEN("The inner walls adjusted downwards") { + DynamicPrintConfig config = wall_adjust_config(wsfInnerWall, wsdDecrease); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("inner walls print 0.24 mm - the largest divisor of 0.48 mm below 0.36 mm - and the walls split") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + CHECK(warning.string.find("was adjusted") != std::string::npos); + CHECK(warning.string.find("prefer different layer heights") == std::string::npos); + print.process(); + + // In particular no 0.36 mm: the raw inner preference is off for walls. + const WallHeightCounts counts = count_wall_heights(*print.objects().front(), + {0.48f, 0.24f, 0.12f}, {0.24f, 0.12f}); + CHECK(counts.outer_tall >= 15); + CHECK(counts.inner_tall >= 30); + CHECK(counts.bad == 0); + } + } + GIVEN("The inner walls adjusted upwards") { + DynamicPrintConfig config = wall_adjust_config(wsfInnerWall, wsdIncrease); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("the inner walls land on the outer walls' 0.48 mm and the walls merge there") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + CHECK(warning.string.find("was adjusted") != std::string::npos); + CHECK(warning.string.find("prefer different layer heights") == std::string::npos); + print.process(); + + // Merged walls never print the raw 0.36 mm inner preference (bad == 0 covers it). + const WallHeightCounts counts = count_wall_heights(*print.objects().front(), + {0.48f, 0.24f, 0.12f}, {0.48f, 0.24f, 0.12f}); + CHECK(counts.inner_tall >= 15); + CHECK(counts.bad == 0); + } + } + GIVEN("The outer walls adjusted upwards, where the next candidate breaks the layer height limit") { + // The smallest multiple of the inner 0.36 mm above the outer 0.48 mm is 0.72 mm, over + // filament 4's 0.64 mm maximum layer height - a hard bound for adjustments. + DynamicPrintConfig config = wall_adjust_config(wsfOuterWall, wsdIncrease); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("no adjustment happens: the walls merge at the lower height with the conflict warning") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + CHECK(warning.string.find("was adjusted") == std::string::npos); + CHECK(warning.string.find("prefer different layer heights") != std::string::npos); + print.process(); + + const WallHeightCounts counts = count_wall_heights(*print.objects().front(), + {0.36f, 0.24f, 0.12f}, {0.36f, 0.24f, 0.12f}); + CHECK(counts.outer_tall >= 15); + CHECK(counts.bad == 0); + } + } +} From 34733301ea6b4377cdae36934291519300737d55 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:23:57 +0000 Subject: [PATCH 11/43] feat: fixed extruder layer height mode Add a third "Thick layer regions" choice next to Consistent and Adaptive: Fixed. Regions assigned to a thicker-pitch extruder always print at the extruder layer height: the run walk skips the shape drift tolerance and the rest-on-below overhang fallback, so curved boundaries become steps of the run shape instead of dropping to finer layers. Only geometry that cannot span a whole run keeps finer heights: the first layer, region tops, lateral jumps, and columns whose surviving intersection is too thin to carry a bead of the region's nozzle. The thick layer tolerance field grays out in fixed mode, as it no longer participates. The step faces such combined groups expose are classified correctly: a group prints only its layers' common shape, and the combined-away layers' geometry outside it becomes the group's step. Surface detection compared against Layer::lslices, which carry the uncombined shape, so those step faces counted as covered - no top skin or solid backing formed and sparse infill showed through on stepped slopes, and bridges over such steps went undetected. Each combined-away layer now remembers its unprinted remainder; detection classifies the faces it exposes as top / bottom on the printed layers around it, and a combined group's own bridges check the layer below the whole group. Covered by pyramid scenarios comparing fixed against consistent mode and a stepped-cube regression test for the step top surfaces. --- src/libslic3r/Layer.hpp | 5 + src/libslic3r/PrintConfig.cpp | 9 +- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 23 +++- src/libslic3r/PrintObjectSlice.cpp | 41 ++++-- src/slic3r/GUI/ConfigManipulation.cpp | 5 + .../test_multi_nozzle_layer_height.cpp | 117 +++++++++++++++++- 7 files changed, 178 insertions(+), 23 deletions(-) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index c699849eaac..20c06aa327c 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -121,6 +121,10 @@ class LayerRegion // Layer right below the covered group; replaces Layer::lower_layer for overhang / bridge // detection of combined regions. May be nullptr. const Layer* combined_lower_layer() const; + // On combined-away layers: the part of this layer's own shape that the group's committed + // shape does not print. Layer::lslices still carry the uncombined shape, so surface + // detection uses this remainder to classify the exposed step faces as top / bottom. + const ExPolygons& combined_away_exposed() const { return m_combined_away_exposed; } // ORCA: walls-only pitch (PrintObject::wall_layer_height_multiplier()). Number of object // layers whose walls this region's perimeters cover here: > 1 on the top layer of a wall run @@ -164,6 +168,7 @@ class LayerRegion // ORCA: set by PrintObject::apply_extruder_layer_heights(), see combined_layer_count() / combined_height(). unsigned short m_combined_layer_count { 1 }; double m_combined_height { 0. }; + ExPolygons m_combined_away_exposed; // ORCA: set by PrintObject::apply_extruder_layer_heights(), see wall_combined_count() / wall_combined_height(). unsigned short m_wall_combined_count { 1 }; double m_wall_combined_height { 0. }; diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index c4a9df2a222..3c28126f391 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -298,6 +298,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(EnsureVerticalShellThickness) static t_config_enum_values s_keys_map_ExtruderLayerHeightMode{ { "consistent", int(ExtruderLayerHeightMode::elhmConsistent) }, { "adaptive", int(ExtruderLayerHeightMode::elhmAdaptive) }, + { "fixed", int(ExtruderLayerHeightMode::elhmFixed) }, }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(ExtruderLayerHeightMode) @@ -4006,12 +4007,18 @@ void PrintConfigDef::init_fff_params() "gives the most uniform walls.\n" "Adaptive: runs may also be combined at intermediate multiples of the object layer " "height, so more of the part prints with thicker layers, at the price of bands of " - "varying layer heights on curved part boundaries."); + "varying layer heights on curved part boundaries.\n" + "Fixed: parts always print at the extruder layer height, even where the shape changes " + "across the combined layers or overhangs; curved boundaries turn into steps and detail " + "finer than the thick layers is lost. Only geometry too short for a whole thick layer " + "(part tops and the first layer) prints thinner."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.push_back("consistent"); def->enum_values.push_back("adaptive"); + def->enum_values.push_back("fixed"); def->enum_labels.push_back(L("Consistent")); def->enum_labels.push_back(L("Adaptive")); + def->enum_labels.push_back(L("Fixed")); def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(elhmConsistent)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 4ee333f92c1..06c48090de1 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -199,6 +199,7 @@ enum EnsureVerticalShellThickness { enum ExtruderLayerHeightMode { elhmConsistent, elhmAdaptive, + elhmFixed, }; // ORCA: split wall layer heights - which wall class gets its wall-only layer height adjusted diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 4b57b99c2bc..1e67f014c62 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1412,6 +1412,11 @@ void PrintObject::detect_surfaces_type() ExPolygons upper_slices = interface_shells ? diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); + // ORCA: per-extruder layer height: geometry combined away above extrudes as + // part of a group further up; the step faces it exposes here are real top + // surfaces even though lslices still carry the uncombined shape. + if (const ExPolygons &exposed = upper_layer->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) + upper_slices = union_ex(upper_slices, intersection_ex(layerm_slices_surfaces, exposed)); surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid @@ -1434,12 +1439,18 @@ void PrintObject::detect_surfaces_type() surface_type_bottom_other); #else // Any surface lying on the void is a true bottom bridge (an overhang) - surfaces_append( - bottom, - opening_ex( - diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes), - offset), - surface_type_bottom_other); + ExPolygons unsupported = diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes); + // ORCA: per-extruder layer height: geometry combined away below extrudes as + // part of a group whose top lies underneath this layer; anything over its + // leftover band hangs over the step it exposes. A combined group itself + // reaches down to the layer below the whole group; its bridges hang over + // that layer, not over the combined-away one right below. + if (const ExPolygons &exposed = lower_layer->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) + unsupported = union_ex(unsupported, intersection_ex(layerm_slices_surfaces, exposed)); + if (const unsigned short count = layerm->combined_layer_count(); count > 1 && idx_layer >= size_t(count)) + unsupported = union_ex(unsupported, + diff_ex(layerm_slices_surfaces, m_layers[idx_layer - count]->lslices, ApplySafetyOffset::Yes)); + surfaces_append(bottom, opening_ex(unsupported, offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces // lying on other slices not belonging to this region if (interface_shells) { diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 8fb5b1c4bbe..393a3f21bc6 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -925,8 +925,11 @@ void PrintObject::apply_extruder_layer_heights() return; // Adaptive mode commits runs cut short by shape drift at intermediate heights instead of - // falling back to the object layer height. + // falling back to the object layer height. Fixed mode has no drift concept at all: runs grow + // to the full pitch wherever the region exists with a common shape, ignoring the tolerance + // and overhangs, so the extruder never drops to finer layers (boundaries become steps). const bool adaptive = m_config.extruder_layer_height_mode.value == elhmAdaptive; + const bool fixed = m_config.extruder_layer_height_mode.value == elhmFixed; // The filament index is the extruder index on classic multi-tool printers (same mapping as // extruder_preferred_layer_height() and Print::validate()). const PrintConfig &print_config = m_print->config(); @@ -997,14 +1000,22 @@ void PrintObject::apply_extruder_layer_heights() if (next_merged.empty()) // Laterally displaced (a painted-boundary step): this column ends, the next anchors above. break; - Polygons next_unioned = unioned; - polygons_append(next_unioned, to_polygons(expolys)); - if (! opening(diff(union_(next_unioned), offset(next_merged, tolerance)), 0.5f * tolerance).empty()) { - shape_drifted = true; - break; + if (fixed) { + // Even fixed mode ends a column where the shape displaces so far sideways that + // the surviving intersection cannot carry a bead of the region's nozzle anymore: + // committing such a sliver would erase the layers' real geometry, not step it. + if (opening_ex(next_merged, 0.25f * float(scale_(nozzle_diameter))).empty()) + break; + } else { + Polygons next_unioned = unioned; + polygons_append(next_unioned, to_polygons(expolys)); + if (! opening(diff(union_(next_unioned), offset(next_merged, tolerance)), 0.5f * tolerance).empty()) { + shape_drifted = true; + break; + } + unioned = std::move(next_unioned); } merged = std::move(next_merged); - unioned = std::move(next_unioned); top_idx = next; } // Full runs and clean caps commit as grown; a run cut short by shape drift falls back to @@ -1062,9 +1073,9 @@ void PrintObject::apply_extruder_layer_heights() } } // The run must rest on the object below, else the finer per-layer bridge / overhang path - // is needed. Skipped when min_run > 1: no finer path exists then, overhangs are detected - // against the layer below the whole run instead. - if (min_run <= 1) + // is needed. Skipped when min_run > 1 or in fixed mode: no finer path is allowed then, + // overhangs are detected against the layer below the whole run instead. + if (min_run <= 1 && ! fixed) if (const Layer *below = m_layers[idx]->lower_layer; below != nullptr && ! opening_ex(diff_ex(merged, below->lslices, ApplySafetyOffset::Yes), 0.5f * tolerance).empty()) { ++ idx; @@ -1092,8 +1103,13 @@ void PrintObject::apply_extruder_layer_heights() top_layerm->slices.set(std::move(merged), stInternal); top_layerm->m_combined_layer_count = (unsigned short)(commit_top - idx + 1); top_layerm->m_combined_height = combined_height; + const ExPolygons committed = to_expolygons(top_layerm->slices.surfaces); for (size_t i = idx; i < commit_top; ++ i) { LayerRegion *combined_away = m_layers[i]->regions()[region_id]; + // The run prints only its common shape; this layer's own geometry outside it is + // approximated by the run's step. Surface detection classifies the step faces it + // exposes via this remainder (lslices still carry the uncombined shape). + combined_away->m_combined_away_exposed = diff_ex(to_expolygons(combined_away->slices.surfaces), committed); combined_away->slices.clear(); // 0 marks "extrudes at the run top above", as opposed to genuinely absent geometry. combined_away->m_combined_layer_count = 0; @@ -1135,9 +1151,10 @@ void PrintObject::apply_extruder_layer_heights() valid = ! merged.empty() && std::abs(m_layers[i]->height - m_layers[bottom]->height) <= EPSILON; } if (valid) - valid = opening(diff(union_(unioned), offset(merged, tolerance)), 0.5f * tolerance).empty(); + valid = fixed ? ! opening_ex(merged, 0.25f * float(scale_(nozzle_diameter))).empty() + : opening(diff(union_(unioned), offset(merged, tolerance)), 0.5f * tolerance).empty(); // The coarse walls must rest on the object below the whole run, like the fine walk. - if (valid) + if (valid && ! fixed) if (const Layer *below = m_layers[bottom]->lower_layer; below != nullptr && ! opening_ex(diff_ex(merged, below->lslices, ApplySafetyOffset::Yes), 0.5f * tolerance).empty()) valid = false; diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 28aa8bcf54f..3240dcafcb3 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -550,6 +550,11 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co apply(config, &new_conf); } + // ORCA: per-extruder layer height: the thick layer tolerance only drives the consistent / + // adaptive drift checks; fixed mode ignores it. + toggle_field("extruder_layer_height_tolerance", + config->opt_enum("extruder_layer_height_mode") != elhmFixed); + bool have_perimeters = config->opt_int("wall_loops") > 0; for (auto el : { "extra_perimeters_on_overhangs", "ensure_vertical_shell_thickness", "detect_thin_wall", "detect_overhang_wall", "seam_position", "staggered_inner_seams", "wall_sequence", "outer_wall_line_width", diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index 6cee541f29b..3190bffca91 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -95,13 +95,15 @@ static DynamicPrintConfig two_extruder_config(double second_extruder_layer_heigh return config; } -// One object made of two 20x20 mm cube parts side by side; the second part prints with filament 2. -// The parts are 20 * z_scale mm tall (10 mm by default). -static void init_two_part_print(Print &print, Model &model, const DynamicPrintConfig &config, float z_scale = 0.5f) +// One object made of two 20x20 mm parts side by side; the second part prints with filament 2. +// The parts are z-scaled by z_scale (cubes are 10 mm tall by default). +static void init_two_part_print(Print &print, Model &model, const DynamicPrintConfig &config, float z_scale = 0.5f, + TestMesh coarse_shape = TestMesh::cube_20x20x20) { TriangleMesh fine_mesh = mesh(TestMesh::cube_20x20x20); fine_mesh.scale(Vec3f(1.f, 1.f, z_scale)); - TriangleMesh coarse_mesh = fine_mesh; + TriangleMesh coarse_mesh = mesh(coarse_shape); + coarse_mesh.scale(Vec3f(1.f, 1.f, z_scale)); coarse_mesh.translate(30.f, 0.f, 0.f); ModelObject *object = model.add_object(); @@ -221,6 +223,113 @@ SCENARIO("Per-extruder layer height combines region layers", "[MultiNozzleLayerH } } +SCENARIO("Fixed mode always prints the extruder layer height", "[MultiNozzleLayerHeight]") { + // A 10 mm tall pyramid on the coarse extruder: its outline drifts by 0.2 mm per edge on every + // 0.2 mm layer, far past the default thick layer tolerance (10 % of the 0.6 mm nozzle), so + // consistent mode falls back to the object layer height everywhere. + GIVEN("A pyramid part whose outline drifts past the thick layer tolerance on every layer") { + DynamicPrintConfig config = two_extruder_config(0.4); + // Count coarse extrusion heights well below the apex, where slices stay large enough to print. + auto coarse_height_counts = [](Print &print, size_t &at_pitch, size_t &at_base, size_t &odd_layers) { + print.process(); + const PrintObject &object = *print.objects().front(); + int fine_region, coarse_region; + find_regions(object, fine_region, coarse_region); + REQUIRE(coarse_region >= 0); + at_pitch = at_base = odd_layers = 0; + for (size_t idx = 1; idx <= 40; ++ idx) { + const std::vector heights = region_path_heights(object.get_layer(int(idx))->get_region(coarse_region)); + if (idx % 2 == 1 && ! heights.empty()) + ++ odd_layers; + for (float height : heights) { + if (std::abs(height - 0.4) < 1e-3) + ++ at_pitch; + else if (std::abs(height - 0.2) < 1e-3) + ++ at_base; + } + } + }; + WHEN("thick layer regions are Consistent") { + Print print; + Model model; + init_two_part_print(print, model, config, 0.25f, TestMesh::pyramid); + THEN("the drift keeps the coarse part at the object layer height") { + REQUIRE(print.validate().string.empty()); + size_t at_pitch, at_base, odd_layers; + coarse_height_counts(print, at_pitch, at_base, odd_layers); + CHECK(at_pitch == 0); + CHECK(at_base > 0); + CHECK(odd_layers == 20); + } + } + WHEN("thick layer regions are Fixed") { + config.option>("extruder_layer_height_mode", true)->value = elhmFixed; + Print print; + Model model; + init_two_part_print(print, model, config, 0.25f, TestMesh::pyramid); + THEN("every coarse extrusion above the first layer keeps the extruder layer height") { + REQUIRE(print.validate().string.empty()); + size_t at_pitch, at_base, odd_layers; + coarse_height_counts(print, at_pitch, at_base, odd_layers); + CHECK(at_pitch > 0); + CHECK(at_base == 0); + CHECK(odd_layers == 0); + } + } + } +} + +SCENARIO("Fixed mode keeps top surfaces on combined steps", "[MultiNozzleLayerHeight]") { + // A shoulder that ends mid-run: the wide cube's exposed ring is combined away with its layer + // (the run prints only the common shape), so it must reappear as a top surface on the printed + // layer below it - otherwise the step carries bare sparse infill (the benchy cabin roof case). + GIVEN("A wide cube ending mid-run with a narrower cube on top") { + auto top_area = [](ExtruderLayerHeightMode mode) { + DynamicPrintConfig config = two_extruder_config(0.4); + config.option>("extruder_layer_height_mode", true)->value = mode; + Print print; + Model model; + TriangleMesh base = mesh(TestMesh::cube_20x20x20); + base.scale(Vec3f(1.f, 1.f, 0.25f)); // 5 mm tall: the shoulder ends mid-run + TriangleMesh boss = mesh(TestMesh::cube_20x20x20); + boss.scale(Vec3f(0.5f, 0.5f, 0.1f)); // 10 x 10 x 2 mm on top + boss.translate(5.f, 5.f, 5.f); + ModelObject *object = model.add_object(); + object->name = "stepped_cube"; + object->add_volume(std::move(base)); + object->add_volume(std::move(boss)); + object->config.set("extruder", 2); + object->add_instance(); + // This fork's arrangement engine rejects positions outside the (unset) plate even for + // an InfiniteBed; place the object at a fixed bed spot like init_two_part_print(). + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); + mo->ensure_on_bed(); + } + print.apply(model, config); + print.set_status_silent(); + REQUIRE(print.validate().string.empty()); + print.process(); + const PrintObject &po = *print.objects().front(); + double area = 0.; + for (size_t idx = 0; idx < po.layer_count(); ++ idx) + for (int r = 0; r < po.get_layer(int(idx))->region_count(); ++ r) + for (const Surface &surface : po.get_layer(int(idx))->get_region(r)->fill_surfaces.surfaces) + if (surface.surface_type == stTop) + area += unscale(unscale(surface.expolygon.area())); + return area; + }; + THEN("the combined print keeps most of the per-layer top surface area") { + const double per_layer = top_area(elhmConsistent); + const double combined = top_area(elhmFixed); + CAPTURE(per_layer, combined); + REQUIRE(per_layer > 100.); + REQUIRE(combined > 0.7 * per_layer); + } + } +} + SCENARIO("Per-extruder layer height respects the extruder's minimum layer height", "[MultiNozzleLayerHeight]") { GIVEN("A 0.6 mm preferred layer height with a 0.4 mm minimum, on a part height leaving a 1-layer tail") { DynamicPrintConfig config = two_extruder_config(0.6); From f238b800588ec0a2cc7d328ab0742d23eb8700e7 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Fri, 17 Jul 2026 17:43:06 +0000 Subject: [PATCH 12/43] feat: default thick layer regions to fixed with an 80% tolerance Fixed mode is the intended everyday behavior for per-extruder layer heights, so ship it as the default; the tolerance default rises to 80% so consistent and adaptive combine generously when selected (fixed ignores it). The test suite pins the previous consistent / 10% baseline explicitly, as its scenarios are written against it. --- src/libslic3r/PrintConfig.cpp | 4 ++-- tests/fff_print/test_multi_nozzle_layer_height.cpp | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 3c28126f391..be8c38f89ce 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -4020,7 +4020,7 @@ void PrintConfigDef::init_fff_params() def->enum_labels.push_back(L("Adaptive")); def->enum_labels.push_back(L("Fixed")); def->mode = comAdvanced; - def->set_default_value(new ConfigOptionEnum(elhmConsistent)); + def->set_default_value(new ConfigOptionEnum(elhmFixed)); def = this->add("extruder_layer_height_tolerance", coPercent); def->label = L("Thick layer tolerance"); @@ -4034,7 +4034,7 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->max = 100; def->mode = comAdvanced; - def->set_default_value(new ConfigOptionPercent(10)); + def->set_default_value(new ConfigOptionPercent(80)); def = this->add("slow_down_min_speed", coFloats); def->label = L("Min print speed"); diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index 3190bffca91..ae097904b19 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -92,6 +92,10 @@ static DynamicPrintConfig two_extruder_config(double second_extruder_layer_heigh // The default G-code flavor rejects relative extruder addressing without a G92 E0 layer-change // reset; this suite does not exercise the G-code writer, keep validation quiet. config.set_key_value("use_relative_e_distances", new ConfigOptionBool(false)); + // The scenarios build on consistent-mode expectations with a tight drift tolerance; the + // shipping defaults are fixed mode with a generous tolerance. + config.option>("extruder_layer_height_mode", true)->value = elhmConsistent; + config.set_key_value("extruder_layer_height_tolerance", new ConfigOptionPercent(10)); return config; } @@ -225,7 +229,7 @@ SCENARIO("Per-extruder layer height combines region layers", "[MultiNozzleLayerH SCENARIO("Fixed mode always prints the extruder layer height", "[MultiNozzleLayerHeight]") { // A 10 mm tall pyramid on the coarse extruder: its outline drifts by 0.2 mm per edge on every - // 0.2 mm layer, far past the default thick layer tolerance (10 % of the 0.6 mm nozzle), so + // 0.2 mm layer, far past the suite's 10 % thick layer tolerance (of the 0.6 mm nozzle), so // consistent mode falls back to the object layer height everywhere. GIVEN("A pyramid part whose outline drifts past the thick layer tolerance on every layer") { DynamicPrintConfig config = two_extruder_config(0.4); From 60844f359fec26c4682639c6a921a1d2b29a8518 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Wed, 29 Jul 2026 03:43:07 +0000 Subject: [PATCH 13/43] fix: bottom detection over combined groups Geometry starting inside a combined group could count as supported and print as sparse infill over air, depending on where the group phase fell. Track the groups' unprinted remainders (run top included) and use them in the unsupported-bottom check. --- src/libslic3r/Layer.hpp | 4 +- src/libslic3r/PrintObject.cpp | 9 +++- src/libslic3r/PrintObjectSlice.cpp | 2 + .../test_multi_nozzle_layer_height.cpp | 43 +++++++++++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index 20c06aa327c..41f711ff462 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -121,9 +121,7 @@ class LayerRegion // Layer right below the covered group; replaces Layer::lower_layer for overhang / bridge // detection of combined regions. May be nullptr. const Layer* combined_lower_layer() const; - // On combined-away layers: the part of this layer's own shape that the group's committed - // shape does not print. Layer::lslices still carry the uncombined shape, so surface - // detection uses this remainder to classify the exposed step faces as top / bottom. + // Shape of this layer that its combined group does not print; classifies exposed step faces. const ExPolygons& combined_away_exposed() const { return m_combined_away_exposed; } // ORCA: walls-only pitch (PrintObject::wall_layer_height_multiplier()). Number of object diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 1e67f014c62..e1f731fb93f 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1447,9 +1447,14 @@ void PrintObject::detect_surfaces_type() // that layer, not over the combined-away one right below. if (const ExPolygons &exposed = lower_layer->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) unsupported = union_ex(unsupported, intersection_ex(layerm_slices_surfaces, exposed)); - if (const unsigned short count = layerm->combined_layer_count(); count > 1 && idx_layer >= size_t(count)) + if (const unsigned short count = layerm->combined_layer_count(); count > 1 && idx_layer >= size_t(count)) { + // The below-group layer's own leftover band is unprinted too. + const Layer *below_group = m_layers[idx_layer - count]; unsupported = union_ex(unsupported, - diff_ex(layerm_slices_surfaces, m_layers[idx_layer - count]->lslices, ApplySafetyOffset::Yes)); + diff_ex(layerm_slices_surfaces, below_group->lslices, ApplySafetyOffset::Yes)); + if (const ExPolygons &exposed = below_group->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) + unsupported = union_ex(unsupported, intersection_ex(layerm_slices_surfaces, exposed)); + } surfaces_append(bottom, opening_ex(unsupported, offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces // lying on other slices not belonging to this region diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 393a3f21bc6..b70682edccc 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1100,10 +1100,12 @@ void PrintObject::apply_extruder_layer_heights() } // Commit: move the common shape to the top layer of the run, drop the layers below. LayerRegion *top_layerm = m_layers[commit_top]->regions()[region_id]; + ExPolygons top_remainder = to_expolygons(top_layerm->slices.surfaces); top_layerm->slices.set(std::move(merged), stInternal); top_layerm->m_combined_layer_count = (unsigned short)(commit_top - idx + 1); top_layerm->m_combined_height = combined_height; const ExPolygons committed = to_expolygons(top_layerm->slices.surfaces); + top_layerm->m_combined_away_exposed = diff_ex(top_remainder, committed); for (size_t i = idx; i < commit_top; ++ i) { LayerRegion *combined_away = m_layers[i]->regions()[region_id]; // The run prints only its common shape; this layer's own geometry outside it is diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index ae097904b19..ee97fde2ec2 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -334,6 +334,49 @@ SCENARIO("Fixed mode keeps top surfaces on combined steps", "[MultiNozzleLayerHe } } +SCENARIO("Fixed mode bridges lids that start inside a run", "[MultiNozzleLayerHeight]") { + // The runs spanning the lid's first layers print nothing there; it must still bridge. + GIVEN("A hollow tube capped by a lid whose bottom starts in the middle of a run") { + DynamicPrintConfig config = two_extruder_config(0.4); + config.option>("extruder_layer_height_mode", true)->value = elhmFixed; + Print print; + Model model; + TriangleMesh tube = mesh(TestMesh::cube_with_hole); // 20 x 20 x 10 mm, 10 mm hole through z + tube.scale(Vec3f(1.f, 1.f, 0.5f)); // 5 mm tall: the lid starts mid-run + TriangleMesh lid = mesh(TestMesh::cube_20x20x20); + lid.scale(Vec3f(1.f, 1.f, 0.1f)); + lid.translate(0.f, 0.f, 5.f); + ModelObject *object = model.add_object(); + object->name = "capped_tube"; + object->add_volume(std::move(tube)); + object->add_volume(std::move(lid)); + object->config.set("extruder", 2); + object->add_instance(); + // This fork's arrangement engine rejects positions outside the (unset) plate even for + // an InfiniteBed; place the object at a fixed bed spot like init_two_part_print(). + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); + mo->ensure_on_bed(); + } + print.apply(model, config); + print.set_status_silent(); + THEN("the lid interior is classified as an unsupported bottom") { + REQUIRE(print.validate().string.empty()); + print.process(); + const PrintObject &po = *print.objects().front(); + double bridge_area = 0.; + for (size_t idx = 0; idx < po.layer_count(); ++ idx) + for (int r = 0; r < po.get_layer(int(idx))->region_count(); ++ r) + for (const Surface &surface : po.get_layer(int(idx))->get_region(r)->fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge) + bridge_area += unscale(unscale(surface.expolygon.area())); + CAPTURE(bridge_area); + REQUIRE(bridge_area > 40.); + } + } +} + SCENARIO("Per-extruder layer height respects the extruder's minimum layer height", "[MultiNozzleLayerHeight]") { GIVEN("A 0.6 mm preferred layer height with a 0.4 mm minimum, on a part height leaving a 1-layer tail") { DynamicPrintConfig config = two_extruder_config(0.6); From da07e7d3138a8526abf22a20367317bfba2355b3 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Fri, 31 Jul 2026 14:38:42 +0000 Subject: [PATCH 14/43] fix: print region-boundary geometry over combined groups reliably Refactor the feature code for review: shared filament-to-extruder mapping helpers, deduplicated filters and lower-layer walks, folded duplicate blocks. No behavior change. At a filament boundary a region's per-layer geometry can lose its support to a neighboring region's combined group: the support prints later (at the run top) or never (dropped remainders). Classify bottoms over any region's unprinted remainder as unsupported bridges; drop pieces that would extrude into thin air before the covering pass exists so the column resumes - bridging, anchored - at the pass; where a piece's only anchor is the pass committing at its own layer, fill the object volume with the pass (welded into its shape) and resume fully supported on top of it. --- src/libslic3r/GCode/ToolOrdering.cpp | 23 +-- src/libslic3r/Layer.cpp | 30 ++-- src/libslic3r/Print.cpp | 94 +++++-------- src/libslic3r/Print.hpp | 7 + src/libslic3r/PrintConfig.cpp | 6 +- src/libslic3r/PrintConfig.hpp | 11 ++ src/libslic3r/PrintObject.cpp | 119 +++++++--------- src/libslic3r/PrintObjectSlice.cpp | 116 +++++++++++++-- src/slic3r/GUI/GUI_ObjectList.cpp | 22 +-- src/slic3r/GUI/Plater.cpp | 24 ++-- .../test_multi_nozzle_layer_height.cpp | 132 +++++++++++++++++- 11 files changed, 384 insertions(+), 200 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 9a7412851b2..43af289e9cf 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -378,7 +378,8 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c assert(region.config().bottom_surface_filament_id.value > 0); if (extrusions.has_infill()) { ExtrusionRole role = extrusions.entities.empty() ? erNone : extrusions.entities.front()->role(); - // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + // Gap fill inherits the filament of the surface it fills; derive the role from + // the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). if (role == erGapFill) for (const ExtrusionEntity *ee : extrusions.entities) if (ee->role() != erGapFill) { @@ -387,7 +388,8 @@ unsigned int LayerTools::extruder(const ExtrusionEntityCollection &extrusions, c } if (extrusions.has_solid_infill()) { ExtrusionRole solid_role = extrusions.role(); - // gap fill inherits the filament of the surface it fills; derive the role from the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). + // Gap fill inherits the filament of the surface it fills; derive the role from + // the first non-gap-fill entity (must match ToolOrdering::collect_extruders()). if (solid_role == erMixed) for (const ExtrusionEntity *ee : extrusions.entities) if (ee->role() != erGapFill) { @@ -885,7 +887,11 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto firstLayerExtruders.emplace_back(wall_ext); }; emplace_wall_filament((extruder_override == 0) ? region.config().outer_wall_filament_id.value : extruder_override, true); - // alternate_extra_wall should add an inner loop on odd layers (mirrors PerimeterGenerator's loop_number and the spiral gate of LayerRegion::make_perimeters()); layers dropping the loop again (only_one_wall_top / only_one_wall_first_layer) may reserve the filament unused, which merely costs a toolchange. + // alternate_extra_wall should add an inner loop on odd layers (mirrors + // PerimeterGenerator's loop_number and the spiral gate of + // LayerRegion::make_perimeters()); layers dropping the loop again + // (only_one_wall_top / only_one_wall_first_layer) may reserve the filament + // unused, which merely costs a toolchange. const bool spiral_vase_layer = object.print()->config().spiral_mode.value && layer->id() >= size_t(region.config().bottom_shell_layers.value) && layer->print_z >= region.config().bottom_shell_thickness - EPSILON; @@ -1206,10 +1212,10 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // and maybe other problems. We will therefore go through layer_tools and detect and fix this. // So, if there is a non-object layer starting with different extruder than the last one ended with (or containing more than one extruder), // we'll mark it with has_wipe tower. - // Per-extruder layer height combines layers away, leaving many LayerTools empty; skip them and compare against the last printing layer. - // Without the feature keep the historic behavior of stopping at the first empty layer. - const bool skip_empty_layer_tools = std::any_of(config.extruder_layer_height.values.begin(), - config.extruder_layer_height.values.end(), [](double h) { return h > 0.; }); + // Per-extruder layer height combines layers away, leaving many LayerTools empty; skip them and + // compare against the last printing layer. Without the feature keep the historic behavior of + // stopping at the first empty layer. + const bool skip_empty_layer_tools = has_extruder_layer_heights(config); const LayerTools *lt_last_printing = nullptr; for (unsigned int i=0; i+1 0. ? m_combined_height : m_layer->height; } -const Layer* LayerRegion::combined_lower_layer() const +static const Layer* nth_lower_layer(const Layer *layer, unsigned short count) { - const Layer *layer = m_layer; - // count == 0 (layer combined away into a group top) is treated as 1; it produces no extrusions anyway. - for (unsigned short i = 0, count = std::max(m_combined_layer_count, 1); layer != nullptr && i < count; ++ i) + for (unsigned short i = 0; layer != nullptr && i < count; ++ i) layer = layer->lower_layer; return layer; } +const Layer* LayerRegion::combined_lower_layer() const +{ + // count == 0 (layer combined away into a group top) is treated as 1; it produces no extrusions anyway. + return nth_lower_layer(m_layer, std::max(m_combined_layer_count, 1)); +} + // ORCA: walls-only pitch, see PrintObject::wall_layer_height_multiplier(). double LayerRegion::wall_combined_height() const { @@ -97,24 +101,16 @@ double LayerRegion::wall_combined_height() const const Layer* LayerRegion::wall_combined_lower_layer() const { - if (m_wall_combined_count <= 1) - // count == 0 (walls extrude at the run top above) produces no wall extrusions anyway. - return this->combined_lower_layer(); - const Layer *layer = m_layer; - for (unsigned short i = 0; layer != nullptr && i < m_wall_combined_count; ++ i) - layer = layer->lower_layer; - return layer; + // count == 0 (walls extrude at the run top above) produces no wall extrusions anyway. + return m_wall_combined_count <= 1 ? this->combined_lower_layer() : + nth_lower_layer(m_layer, m_wall_combined_count); } // ORCA: split wall layer heights, see PrintObject::wall_split_pitches(). const Layer* LayerRegion::wall_split_lower_layer() const { - if (m_wall_split_count <= 1) - return this->wall_combined_lower_layer(); - const Layer *layer = m_layer; - for (unsigned short i = 0; layer != nullptr && i < m_wall_split_count; ++ i) - layer = layer->lower_layer; - return layer; + return m_wall_split_count <= 1 ? this->wall_combined_lower_layer() : + nth_lower_layer(m_layer, m_wall_split_count); } // merge all regions' slices to get islands diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 44832e806be..bd4a26a9094 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -810,9 +810,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "print_order" || opt_key == "wall_sequence") { // The min / max layer height limits gate layer combining at the slicing step (see region_layer_height_multiplier()), so re-slice while per-extruder layer heights are active. - if ((opt_key == "min_layer_height" || opt_key == "max_layer_height") && - std::any_of(m_config.extruder_layer_height.values.begin(), m_config.extruder_layer_height.values.end(), - [](double h) { return h > 0.; })) + if ((opt_key == "min_layer_height" || opt_key == "max_layer_height") && has_extruder_layer_heights(m_config)) osteps.emplace_back(posSlice); osteps.emplace_back(posPerimeters); osteps.emplace_back(posEstimateCurledExtrusions); @@ -920,28 +918,17 @@ std::vector Print::support_material_extruders() const if (object->has_support_material()) { // Under a support nozzle diameter restriction a "default" (0) support filament resolves to a matching-nozzle filament: one of the object's own or the deterministic fallback. const unsigned int resolved_default = object->resolved_default_support_filament(); - assert(object->config().support_filament >= 0); - if (object->config().support_filament == 0) { - if (resolved_default > 0) { - unsigned int i = resolved_default - 1; - extruders.emplace_back((i >= num_extruders) ? 0 : i); - } else + auto add_support_filament = [&](int configured) { + assert(configured >= 0); + if (configured == 0 && resolved_default == 0) { support_uses_current_extruder = true; - } else { - unsigned int i = (unsigned int)object->config().support_filament - 1; - extruders.emplace_back((i >= num_extruders) ? 0 : i); - } - assert(object->config().support_interface_filament >= 0); - if (object->config().support_interface_filament == 0) { - if (resolved_default > 0) { - unsigned int i = resolved_default - 1; + } else { + unsigned int i = (configured > 0 ? (unsigned int)configured : resolved_default) - 1; extruders.emplace_back((i >= num_extruders) ? 0 : i); - } else - support_uses_current_extruder = true; - } else { - unsigned int i = (unsigned int)object->config().support_interface_filament - 1; - extruders.emplace_back((i >= num_extruders) ? 0 : i); - } + } + }; + add_support_filament(object->config().support_filament); + add_support_filament(object->config().support_interface_filament); } } @@ -1949,8 +1936,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // ORCA: per-extruder layer height ("extruder_layer_height"). if (layer_height > EPSILON) { // Gates diagnostics that would otherwise fire for configurations not using the feature. - const bool heights_feature_active = std::any_of(m_config.extruder_layer_height.values.begin(), - m_config.extruder_layer_height.values.end(), [](double v) { return v > 0.; }); + const bool heights_feature_active = has_extruder_layer_heights(m_config); bool object_has_combined_regions = false; for (const PrintRegion ®ion : object->all_regions()) { const PrintRegionConfig ®ion_config = region.config(); @@ -1962,8 +1948,6 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* bool wall_prefs_disagree = false; // false = a mixed (virtual) filament rotates physical extruders per layer, no pitch applies. const bool no_mixed = object->collect_region_pitch_filaments(region_config, lattice_filaments, pitch_from_features); - // The filament index is the extruder index on classic multi-tool printers. - auto extruder_of = [](unsigned int filament) { return size_t(filament); }; // Multiple of the object layer height a preferred height conforms to, 0 when it does // not conform (not an integer multiple, below the object layer height, or too tall // for the extruder's bore). Min/max layer heights are soft profile limits - exceeding @@ -1977,7 +1961,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* }; unsigned int region_multiplier = 0; for (const unsigned int filament : lattice_filaments) { - const size_t extruder_idx = extruder_of(filament); + const size_t extruder_idx = this->extruder_index_of(filament); const double extruder_height = m_config.extruder_layer_height.get_at(extruder_idx); if (extruder_height <= 0.) continue; // no preference (0 = object layer height): follows the others @@ -2024,40 +2008,28 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (region_multiplier == 0 || ! no_mixed) region_multiplier = 1; const bool solid_combined_infill = std::fabs(region_config.sparse_infill_density.value - 100.) < EPSILON; - auto selector_filament0 = [](int filament_id) { return (unsigned int)std::max(1, filament_id) - 1; }; - auto selector_extruder_idx = [&](int filament_id) { return extruder_of(selector_filament0(filament_id)); }; - // At 100% density there is no sparse infill: the solid interior is internal solid - // infill (PrintRegion::extruder()), so the sparse filament prints nothing - unless - // it also prints another feature here. - auto prints_nothing = [&](unsigned int filament) { - return solid_combined_infill && - filament == selector_filament0(region_config.sparse_infill_filament_id.value) && - filament != selector_filament0(region_config.internal_solid_filament_id.value) && - filament != selector_filament0(region_config.top_surface_filament_id.value) && - filament != selector_filament0(region_config.bottom_surface_filament_id.value) && - filament != selector_filament0(region_config.outer_wall_filament_id.value) && - filament != selector_filament0(region_config.inner_wall_filament_id.value); - }; + auto selector_extruder_idx = [&](int filament_id) { return this->extruder_index_of(feature_filament_idx(filament_id)); }; + auto prints_nothing = [&](unsigned int filament) { return PrintObject::region_filament_prints_nothing(region_config, filament); }; // Inner-wall loops can appear at a single wall loop too (alternate / extra walls, // see PrintObject::region_prints_inner_walls()). auto prints_outer_wall = [&](unsigned int filament) { return region_config.wall_loops.value > 0 && - filament == selector_filament0(region_config.outer_wall_filament_id.value); + filament == feature_filament_idx(region_config.outer_wall_filament_id.value); }; auto prints_inner_wall = [&](unsigned int filament) { return region_config.wall_loops.value > 0 && PrintObject::region_prints_inner_walls(region_config) && - filament == selector_filament0(region_config.inner_wall_filament_id.value); + filament == feature_filament_idx(region_config.inner_wall_filament_id.value); }; auto prints_walls = [&](unsigned int filament) { return prints_outer_wall(filament) || prints_inner_wall(filament); }; auto prints_top = [&](unsigned int filament) { return region_config.top_shell_layers.value > 0 && - filament == selector_filament0(region_config.top_surface_filament_id.value); + filament == feature_filament_idx(region_config.top_surface_filament_id.value); }; auto prints_bottom_or_solid = [&](unsigned int filament) { - return (region_config.bottom_shell_layers.value > 0 && filament == selector_filament0(region_config.bottom_surface_filament_id.value)) || - (! solid_combined_infill && filament == selector_filament0(region_config.internal_solid_filament_id.value)); + return (region_config.bottom_shell_layers.value > 0 && filament == feature_filament_idx(region_config.bottom_surface_filament_id.value)) || + (! solid_combined_infill && filament == feature_filament_idx(region_config.internal_solid_filament_id.value)); }; // Mirrors PrintObject::combine_top_surfaces(): a conforming preferred pitch of a // full-density top surface absorbs the solid layers below whenever the region itself @@ -2119,17 +2091,15 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (prints_nothing(filament)) // The inert 100%-density sparse selector must not veto the pitch. continue; - const size_t extruder_idx = extruder_of(filament); + const size_t extruder_idx = this->extruder_index_of(filament); const double pitch = region_multiplier * layer_height; if (pitch > m_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) { - if (! pitch_from_features && wall_candidate == region_multiplier) - // The wall filaments' own agreed pitch survives as a walls-only - // pitch: only the walls extrude it and nothing is lost. - ; - else if (! warned_pitch_fallbacks && - // Combining top surfaces still print a feature-derived - // pitch; the unhonored-heights warning covers the rest. - ! (pitch_from_features && top_candidate == region_multiplier)) { + // Stay silent when the pitch survives anyway - as the walls' own + // walls-only pitch or a combining top surface's pitch; the + // unhonored-heights warning covers the rest. + if (! warned_pitch_fallbacks && + (pitch_from_features ? top_candidate != region_multiplier : + wall_candidate != region_multiplier)) { warned_pitch_fallbacks = true; if (prints_walls(filament)) // Blocked by the walls' own bore: outer and inner walls @@ -2162,7 +2132,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* "%3% mm so the outer and inner walls can print at compatible layer heights " "(\"Adjust wall layer height\"). Only this filament's walls print the adjusted " "height; its other features keep the preferred one."), - selector_filament0(adjusted_id) + 1, + feature_filament_idx(adjusted_id) + 1, object->extruder_preferred_layer_height((unsigned int)std::max(0, adjusted_id)), (adjusted_outer ? eff_outer : eff_inner) * layer_height), "split_wall_adjust", object); @@ -2216,7 +2186,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* continue; pitch = prints_outer_wall(filament) ? outer_wall_height : inner_wall_height; } - warn_above_max(pitch, filament, m_config.max_layer_height.get_at(extruder_of(filament))); + warn_above_max(pitch, filament, m_config.max_layer_height.get_at(this->extruder_index_of(filament))); } auto filament_nozzle = [&](int filament_id) { return m_config.nozzle_diameter.get_at(selector_extruder_idx(filament_id)); @@ -2244,8 +2214,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* const char *width_key = solid_combined_infill ? "internal_solid_infill_line_width" : "sparse_infill_line_width"; const double preferred = object->extruder_preferred_layer_height((unsigned int)std::max(0, filament_id)); if (preferred > layer_height + EPSILON) { - const unsigned int combine_filament0 = selector_filament0(filament_id); - const size_t combine_extruder_idx = extruder_of(combine_filament0); + const unsigned int combine_filament0 = feature_filament_idx(filament_id); + const size_t combine_extruder_idx = this->extruder_index_of(combine_filament0); const double combine_cap = std::min(preferred, m_config.nozzle_diameter.get_at(combine_extruder_idx)); combined_infill_height = std::floor(combine_cap / layer_height + EPSILON) * layer_height; combined_infill_filament = combine_filament0; @@ -2270,7 +2240,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* const unsigned int top_multiplier = region_multiplier == 1 ? top_candidate : 1; if (top_multiplier > 1) warn_above_max(top_multiplier * layer_height, - selector_filament0(region_config.top_surface_filament_id.value), + feature_filament_idx(region_config.top_surface_filament_id.value), m_config.max_layer_height.get_at(selector_extruder_idx(region_config.top_surface_filament_id.value))); // Heights the filament's pitch-bound features print at: walls at their class's // pitch when active, top surfaces at the top pitch when active, everything else @@ -2293,7 +2263,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // print at (the combined infill prints above the minimum by construction). if (! warned_below_min_heights && heights_feature_active) for (const unsigned int filament : used_filaments) { - const double min_lh = m_config.min_layer_height.get_at(extruder_of(filament)); + const double min_lh = m_config.min_layer_height.get_at(this->extruder_index_of(filament)); if (min_lh <= EPSILON || prints_nothing(filament)) continue; const double lowest = lowest_feature_height(filament); diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 9333fc122da..c4385ec3c0f 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -502,6 +502,10 @@ class PrintObject : public PrintObjectBaseWithState 1, the alternating extra // wall and the extra perimeters on overhangs add inner loops even at a single wall loop. static bool region_prints_inner_walls(const PrintRegionConfig &config); + // Does the given 0-based filament print nothing in this region? True only for a sparse infill + // selector at 100% density (the solid interior belongs to the internal solid filament) that + // prints no other feature of the region. + static bool region_filament_prints_nothing(const PrintRegionConfig &config, unsigned int filament); // Walls-only pitch multiplier: when the region as a whole cannot follow its wall filaments' // preferred pitch (region_layer_height_multiplier() == 1, e.g. a finer-nozzle filament prints // the region's other features), the walls alone combine to it while everything else keeps @@ -1093,6 +1097,9 @@ class Print : public PrintBaseWithState //SoftFever bool &is_BBL_printer() { return m_isBBLPrinter; } const bool is_BBL_printer() const { return m_isBBLPrinter; } + // Per-extruder vector index of a 0-based filament: this fork's classic multi-tool printers + // index per-extruder options by the filament directly. + size_t extruder_index_of(unsigned int filament_idx) const { return size_t(filament_idx); } CalibMode& calib_mode() { return m_calib_params.mode; } const CalibMode calib_mode() const { return m_calib_params.mode; } void set_calib_params(const Calib_Params& params); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index be8c38f89ce..f95a0d2e88e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7700,11 +7700,7 @@ void DynamicPrintConfig::normalize_fdm_1() } } - // Note: no cross-propagation between the per-feature filament selectors here. Filling one - // selector from another (sparse -> internal solid, internal solid <-> top/bottom) silently - // overwrote "Default" (0), which means "use the part's filament", with an unrelated feature's - // explicit filament - e.g. assigning internal solid infill dragged the top/bottom surfaces along. - // Each selector resolves its own "Default" at slicing time (PrintRegion::extruder()). + // No cross-propagation between the per-feature filament selectors (see normalize_fdm() above). if (this->has("spiral_mode") && this->opt("spiral_mode", true)->value) { { diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 06c48090de1..1cd59f77788 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1979,6 +1979,17 @@ class ModelConfig static uint64_t s_last_timestamp; }; +// 0-based filament of a 1-based feature filament selector ("Default" = 0 falls back to filament 1). +inline unsigned int feature_filament_idx(int filament_id) { return filament_id > 1 ? (unsigned int)(filament_id - 1) : 0u; } + +// True when any extruder carries a per-extruder preferred layer height. +inline bool has_extruder_layer_heights(const PrintConfig &config) { + for (double h : config.extruder_layer_height.values) + if (h > 0.) + return true; + return false; +} + } // namespace Slic3r // Serialization through the Cereal library diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index e1f731fb93f..6a6a0ddf312 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -911,11 +911,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "top_surface_filament_id" || opt_key == "bottom_surface_filament_id" || opt_key == "wall_loops" || opt_key == "sparse_infill_density" || opt_key == "top_shell_layers" || opt_key == "bottom_shell_layers") { - for (double extruder_layer_height : m_print->config().extruder_layer_height.values) - if (extruder_layer_height > 0.) { - steps.emplace_back(posSlice); - break; - } + if (has_extruder_layer_heights(m_print->config())) + steps.emplace_back(posSlice); } if ( opt_key == "brim_width" || opt_key == "brim_object_gap" @@ -1405,6 +1402,16 @@ void PrintObject::detect_surfaces_type() layerm_slices_surfaces = union_ex(layerm_slices_surfaces, to_expolygons(layerm->fill_surfaces.surfaces)); } + // ORCA: per-extruder layer height: a neighbor's combined group prints only its + // common shape; anything of this layer over the remainder is an exposed step face. + // Every region of the neighbor counts: at a painted or per-part filament boundary + // the remainder under this region's geometry belongs to another region. + auto add_exposed_steps = [&layerm_slices_surfaces](ExPolygons &dst, const Layer *neighbor) { + for (const LayerRegion *neighbor_layerm : neighbor->m_regions) + if (const ExPolygons &exposed = neighbor_layerm->combined_away_exposed(); ! exposed.empty()) + dst = union_ex(dst, intersection_ex(layerm_slices_surfaces, exposed)); + }; + // find top surfaces (difference between current surfaces // of current layer and upper one) Surfaces top; @@ -1412,11 +1419,7 @@ void PrintObject::detect_surfaces_type() ExPolygons upper_slices = interface_shells ? diff_ex(layerm_slices_surfaces, upper_layer->m_regions[region_id]->slices.surfaces, ApplySafetyOffset::Yes) : diff_ex(layerm_slices_surfaces, upper_layer->lslices, ApplySafetyOffset::Yes); - // ORCA: per-extruder layer height: geometry combined away above extrudes as - // part of a group further up; the step faces it exposes here are real top - // surfaces even though lslices still carry the uncombined shape. - if (const ExPolygons &exposed = upper_layer->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) - upper_slices = union_ex(upper_slices, intersection_ex(layerm_slices_surfaces, exposed)); + add_exposed_steps(upper_slices, upper_layer); surfaces_append(top, opening_ex(upper_slices, offset), stTop); } else { // if no upper layer, all surfaces of this one are solid @@ -1440,20 +1443,12 @@ void PrintObject::detect_surfaces_type() #else // Any surface lying on the void is a true bottom bridge (an overhang) ExPolygons unsupported = diff_ex(layerm_slices_surfaces, lower_layer->lslices, ApplySafetyOffset::Yes); - // ORCA: per-extruder layer height: geometry combined away below extrudes as - // part of a group whose top lies underneath this layer; anything over its - // leftover band hangs over the step it exposes. A combined group itself - // reaches down to the layer below the whole group; its bridges hang over - // that layer, not over the combined-away one right below. - if (const ExPolygons &exposed = lower_layer->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) - unsupported = union_ex(unsupported, intersection_ex(layerm_slices_surfaces, exposed)); + add_exposed_steps(unsupported, lower_layer); + // A combined group's own bridges hang over the layer below the whole group. if (const unsigned short count = layerm->combined_layer_count(); count > 1 && idx_layer >= size_t(count)) { - // The below-group layer's own leftover band is unprinted too. const Layer *below_group = m_layers[idx_layer - count]; - unsupported = union_ex(unsupported, - diff_ex(layerm_slices_surfaces, below_group->lslices, ApplySafetyOffset::Yes)); - if (const ExPolygons &exposed = below_group->m_regions[region_id]->combined_away_exposed(); ! exposed.empty()) - unsupported = union_ex(unsupported, intersection_ex(layerm_slices_surfaces, exposed)); + unsupported = union_ex(unsupported, diff_ex(layerm_slices_surfaces, below_group->lslices, ApplySafetyOffset::Yes)); + add_exposed_steps(unsupported, below_group); } surfaces_append(bottom, opening_ex(unsupported, offset), surface_type_bottom_other); // if user requested internal shells, we need to identify surfaces @@ -3538,9 +3533,7 @@ double PrintObject::extruder_preferred_layer_height(unsigned int filament_id) co // different physical extruder on every layer, so no stable preferred height exists for them. if (filament_id > print_config.filament_diameter.values.size()) return 0.; - // Classic multi-tool printers index per-extruder vectors by the filament id directly (like the min/max_layer_height and nozzle_diameter lookups elsewhere). - const size_t extruder_idx = filament_id - 1; - return std::max(0., print_config.extruder_layer_height.get_at(extruder_idx)); + return std::max(0., print_config.extruder_layer_height.get_at(m_print->extruder_index_of(filament_id - 1))); } // Returns the number of object layers a region printing with the given 1-based filament id combines into one extrusion (1 = no combining). @@ -3557,9 +3550,8 @@ unsigned int PrintObject::layer_height_multiplier_for_filament(unsigned int fila if (multiplier <= 1 || std::abs(extruder_height - multiplier * layer_height) > EPSILON) // Not combining, or not an integer multiple (the latter is rejected by Print::validate()). return 1; - // Same filament -> extruder mapping as extruder_preferred_layer_height() and Print::validate(). const PrintConfig &print_config = m_print->config(); - const size_t extruder_idx = size_t((filament_id == 0 ? 1 : filament_id) - 1); + const size_t extruder_idx = m_print->extruder_index_of((filament_id == 0 ? 1 : filament_id) - 1); if (extruder_height > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) // Taller than the nozzle can print, rejected by Print::validate(). return 1; @@ -3575,6 +3567,20 @@ bool PrintObject::region_prints_inner_walls(const PrintRegionConfig &config) config.extra_perimeters_on_overhangs.value; } +// At 100% sparse infill density the solid interior belongs to the internal solid filament +// (PrintRegion::extruder()), so a sparse infill selector that prints no other feature of the +// region prints nothing at all. +bool PrintObject::region_filament_prints_nothing(const PrintRegionConfig &config, unsigned int filament) +{ + return std::abs(config.sparse_infill_density.value - 100.) < EPSILON && + filament == feature_filament_idx(config.sparse_infill_filament_id.value) && + filament != feature_filament_idx(config.internal_solid_filament_id.value) && + filament != feature_filament_idx(config.top_surface_filament_id.value) && + filament != feature_filament_idx(config.bottom_surface_filament_id.value) && + filament != feature_filament_idx(config.outer_wall_filament_id.value) && + filament != feature_filament_idx(config.inner_wall_filament_id.value); +} + // Collects the 0-based filaments whose explicit preferred layer heights define the region's // pitch: the wall filaments when any of them carries a preference, otherwise the other // pitch-bound feature filaments (top / bottom / internal solid; sparse infill always combines @@ -3672,25 +3678,11 @@ unsigned int PrintObject::region_layer_height_multiplier(const PrintRegion ®i const double pitch = multiplier * m_config.layer_height.value; std::vector used_filaments; // 0-based filament indices PrintRegion::collect_object_printing_extruders(print_config, config, false /* has_brim */, used_filaments); - // At 100% density the sparse infill filament prints nothing (the solid interior belongs to - // the internal solid filament) - unless it also prints another feature, its nozzle must not - // veto the pitch (mirrors Print::validate()'s prints_nothing()). - auto selector0 = [](int filament_id) { return (unsigned int)std::max(1, filament_id) - 1; }; - auto prints_nothing = [&](unsigned int filament) { - return std::abs(config.sparse_infill_density.value - 100.) < EPSILON && - filament == selector0(config.sparse_infill_filament_id.value) && - filament != selector0(config.internal_solid_filament_id.value) && - filament != selector0(config.top_surface_filament_id.value) && - filament != selector0(config.bottom_surface_filament_id.value) && - filament != selector0(config.outer_wall_filament_id.value) && - filament != selector0(config.inner_wall_filament_id.value); - }; for (unsigned int filament : used_filaments) { - if (prints_nothing(filament)) + // An inert 100%-density sparse infill selector must not veto the pitch. + if (region_filament_prints_nothing(config, filament)) continue; - // The filament index is the extruder index on classic multi-tool printers. - const size_t extruder_idx = size_t(filament); - if (pitch > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON) + if (pitch > print_config.nozzle_diameter.get_at(m_print->extruder_index_of(filament)) + EPSILON) return 1; } return multiplier; @@ -3710,8 +3702,6 @@ unsigned int PrintObject::wall_layer_height_multiplier(const PrintRegion ®ion return 1; unsigned int outer_m = 0, inner_m = 0; if (! this->wall_effective_multipliers(region, outer_m, inner_m)) - // Snapmaker mixed filament: virtual mixed ids resolve to a different physical - // extruder per layer, no stable pitch exists for them. return 1; // A preference-less wall filament (0) follows the other one's pitch. Outer and inner walls // that cannot split print together at one height: disagreeing effective preferences meet at @@ -3726,9 +3716,7 @@ unsigned int PrintObject::wall_layer_height_multiplier(const PrintRegion ®ion const PrintConfig &print_config = m_print->config(); const double pitch = multiplier * m_config.layer_height.value; auto nozzle_too_small = [&](int filament_id) { - // The filament index is the extruder index on classic multi-tool printers. - const size_t extruder_idx = size_t((unsigned int)std::max(1, filament_id) - 1); - return pitch > print_config.nozzle_diameter.get_at(extruder_idx) + EPSILON; + return pitch > print_config.nozzle_diameter.get_at(m_print->extruder_index_of(feature_filament_idx(filament_id))) + EPSILON; }; if (nozzle_too_small(config.outer_wall_filament_id.value)) return 1; @@ -3774,8 +3762,7 @@ bool PrintObject::wall_effective_multipliers(const PrintRegion ®ion, unsigned const unsigned int other = adjust_outer ? inner_m : outer_m; const int filament_id = adjust_outer ? config.outer_wall_filament_id.value : config.inner_wall_filament_id.value; const PrintConfig &print_config = m_print->config(); - // The filament index is the extruder index on classic multi-tool printers. - const size_t extruder_idx = size_t((unsigned int)std::max(1, filament_id) - 1); + const size_t extruder_idx = m_print->extruder_index_of(feature_filament_idx(filament_id)); const double h = m_config.layer_height.value; const double min_lh = print_config.min_layer_height.get_at(extruder_idx); const double max_lh = print_config.max_layer_height.get_at(extruder_idx); @@ -3808,7 +3795,6 @@ bool PrintObject::wall_split_pitches(const PrintRegion ®ion, unsigned int &fi { unsigned int outer_m = 0, inner_m = 0; if (! this->wall_effective_multipliers(region, outer_m, inner_m)) - // Mixed (virtual) filament ids never combine. return false; // Split needs an explicit preference on both wall classes; a preference-less wall filament // follows the other one at a single merged pitch as usual. @@ -4781,6 +4767,15 @@ void PrintObject::discover_horizontal_shells() #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ } // void PrintObject::discover_horizontal_shells() +// Drop expolygons too small to be worth combining (see LayerRegion::infill_area_threshold()). +static void remove_small_expolygons(ExPolygons &expolygons, double area_threshold) +{ + if (! expolygons.empty() && area_threshold > 0.) + expolygons.erase(std::remove_if(expolygons.begin(), expolygons.end(), + [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), + expolygons.end()); +} + // Per-extruder layer height: print top surfaces with the preferred layer height of their filament // by absorbing the internal solid shell layers right below them. The top surface then extrudes // once at N times the object layer height - replacing itself and N-1 solid layers underneath - @@ -4834,10 +4829,7 @@ void PrintObject::combine_top_surfaces() if (combined.empty()) break; } - if (double area_threshold = top_layerm->infill_area_threshold(); ! combined.empty() && area_threshold > 0.) - combined.erase(std::remove_if(combined.begin(), combined.end(), - [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), - combined.end()); + remove_small_expolygons(combined, top_layerm->infill_area_threshold()); if (combined.empty()) continue; // Clearance against the absorbed layers' remaining solid infill, which is grown later @@ -4891,8 +4883,7 @@ void PrintObject::combine_infill() const bool combine_solid = std::abs(region.config().sparse_infill_density.value - 100.) < EPSILON; const int combine_filament_id = std::max(1, combine_solid ? region.config().internal_solid_filament_id.value : region.config().sparse_infill_filament_id.value); - // Per-extruder vector index: the filament index is the extruder index on classic multi-tool printers (like extruder_preferred_layer_height()). - const size_t combine_extruder_idx = size_t(combine_filament_id - 1); + const size_t combine_extruder_idx = this->print()->extruder_index_of((unsigned int)(combine_filament_id - 1)); double preferred_infill_height = this->extruder_preferred_layer_height((unsigned int)combine_filament_id); if (preferred_infill_height <= m_config.layer_height.value + EPSILON) preferred_infill_height = 0.; @@ -4968,11 +4959,7 @@ void PrintObject::combine_infill() // Start looping from the second layer and intersect the current intersection with it. for (size_t i = 1; i < layerms.size(); ++ i) intersection = intersection_ex(layerms[i]->fill_surfaces.filter_by_type(surface_type), intersection); - double area_threshold = layerms.front()->infill_area_threshold(); - if (! intersection.empty() && area_threshold > 0.) - intersection.erase(std::remove_if(intersection.begin(), intersection.end(), - [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), - intersection.end()); + remove_small_expolygons(intersection, layerms.front()->infill_area_threshold()); if (intersection.empty()) continue; // Slic3r::debugf " combining %d %s regions from layers %d-%d\n", @@ -5068,11 +5055,7 @@ void PrintObject::combine_infill() ExPolygons intersection = leftover_expolygons(layerms.front()); for (size_t i = 1; i < layerms.size() && ! intersection.empty(); ++ i) intersection = intersection_ex(leftover_expolygons(layerms[i]), intersection); - double area_threshold = layerms.front()->infill_area_threshold(); - if (! intersection.empty() && area_threshold > 0.) - intersection.erase(std::remove_if(intersection.begin(), intersection.end(), - [area_threshold](const ExPolygon &expoly) { return expoly.area() <= area_threshold; }), - intersection.end()); + remove_small_expolygons(intersection, layerms.front()->infill_area_threshold()); if (intersection.empty()) continue; // Same clearance as the pass above: the leftover fills also grow to overlap perimeters. diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index b70682edccc..6045aeef7ec 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -894,7 +894,6 @@ void PrintObject::apply_extruder_layer_heights() // Split wall layer heights (see wall_split_pitches()): the wall min-merge above already // equals the fine class's pitch; the coarse class additionally combines to its own runs. std::vector split_coarses(this->num_printing_regions(), 1); - std::vector split_outers(this->num_printing_regions(), 0); bool any_combined = false; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { multipliers[region_id] = this->region_layer_height_multiplier(this->printing_region(region_id)); @@ -902,10 +901,8 @@ void PrintObject::apply_extruder_layer_heights() wall_multipliers[region_id] = this->wall_layer_height_multiplier(this->printing_region(region_id)); unsigned int fine = 0, coarse = 0; bool coarse_is_outer = false; - if (this->wall_split_pitches(this->printing_region(region_id), fine, coarse, coarse_is_outer)) { + if (this->wall_split_pitches(this->printing_region(region_id), fine, coarse, coarse_is_outer)) split_coarses[region_id] = coarse; - split_outers[region_id] = coarse_is_outer; - } } any_combined |= multipliers[region_id] > 1 || wall_multipliers[region_id] > 1 || split_coarses[region_id] > 1; } @@ -930,12 +927,7 @@ void PrintObject::apply_extruder_layer_heights() // and overhangs, so the extruder never drops to finer layers (boundaries become steps). const bool adaptive = m_config.extruder_layer_height_mode.value == elhmAdaptive; const bool fixed = m_config.extruder_layer_height_mode.value == elhmFixed; - // The filament index is the extruder index on classic multi-tool printers (same mapping as - // extruder_preferred_layer_height() and Print::validate()). const PrintConfig &print_config = m_print->config(); - auto extruder_idx_of = [](int filament_id) { - return size_t((unsigned int)std::max(1, filament_id) - 1); - }; for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { // Walls-only mode marks the runs on the LayerRegions for make_perimeters() instead of // moving any slices: every layer keeps its geometry, fills and surfaces. @@ -946,8 +938,8 @@ void PrintObject::apply_extruder_layer_heights() continue; // Shapes deviating by less than this fraction of the region's nozzle diameter are considered // identical; the deviations swallowed stay below what printing N layers at once causes anyway. - const double nozzle_diameter = print_config.nozzle_diameter.get_at( - extruder_idx_of(this->printing_region(region_id).config().outer_wall_filament_id.value)); + const double nozzle_diameter = print_config.nozzle_diameter.get_at(m_print->extruder_index_of( + feature_filament_idx(this->printing_region(region_id).config().outer_wall_filament_id.value))); const float tolerance = float(scale_(m_config.extruder_layer_height_tolerance.get_abs_value(nozzle_diameter))); // Where geometry would fall back below the extruders' minimum layer height, runs of at least // min_run layers are forced instead, ignoring the shape tolerance (the nozzle cannot print @@ -959,15 +951,15 @@ void PrintObject::apply_extruder_layer_heights() std::vector pitch_filaments; // 0-based filament indices if (walls_only) { // Only the wall filaments print the combined runs here. - pitch_filaments.emplace_back((unsigned int)extruder_idx_of(region_config.outer_wall_filament_id.value)); + pitch_filaments.emplace_back(feature_filament_idx(region_config.outer_wall_filament_id.value)); if (region_prints_inner_walls(region_config)) - pitch_filaments.emplace_back((unsigned int)extruder_idx_of(region_config.inner_wall_filament_id.value)); + pitch_filaments.emplace_back(feature_filament_idx(region_config.inner_wall_filament_id.value)); } else { bool pitch_from_features = false; this->collect_region_pitch_filaments(region_config, pitch_filaments, pitch_from_features); } for (unsigned int filament : pitch_filaments) - min_layer_height = std::max(min_layer_height, print_config.min_layer_height.get_at(size_t(filament))); + min_layer_height = std::max(min_layer_height, print_config.min_layer_height.get_at(m_print->extruder_index_of(filament))); } size_t min_run = 1; if (min_layer_height > m_config.layer_height.value + EPSILON) @@ -1178,6 +1170,102 @@ void PrintObject::apply_extruder_layer_heights() } m_print->throw_if_canceled(); } + + // ORCA: floating pieces at region boundaries. Combining defers or drops a region's layer + // geometry (cleared run members extrude at their run top; remainders outside the committed + // shape never print), so a neighboring region's per-layer geometry can lose both its support + // below and its same-layer lateral anchor: it would extrude into thin air before the covering + // pass exists. Unanchored pieces are dropped (recorded as exposed step faces) and the column + // resumes - bridging - on the first layer with a printed anchor; a piece whose only anchor is + // a run committing at its own layer is filled by that run instead and resumes fully supported + // on top of the pass. Pieces away from any deferred neighbor geometry are genuine model + // overhangs and print as usual. + const size_t num_regions = this->num_printing_regions(); + const float anchor_dist = float(scale_(0.1)); + auto printed_at = [this, num_regions](size_t layer_idx) { + Polygons printed; + for (size_t region_id = 0; region_id < num_regions; ++ region_id) + polygons_append(printed, to_polygons(m_layers[layer_idx]->regions()[region_id]->slices.surfaces)); + return printed; + }; + for (size_t idx = first_idx; idx < m_layers.size(); ++ idx) { + m_print->throw_if_canceled(); + // Only layers around active combining can need work. + bool combining_nearby = false; + for (size_t region_id = 0; region_id < num_regions && ! combining_nearby; ++ region_id) + combining_nearby = m_layers[idx]->regions()[region_id]->combined_layer_count() != 1 || + m_layers[idx - 1]->regions()[region_id]->combined_layer_count() != 1; + if (! combining_nearby) + continue; + const Polygons printed_below = printed_at(idx - 1); + const Polygons printed_now = printed_at(idx); + // Object areas of this layer nothing extrudes at: deferred to a run top above, or dropped. + // The opening removes hairline residue along region boundaries (lslices are safety-offset + // unions of the region slices), keeping only real deferred geometry. + const Polygons unprinted_now = to_polygons(opening_ex(diff_ex(m_layers[idx]->lslices, printed_now), anchor_dist)); + for (size_t region_id = 0; region_id < num_regions; ++ region_id) { + LayerRegion *layerm = m_layers[idx]->regions()[region_id]; + // Only plain per-layer regions: run members print at their run top, and wall-combined + // and split runs delegate their walls to the run top and must not be trimmed. + if (layerm->combined_layer_count() != 1 || layerm->wall_combined_count() != 1 || + layerm->wall_split_count() != 1 || layerm->slices.empty()) + continue; + const ExPolygons own = to_expolygons(layerm->slices.surfaces); + ExPolygons floating = diff_ex(own, printed_below); + if (floating.empty()) + continue; + const Polygons anchors = diff(printed_now, to_polygons(floating)); + // An anchored piece is normally a legitimate flush bridge. But when its anchor is a + // run committing at this very layer and nothing below that run's whole span carries + // the piece, the bridge would hang beside the pass over the run's full height of air; + // object volume exists through the pass height, so the run fills it instead. + LayerRegion *fill_target = nullptr; + ExPolygons filled; + auto fill_by_covering_run = [&](const ExPolygon &piece, const Polygons &near) { + for (size_t other = 0; other < num_regions; ++ other) { + LayerRegion *neighbor = m_layers[idx]->regions()[other]; + if (other == region_id || neighbor->combined_layer_count() < 2 || + intersection(near, to_polygons(neighbor->slices.surfaces)).empty()) + continue; + const size_t run_bottom = idx + 1 - size_t(neighbor->combined_layer_count()); + if (run_bottom > 0 && ! intersection(to_polygons(piece), printed_at(run_bottom - 1)).empty()) + return; // carried below the covering run: the flush bridge is fine + ExPolygons fill { piece }; + for (size_t i = run_bottom; i <= idx && ! fill.empty(); ++ i) + fill = intersection_ex(fill, m_layers[i]->lslices); + if (! fill.empty()) { + fill_target = neighbor; + append(filled, std::move(fill)); + } + return; + } + }; + ExPolygons dropped; + for (ExPolygon &piece : floating) { + const Polygons near = offset(piece, anchor_dist); + if (! intersection(near, anchors).empty()) + fill_by_covering_run(piece, near); + else if (! intersection(near, unprinted_now).empty()) + // Beside or over deferred geometry, with no anchor: would extrude into thin air. + dropped.emplace_back(std::move(piece)); + } + if (dropped.empty() && filled.empty()) + continue; + ExPolygons removed = dropped; + append(removed, filled); + layerm->slices.set(diff_ex(own, removed), stInternal); + // Dropped pieces never print and classify the surfaces around them; filled ones DO + // print (as the neighbor's pass), so they must not count as exposed remainders. + layerm->m_combined_away_exposed = union_ex(layerm->m_combined_away_exposed, std::move(dropped)); + if (fill_target != nullptr) { + // Safety-offset union: the filled pieces must weld into the committed shape, or + // they stay separate islands walled off by their own mid-air perimeters. + Polygons merged = to_polygons(fill_target->slices.surfaces); + polygons_append(merged, to_polygons(filled)); + fill_target->slices.set(union_safety_offset_ex(merged), stInternal); + } + } + } } static bool bool_from_full_config(const DynamicPrintConfig &full_cfg, const char *key, bool fallback) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index c085d6cddf1..9f886f588de 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -683,6 +683,12 @@ ModelConfig& ObjectList::get_item_config(const wxDataViewItem& item) const (*m_objects)[obj_idx]->config; } +// ORCA: per-object/per-part support and feature filament selectors. They must track filament +// count changes alongside the "extruder" selector, or a stale id lands on the wrong filament. +static const char *filament_selector_keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", + "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", + "support_filament", "support_interface_filament"}; + void ObjectList::update_filament_values_for_items(const size_t filaments_count) { for (size_t i = 0; i < m_objects->size(); ++i) @@ -701,10 +707,7 @@ void ObjectList::update_filament_values_for_items(const size_t filaments_count) } m_objects_model->SetExtruder(extruder, item); - static const char *keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", - "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", - "support_filament", "support_interface_filament"}; - for (auto key : keys) + for (auto key : filament_selector_keys) if (object->config.has(key) && object->config.opt_int(key) > filaments_count) object->config.erase(key); @@ -722,7 +725,7 @@ void ObjectList::update_filament_values_for_items(const size_t filaments_count) m_objects_model->SetExtruder(extruder, item); - for (auto key : keys) + for (auto key : filament_selector_keys) if (object->volumes[id]->config.has(key) && object->volumes[id]->config.opt_int(key) > filaments_count) object->volumes[id]->config.erase(key); } @@ -878,10 +881,7 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz } m_objects_model->SetExtruder(extruder, item); - static const char* keys[] = {"outer_wall_filament_id", "inner_wall_filament_id", "sparse_infill_filament_id", - "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", - "support_filament", "support_interface_filament"}; - for (auto key : keys) { + for (auto key : filament_selector_keys) { if (object->config.has(key)) { if (object->config.opt_int(key) == filament_id + 1) object->config.erase(key); @@ -899,7 +899,7 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz if (!item) continue; - for (auto key : keys) { + for (auto key : filament_selector_keys) { if (object->volumes[id]->config.has(key)) { if (object->volumes[id]->config.opt_int(key) == filament_id + 1) object->volumes[id]->config.erase(key); @@ -957,7 +957,7 @@ void ObjectList::update_filament_values_for_items_when_delete_filament(const siz } // Height ranges carry the per-feature selectors too (the engine reads them from // layer_config_ranges); remap them like the object / volume configs above. - for (auto key : keys) { + for (auto key : filament_selector_keys) { if (layer_range_item.second.has(key)) { if (layer_range_item.second.option(key)->getInt() == int(filament_id) + 1) layer_range_item.second.erase(key); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 35efc7fe5eb..7bd637455dc 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -8269,6 +8269,14 @@ static wxString nozzle_combo_label(double value) return wxString(oss.str()) + "mm"; } +// Numeric part of a nozzle_combo_label() item ("0.4mm" -> "0.4"); parse with ToCDouble(). +static wxString nozzle_combo_number(wxString label) +{ + if (label.EndsWith("mm")) + label.RemoveLast(2); + return label; +} + // Show THIS nozzle's own diameter (per-nozzle sizes are supported), selecting the matching // "x.xmm" item so the read-only combo accepts it. Falls back to the uniform printer_variant // label only when the per-nozzle value is unavailable. @@ -8276,12 +8284,9 @@ static void select_nozzle_diameter_label(ComboBox *diameter_combo, double this_n { wxString this_label; for (unsigned int n = 0; n < diameter_combo->GetCount(); ++n) { - wxString item = diameter_combo->GetString(n); - wxString num = item; - if (num.EndsWith("mm")) - num.RemoveLast(2); + const wxString item = diameter_combo->GetString(n); double item_nd = 0.; - if (num.ToCDouble(&item_nd) && std::abs(item_nd - this_nd) < EPSILON) { + if (nozzle_combo_number(item).ToCDouble(&item_nd) && std::abs(item_nd - this_nd) < EPSILON) { this_label = item; break; } @@ -8412,9 +8417,7 @@ void Sidebar::update_nozzle_settings(bool switch_machine) // per-nozzle sizes, which the slicer now supports. // Parse the selected diameter from the "0.4mm" combo item. - wxString sel_num = diameter_combo->GetValue(); - if (sel_num.EndsWith("mm")) - sel_num.RemoveLast(2); + const wxString sel_num = nozzle_combo_number(diameter_combo->GetValue()); double new_nd = 0.; if (!sel_num.ToCDouble(&new_nd) || new_nd <= 0.) return; @@ -8522,11 +8525,8 @@ void Sidebar::update_nozzle_settings(bool switch_machine) lh_combo->Bind(wxEVT_COMBOBOX, [lh_combo, i](wxCommandEvent& event) { // "Default" (or anything non-numeric) clears the preference: 0 = object layer height. - wxString sel = lh_combo->GetValue(); - if (sel.EndsWith("mm")) - sel.RemoveLast(2); double new_height = 0.; - if (!sel.ToCDouble(&new_height) || new_height < 0.) + if (!nozzle_combo_number(lh_combo->GetValue()).ToCDouble(&new_height) || new_height < 0.) new_height = 0.; Tab* printer_tab = wxGetApp().get_tab(Preset::TYPE_PRINTER); diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index ee97fde2ec2..20c31292bd8 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -286,7 +286,7 @@ SCENARIO("Fixed mode always prints the extruder layer height", "[MultiNozzleLaye SCENARIO("Fixed mode keeps top surfaces on combined steps", "[MultiNozzleLayerHeight]") { // A shoulder that ends mid-run: the wide cube's exposed ring is combined away with its layer // (the run prints only the common shape), so it must reappear as a top surface on the printed - // layer below it - otherwise the step carries bare sparse infill (the benchy cabin roof case). + // layer below it - otherwise the step carries bare sparse infill. GIVEN("A wide cube ending mid-run with a narrower cube on top") { auto top_area = [](ExtruderLayerHeightMode mode) { DynamicPrintConfig config = two_extruder_config(0.4); @@ -377,6 +377,134 @@ SCENARIO("Fixed mode bridges lids that start inside a run", "[MultiNozzleLayerHe } } +SCENARIO("A bottom over another region's combined-away geometry bridges", "[MultiNozzleLayerHeight]") { + // At a filament boundary the support below a region's bottom can belong to a neighboring + // region whose run commits only its common shape: that support never prints even though the + // object's slices still cover the area, so the bottom must classify as an unsupported bridge. + GIVEN("A coarse slab whose top layer is a pocket rim holding a fine-filament insert") { + DynamicPrintConfig config = two_extruder_config(0.4); + config.option>("extruder_layer_height_mode", true)->value = elhmFixed; + Print print; + Model model; + // One coarse volume: a full slab plus a one-layer pocket rim on top. The run pairing the + // slab's top layer with the rim commits only the rim and drops the pocket footprint. + TriangleMesh slab = mesh(TestMesh::cube_20x20x20); + slab.scale(Vec3f(1.f, 1.f, 0.07f)); // 20 x 20 x 1.4 mm + TriangleMesh rim = mesh(TestMesh::cube_with_hole); // 20 x 20, 10 mm hole + rim.scale(Vec3f(1.f, 1.f, 0.02f)); // one 0.2 mm layer + rim.translate(0.f, 0.f, 1.4f); + slab.merge(rim); + TriangleMesh insert = mesh(TestMesh::cube_20x20x20); + insert.scale(Vec3f(0.4f, 0.4f, 0.04f)); // 8 x 8 x 0.8 mm in the pocket + insert.translate(6.f, 6.f, 1.4f); + ModelObject *object = model.add_object(); + object->name = "pocketed_slab"; + ModelVolume *coarse_volume = object->add_volume(std::move(slab)); + coarse_volume->config.set("extruder", 2); + object->add_volume(std::move(insert)); + object->add_instance(); + // This fork's arrangement engine rejects positions outside the (unset) plate even for + // an InfiniteBed; place the object at a fixed bed spot like init_two_part_print(). + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); + mo->ensure_on_bed(); + } + print.apply(model, config); + print.set_status_silent(); + THEN("the insert's first layer is classified as an unsupported bottom") { + REQUIRE(print.validate().string.empty()); + print.process(); + const PrintObject &po = *print.objects().front(); + int fine_region = -1, coarse_region = -1; + find_regions(po, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + double bridge_area = 0.; + for (size_t idx = 0; idx < po.layer_count(); ++ idx) + if (const Layer *layer = po.get_layer(int(idx)); fine_region < layer->region_count()) + for (const Surface &surface : layer->get_region(fine_region)->fill_surfaces.surfaces) + if (surface.surface_type == stBottomBridge) + bridge_area += unscale(unscale(surface.expolygon.area())); + CAPTURE(bridge_area); + REQUIRE(bridge_area > 30.); + } + } +} + +SCENARIO("A floating insert below its covering run is filled by the run and resumes on top", "[MultiNozzleLayerHeight]") { + // Geometry whose covering run commits above it would print into thin air before any support + // exists: its floating layers are dropped, the covering run's pass fills the object volume + // they occupied, and the region resumes fully supported on top of the pass. + GIVEN("A hollow tube capped by a pocketed coarse roof with a fine insert starting mid-run") { + DynamicPrintConfig config = two_extruder_config(0.6); + config.set_key_value("max_layer_height", new ConfigOptionFloats({0.3, 0.6})); + config.option>("extruder_layer_height_mode", true)->value = elhmFixed; + Print print; + Model model; + // Coarse volume: tube walls, a full roof slab over the cavity, and a one-run pocket rim + // above it. The run pairs the slab with the rim and drops the slab's pocket footprint. + TriangleMesh roof = mesh(TestMesh::cube_with_hole); // 20 x 20, 10 mm hole + roof.scale(Vec3f(1.f, 1.f, 0.1f)); // 1 mm tall tube + TriangleMesh slab = mesh(TestMesh::cube_20x20x20); + slab.scale(Vec3f(1.f, 1.f, 0.01f)); // 20 x 20 x 0.2 mm + slab.translate(0.f, 0.f, 1.f); + roof.merge(slab); + const float rim_dims[4][4] = {{0.3f, 1.f, 0.f, 0.f}, {0.3f, 1.f, 14.f, 0.f}, + {0.4f, 0.3f, 6.f, 0.f}, {0.4f, 0.3f, 6.f, 14.f}}; + for (const auto &d : rim_dims) { + TriangleMesh rim = mesh(TestMesh::cube_20x20x20); + rim.scale(Vec3f(d[0], d[1], 0.02f)); // rim pieces around an 8 x 8 pocket + rim.translate(d[2], d[3], 1.2f); + roof.merge(rim); + } + TriangleMesh insert = mesh(TestMesh::cube_20x20x20); + insert.scale(Vec3f(0.4f, 0.4f, 0.04f)); // 8 x 8 x 0.8 mm in the pocket + insert.translate(6.f, 6.f, 1.2f); + ModelObject *object = model.add_object(); + object->name = "pocketed_roof"; + ModelVolume *coarse_volume = object->add_volume(std::move(roof)); + coarse_volume->config.set("extruder", 2); + object->add_volume(std::move(insert)); + object->add_instance(); + // This fork's arrangement engine rejects positions outside the (unset) plate even for + // an InfiniteBed; place the object at a fixed bed spot like init_two_part_print(). + for (ModelObject *mo : model.objects) { + mo->center_around_origin(); + mo->translate(120., 120., 0.); + mo->ensure_on_bed(); + } + print.apply(model, config); + print.set_status_silent(); + THEN("the covering run fills the floating layers and the insert resumes on top") { + REQUIRE(print.validate().string.empty()); + print.process(); + const PrintObject &po = *print.objects().front(); + int fine_region = -1, coarse_region = -1; + find_regions(po, fine_region, coarse_region); + REQUIRE(fine_region >= 0); + REQUIRE(coarse_region >= 0); + double floating_area = 0., filled_bridge_area = 0., resumed_area = 0.; + for (size_t idx = 0; idx < po.layer_count(); ++ idx) { + const Layer *layer = po.get_layer(int(idx)); + for (int r = 0; r < layer->region_count(); ++ r) + for (const Surface &surface : layer->get_region(r)->fill_surfaces.surfaces) { + const double area = unscale(unscale(surface.expolygon.area())); + if (r == fine_region && layer->print_z < 1.7) + floating_area += area; + else if (r == coarse_region && std::abs(layer->print_z - 1.6) < EPSILON && surface.surface_type == stBottomBridge) + filled_bridge_area += area; + else if (r == fine_region && std::abs(layer->print_z - 1.8) < EPSILON) + resumed_area += area; + } + } + CAPTURE(floating_area, filled_bridge_area, resumed_area); + REQUIRE(floating_area < 0.1); // nothing of the insert prints in the air + REQUIRE(filled_bridge_area > 60.); // the coarse pass bridges rim and pocket alike + REQUIRE(resumed_area > 25.); // the insert continues on top of the pass + } + } +} + SCENARIO("Per-extruder layer height respects the extruder's minimum layer height", "[MultiNozzleLayerHeight]") { GIVEN("A 0.6 mm preferred layer height with a 0.4 mm minimum, on a part height leaving a 1-layer tail") { DynamicPrintConfig config = two_extruder_config(0.6); @@ -747,7 +875,7 @@ SCENARIO("Combined infill is limited by the printing nozzle only", "[MultiNozzle } GIVEN("Internal solid infill on the coarse filament while the walls stay on Default") { - // The user-reported setup: only internal_solid_filament_id points at filament 2 (preferred + // Only internal_solid_filament_id points at filament 2 (preferred // layer height 0.4) and no wall filament carries a preference. The feature filament's // preference must derive the part's pitch instead of being silently ignored; areas falling // back to the object layer height print below filament 2's 0.3 mm minimum, which warns. From 6fcda3dfff01cdb6fa18639019a41eb7f932af2a Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Fri, 31 Jul 2026 20:20:40 +0000 Subject: [PATCH 15/43] fix: combine internal solid infill to its filament's preferred layer height Internal solid infill was not covered by any combining pass: whole-region, walls-only and split-wall combining skip it, combine_top_surfaces() only absorbs it under a full-density top, and combine_infill() reaches it only at 100% density. Interior and shell-backing solid always fell back to the object layer height - silently below the assigned extruder's minimum layer height when a coarse nozzle prints it (0.12 mm courses on a 0.8 mm nozzle whose minimum is 0.16 mm). Add a combining pass for the leftover internal solid: groups anchor top-down at the top of each solid column (shell bands sit wherever their surface is, not on a fixed grid) and extrude once at the solid filament's preferred height, leaving VOID surfaces behind like the other passes. Leftover bands re-group over ever shorter runs down to the filament's minimum layer height, so odd-length bands don't strand a below-minimum layer. Only the single-layer seat an internal bridge rests on - tied to its own layer - and the bottom surfaces keep the object layer height, which Print::validate() warns about. Print::validate() mirrors the pass: the internal solid pitch feeds the per-feature height checks (line width, max/min layer height, unhonored preferences) like the top-surface pitch does. Reported-by: jiyang1018 (https://github.com/jiyang1018) --- src/libslic3r/Print.cpp | 49 ++++++-- src/libslic3r/Print.hpp | 3 + src/libslic3r/PrintObject.cpp | 113 ++++++++++++++++++ .../test_multi_nozzle_layer_height.cpp | 58 +++++++++ 4 files changed, 212 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index bd4a26a9094..41c1c16b7b9 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -2027,9 +2027,12 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* return region_config.top_shell_layers.value > 0 && filament == feature_filament_idx(region_config.top_surface_filament_id.value); }; - auto prints_bottom_or_solid = [&](unsigned int filament) { - return (region_config.bottom_shell_layers.value > 0 && filament == feature_filament_idx(region_config.bottom_surface_filament_id.value)) || - (! solid_combined_infill && filament == feature_filament_idx(region_config.internal_solid_filament_id.value)); + auto prints_bottom = [&](unsigned int filament) { + return region_config.bottom_shell_layers.value > 0 && + filament == feature_filament_idx(region_config.bottom_surface_filament_id.value); + }; + auto prints_solid = [&](unsigned int filament) { + return ! solid_combined_infill && filament == feature_filament_idx(region_config.internal_solid_filament_id.value); }; // Mirrors PrintObject::combine_top_surfaces(): a conforming preferred pitch of a // full-density top surface absorbs the solid layers below whenever the region itself @@ -2044,6 +2047,18 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* if (m > 1) top_candidate = m; } + // Mirrors PrintObject::combine_internal_solid_infill(): a conforming preferred + // pitch of the internal solid filament combines the leftover solid interior + // whenever the region itself prints at the object layer height (at 100% density + // the solid interior is the combined infill instead). Geometry decides per + // area, so this gates warnings only. + unsigned int solid_candidate = 1; + if (! solid_combined_infill) { + const size_t solid_extruder_idx = selector_extruder_idx(region_config.internal_solid_filament_id.value); + const unsigned int m = conforming_multiplier(m_config.extruder_layer_height.get_at(solid_extruder_idx), solid_extruder_idx); + if (m > 1) + solid_candidate = m; + } // Walls-only pitch (mirrors PrintObject::wall_layer_height_multiplier()): when the // rest of the part vetoes the walls' pitch, the walls still combine to it on their // own. Non-splittable effective preferences meet at the lower one; preference-less @@ -2234,18 +2249,25 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // feature is not fully covered by the combined height (at 100% density the internal // solid infill IS the combined infill). auto prints_pitch_features = [&](unsigned int filament) { - return prints_walls(filament) || prints_top(filament) || prints_bottom_or_solid(filament); + return prints_walls(filament) || prints_top(filament) || prints_bottom(filament) || prints_solid(filament); }; - // The top surfaces combine only while the region prints at the object layer height. + // The top surfaces and the solid interior combine only while the region prints + // at the object layer height. const unsigned int top_multiplier = region_multiplier == 1 ? top_candidate : 1; if (top_multiplier > 1) warn_above_max(top_multiplier * layer_height, feature_filament_idx(region_config.top_surface_filament_id.value), m_config.max_layer_height.get_at(selector_extruder_idx(region_config.top_surface_filament_id.value))); + const unsigned int solid_multiplier = region_multiplier == 1 ? solid_candidate : 1; + if (solid_multiplier > 1) + warn_above_max(solid_multiplier * layer_height, + feature_filament_idx(region_config.internal_solid_filament_id.value), + m_config.max_layer_height.get_at(selector_extruder_idx(region_config.internal_solid_filament_id.value))); // Heights the filament's pitch-bound features print at: walls at their class's // pitch when active, top surfaces at the top pitch when active, everything else // at the region pitch (also used by the unhonored-heights check below). - const double top_print_height = top_multiplier > 1 ? top_multiplier * layer_height : region_pitch; + const double top_print_height = top_multiplier > 1 ? top_multiplier * layer_height : region_pitch; + const double solid_print_height = solid_multiplier > 1 ? solid_multiplier * layer_height : region_pitch; auto lowest_feature_height = [&](unsigned int filament) { double h = std::numeric_limits::max(); if (prints_outer_wall(filament)) @@ -2254,8 +2276,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* h = std::min(h, inner_wall_height); if (prints_top(filament)) h = std::min(h, top_print_height); - if (prints_bottom_or_solid(filament)) + if (prints_bottom(filament)) h = std::min(h, region_pitch); + if (prints_solid(filament)) + h = std::min(h, solid_print_height); // No pitch-bound feature (e.g. the sparse filament): prints at the region pitch. return h == std::numeric_limits::max() ? region_pitch : h; }; @@ -2274,7 +2298,8 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* // the minimum. const bool fallback_below_min = (region_multiplier > 1 || ((wall_multiplier > 1 || wall_split) && prints_walls(filament)) || - (top_multiplier > 1 && prints_top(filament))) && + (top_multiplier > 1 && prints_top(filament)) || + (solid_multiplier > 1 && prints_solid(filament))) && layer_height < min_lh - EPSILON; if (! pitch_below_min && ! fallback_below_min) continue; @@ -2314,8 +2339,10 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* honored &= adjusted_inner || std::abs(inner_wall_height - preferred) < EPSILON; if (prints_top(filament)) honored &= std::abs(top_print_height - preferred) < EPSILON; - if (prints_bottom_or_solid(filament)) + if (prints_bottom(filament)) honored &= std::abs(region_pitch - preferred) < EPSILON; + if (prints_solid(filament)) + honored &= std::abs(solid_print_height - preferred) < EPSILON; if (honored) continue; } else if (std::abs(preferred - region_pitch) < EPSILON || @@ -2334,7 +2361,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* "extruder_layer_height", object); break; } - if (region_multiplier > 1 || wall_multiplier > 1 || top_multiplier > 1 || wall_split) { + if (region_multiplier > 1 || wall_multiplier > 1 || top_multiplier > 1 || solid_multiplier > 1 || wall_split) { object_has_combined_regions = true; // Combined extrusions are thicker: explicit line widths must stay above the // height each feature prints at, resolved per role against the region's own @@ -2347,7 +2374,7 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* { region_config.outer_wall_filament_id.value, "outer_wall_line_width", outer_wall_height, region_config.wall_loops.value > 0 }, { region_config.inner_wall_filament_id.value, "inner_wall_line_width", inner_wall_height, region_config.wall_loops.value > 0 }, { region_config.sparse_infill_filament_id.value, "sparse_infill_line_width", region_pitch, region_config.sparse_infill_density.value > 0 && ! solid_combined_infill }, - { region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width", region_pitch, true }, + { region_config.internal_solid_filament_id.value, "internal_solid_infill_line_width", solid_print_height, true }, { region_config.top_surface_filament_id.value, "top_surface_line_width", top_print_height, region_config.top_shell_layers.value > 0 }, { region_config.bottom_surface_filament_id.value, "internal_solid_infill_line_width", region_pitch, region_config.bottom_shell_layers.value > 0 }, }; diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index c4385ec3c0f..5ab02049a60 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -618,6 +618,9 @@ class PrintObject : public PrintObjectBaseWithState prepare_adaptive_infill_data( const std::vector>& surfaces_w_bottom_z) const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 6a6a0ddf312..47853b53cdf 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -521,6 +521,11 @@ void PrintObject::prepare_infill() this->combine_top_surfaces(); m_print->throw_if_canceled(); + // Per-extruder layer height: the solid interior the top surfaces did not absorb combines to + // its own filament's preferred height. + this->combine_internal_solid_infill(); + m_print->throw_if_canceled(); + // combine fill surfaces to honor the "infill every N layers" option this->combine_infill(); m_print->throw_if_canceled(); @@ -4863,6 +4868,114 @@ void PrintObject::combine_top_surfaces() } } +// Per-extruder layer height: print the internal solid infill left over after +// combine_top_surfaces() - shell backing and other solid interior - with the preferred layer +// height of its filament by combining runs of solid layers into one thick pass, leaving VOID +// surfaces behind like the other combining passes. Groups anchor top-down at the top of each +// solid column, because shell bands start wherever their surface sits, not on a fixed grid. +// Leftovers that cannot reach the full preferred height re-group over ever shorter runs down to +// the filament's min layer height (isolated single layers stay at the object layer height and +// Print::validate() warns). +void PrintObject::combine_internal_solid_infill() +{ + for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { + const PrintRegion ®ion = this->printing_region(region_id); + const PrintRegionConfig &config = region.config(); + // At 100% sparse density the whole interior is solid and combine_infill() owns it. + if (std::fabs(config.sparse_infill_density.value - 100.) < EPSILON) + continue; + // Only for regions printing at the object layer height: combined regions already extrude + // everything - solid infill included - once per group over their group tops. + if (this->region_layer_height_multiplier(region) > 1) + continue; + const unsigned int mult = this->layer_height_multiplier_for_filament( + (unsigned int)std::max(0, config.internal_solid_filament_id.value)); + if (mult <= 1) + continue; + // Surfaces not combined yet (groups formed by an earlier sweep have their thickness set). + auto leftover_solid = [](const LayerRegion *layerm) { + ExPolygons out; + for (const Surface &surface : layerm->fill_surfaces.surfaces) + if (surface.surface_type == stInternalSolid && surface.thickness < 0.) + out.emplace_back(surface.expolygon); + return out; + }; + auto combine_solid_runs = [&](size_t m) { + for (size_t layer_idx = m_layers.size(); layer_idx-- > 0; ) { + m_print->throw_if_canceled(); + if (layer_idx + 1 < m) + break; + // Never absorb the first print layer: it keeps its own height for bed adhesion. + if (m_layers[layer_idx - m + 1]->id() == 0) + continue; + LayerRegion *top_layerm = m_layers[layer_idx]->regions()[region_id]; + ExPolygons combined = leftover_solid(top_layerm); + if (combined.empty()) + continue; + // Uniform layer heights only (mirrors apply_extruder_layer_heights()). + bool uniform = true; + for (size_t i = layer_idx - m + 1; uniform && i < layer_idx; ++ i) + uniform = std::abs(m_layers[i]->height - m_layers[layer_idx]->height) < EPSILON; + if (! uniform) + continue; + std::vector layerms; // the whole group, bottom-up + for (size_t i = layer_idx - m + 1; i <= layer_idx; ++ i) + layerms.emplace_back(m_layers[i]->regions()[region_id]); + for (size_t i = 0; i + 1 < layerms.size() && ! combined.empty(); ++ i) + combined = intersection_ex(leftover_solid(layerms[i]), combined); + remove_small_expolygons(combined, top_layerm->infill_area_threshold()); + if (combined.empty()) + continue; + // Clearance against the group's remaining solid infill, which is grown later to + // overlap perimeters (mirrors combine_top_surfaces()'s clearance). + Polygons combined_with_clearance; + combined_with_clearance.reserve(combined.size()); + const float clearance_offset = 0.5f * top_layerm->flow(frPerimeter).scaled_width() + + 1.5f * top_layerm->flow(frSolidInfill).scaled_width(); + for (const ExPolygon &expoly : combined) + polygons_append(combined_with_clearance, offset(expoly, clearance_offset)); + for (LayerRegion *layerm : layerms) { + // Take out only the uncombined surfaces; earlier groups keep their heights. + Polygons leftover; + Surfaces kept; + kept.reserve(layerm->fill_surfaces.surfaces.size()); + for (Surface &surface : layerm->fill_surfaces.surfaces) { + if (surface.surface_type == stInternalSolid && surface.thickness < 0.) + polygons_append(leftover, to_polygons(std::move(surface.expolygon))); + else + kept.emplace_back(std::move(surface)); + } + layerm->fill_surfaces.surfaces = std::move(kept); + layerm->fill_surfaces.append(diff_ex(leftover, combined_with_clearance), stInternalSolid); + if (layerm == layerms.back()) { + // The combined areas extrude once with the whole group's thickness (Fill + // resolves the flow from Surface::thickness). + Surface templ(stInternalSolid, ExPolygon()); + templ.thickness = 0.; + for (const LayerRegion *layerm2 : layerms) + templ.thickness += layerm2->layer()->height; + templ.thickness_layers = (unsigned short)m; + layerm->fill_surfaces.append(std::move(combined), templ); + } else { + layerm->fill_surfaces.append(intersection_ex(leftover, combined_with_clearance), stInternalVoid); + } + } + } + }; + combine_solid_runs(mult); + // Where the layer stack cannot reach the full preferred height it falls back to the + // object layer height, possibly below the solid filament's minimum layer height: re-group + // with ever shorter runs down to the smallest count reaching the minimum, so odd-length + // bands don't strand a below-minimum single layer. + const double min_layer_height = m_print->config().min_layer_height.get_at( + m_print->extruder_index_of(feature_filament_idx(config.internal_solid_filament_id.value))); + const auto min_mult = (unsigned int)std::ceil(min_layer_height / m_config.layer_height.value - EPSILON); + if (min_mult > 1) + for (unsigned int m = mult - 1; m >= min_mult; -- m) + combine_solid_runs(m); + } +} + // combine fill surfaces across layers to honor the "infill every N layers" option // Idempotence of this method is guaranteed by the fact that we don't remove things from // fill_surfaces but we only turn them into VOID surfaces, thus preserving the boundaries. diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index 20c31292bd8..776ab99c7c1 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -1256,6 +1256,64 @@ SCENARIO("Top surfaces combine to their filament's pitch by absorbing the shells } } +SCENARIO("Internal solid infill combines to its filament's pitch", "[MultiNozzleLayerHeight]") { + GIVEN("Disagreeing wall preferences with the internal solid infill on the 0.8 mm nozzle filament") { + // The wall filaments' explicit preferences disagree (0.12 vs 0.24 mm), so the part keeps + // the object layer height and the walls split. The internal solid infill used to fall + // through every combining pass here, printing 0.12 mm layers on the 0.8 mm nozzle - below + // its own 0.16 mm minimum layer height. + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("outer_wall_filament_id", new ConfigOptionInt(1)); + config.set_key_value("inner_wall_filament_id", new ConfigOptionInt(2)); + config.set_key_value("sparse_infill_filament_id", new ConfigOptionInt(3)); + config.set_key_value("internal_solid_filament_id", new ConfigOptionInt(4)); + config.set_key_value("top_surface_filament_id", new ConfigOptionInt(4)); + config.set_key_value("bottom_surface_filament_id", new ConfigOptionInt(4)); + config.set_key_value("top_shell_layers", new ConfigOptionInt(4)); + config.set_key_value("bottom_shell_layers", new ConfigOptionInt(5)); + config.set_key_value("sparse_infill_density", new ConfigOptionPercent(15)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("the solid interior prints 0.48 mm groups and never below the extruder's minimum") { + StringObjectException warning; + REQUIRE(print.validate(&warning).string.empty()); + // The bottom surfaces (also filament 4) keep printing the object layer height: warned. + REQUIRE(warning.string.find("minimum layer") != std::string::npos); + print.process(); + + const PrintObject &object = *print.objects().front(); + // The one legitimate below-minimum leftover: the single solid layer an internal + // bridge rests on. It is tied to its own layer (sparse below, the bridge above), so + // no combining can lift it to the minimum. + auto seats_internal_bridge = [&object](size_t idx) { + if (idx + 1 >= object.layer_count()) + return false; + const Surfaces &above = object.get_layer(int(idx + 1))->get_region(0)->fill_surfaces.surfaces; + return std::any_of(above.begin(), above.end(), + [](const Surface &surface) { return surface.surface_type == stInternalBridge; }); + }; + size_t tall_solid_paths = 0, below_min_paths = 0; + std::vector below_min_layers; + for (size_t idx = 1; idx < object.layer_count(); ++ idx) + for_each_path(object.get_layer(int(idx))->get_region(0)->fills, [&](const ExtrusionPath &path) { + if (path.role() != erSolidInfill) + return; + if (path.height > 0.48f - 1e-3f) + ++ tall_solid_paths; + else if (path.height < 0.16f - 1e-3f && ! seats_internal_bridge(idx)) { + ++ below_min_paths; + if (below_min_layers.empty() || below_min_layers.back() != idx) + below_min_layers.push_back(idx); + } + }); + CAPTURE(below_min_layers); + CHECK(tall_solid_paths > 0); + CHECK(below_min_paths == 0); + } + } +} + SCENARIO("A preference-less fine-nozzle wall filament vetoes the walls-only pitch", "[MultiNozzleLayerHeight]") { GIVEN("Outer walls on the 0.8 mm nozzle preferring 0.48 mm, inner walls on a 0.2 mm nozzle with no preference") { DynamicPrintConfig config = four_nozzle_config(); From 311679cbb5a12438ac99a6c2aaa68c6b68d6d249 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Sun, 2 Aug 2026 05:43:24 +0000 Subject: [PATCH 16/43] fix: combine sparse infill on one grid phase honoring the min layer height With an explicit preferred layer height, sparse infill combined on a fixed window grid: windows align to the global layer stack, not to where an infill band starts at a shell boundary, so band-edge layers fell out of every window - and out of the min-height re-group pass, which used the same grid - and printed at the object layer height, below the infill extruder's minimum layer height when one is set. Extract the anchored run machinery of combine_internal_solid_infill() into a shared combine_surface_runs() and use it for the preference-driven infill path too. The full-height sparse runs stay anchored on one global grid phase, so a contiguous field extrudes on the same layers everywhere (independently anchored areas would combine phase-shifted wherever their band tops differ, leaving a permanent one-course step along the seam). Band edges re-group over ever shorter runs down to the extruder's min layer height, with one-layer lookaheads at both band ends splitting off pairs instead of stranding a below-minimum single layer. Internal solid keeps free band-top anchors - its short shell bands sit wherever their surface is and would mostly miss a fixed grid. The plain infill_combination option keeps the stock window grid. --- src/libslic3r/Print.hpp | 4 + src/libslic3r/PrintObject.cpp | 320 ++++++++---------- .../test_multi_nozzle_layer_height.cpp | 24 ++ 3 files changed, 166 insertions(+), 182 deletions(-) diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index 5ab02049a60..ae58b7a0180 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -10,6 +10,7 @@ #include "Flow.hpp" #include "Point.hpp" #include "Slicing.hpp" +#include "Surface.hpp" #include "TriangleMeshSlicer.hpp" #include "GCode/ToolOrdering.hpp" #include "GCode/WipeTower.hpp" @@ -621,6 +622,9 @@ class PrintObject : public PrintObjectBaseWithState prepare_adaptive_infill_data( const std::vector>& surfaces_w_bottom_z) const; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 47853b53cdf..236e2fcf407 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -4868,14 +4868,126 @@ void PrintObject::combine_top_surfaces() } } +// Per-extruder layer height: combine runs of a region's not-yet-combined fill surfaces of the +// given type into one thick pass extruding at the run top (Fill resolves the flow from +// Surface::thickness), leaving VOID surfaces behind like combine_infill(). grid_aligned anchors +// the full-height runs on one global grid so a contiguous field extrudes on the same layers +// (bands whose tops differ would combine phase-shifted, stepping one course at the seam); +// without it runs anchor at each column's top (solid shell bands rarely fit the grid). Leftover +// bands re-group over ever shorter runs down to min_mult, the smallest count reaching the +// extruder's min layer height; one-layer lookaheads at both band ends split off pairs instead +// of stranding a single below-minimum layer (a true single layer between other features stays +// at the object layer height and Print::validate() warns). +void PrintObject::combine_surface_runs(size_t region_id, SurfaceType surface_type, unsigned int mult, unsigned int min_mult, float fill_clearance_factor, bool grid_aligned) +{ + // Surfaces not combined yet (groups formed by an earlier sweep have their thickness set). + auto leftover = [surface_type, region_id](const Layer *layer) { + ExPolygons out; + for (const Surface &surface : layer->regions()[region_id]->fill_surfaces.surfaces) + if (surface.surface_type == surface_type && surface.thickness < 0.) + out.emplace_back(surface.expolygon); + return out; + }; + // One combined pass over the run [layer_idx - m + 1, layer_idx] limited to the given areas. + auto commit_run = [&](size_t layer_idx, size_t m, ExPolygons &&combined) { + if (combined.empty()) + return; + LayerRegion *top_layerm = m_layers[layer_idx]->regions()[region_id]; + // Clearance against the run's remaining fills, which are grown later to overlap + // perimeters (mirrors combine_infill()'s clearance). + Polygons combined_with_clearance; + combined_with_clearance.reserve(combined.size()); + const float clearance_offset = 0.5f * top_layerm->flow(frPerimeter).scaled_width() + + fill_clearance_factor * top_layerm->flow(frSolidInfill).scaled_width(); + for (const ExPolygon &expoly : combined) + polygons_append(combined_with_clearance, offset(expoly, clearance_offset)); + for (size_t i = layer_idx + 1 - m; i <= layer_idx; ++ i) { + LayerRegion *layerm = m_layers[i]->regions()[region_id]; + // Take out only the uncombined surfaces; earlier runs keep their heights. + Polygons pieces; + Surfaces kept; + kept.reserve(layerm->fill_surfaces.surfaces.size()); + for (Surface &surface : layerm->fill_surfaces.surfaces) { + if (surface.surface_type == surface_type && surface.thickness < 0.) + polygons_append(pieces, to_polygons(std::move(surface.expolygon))); + else + kept.emplace_back(std::move(surface)); + } + layerm->fill_surfaces.surfaces = std::move(kept); + layerm->fill_surfaces.append(diff_ex(pieces, combined_with_clearance), surface_type); + if (i == layer_idx) { + // The combined areas extrude once with the whole run's thickness. + Surface templ(surface_type, ExPolygon()); + templ.thickness = 0.; + for (size_t j = layer_idx + 1 - m; j <= layer_idx; ++ j) + templ.thickness += m_layers[j]->height; + templ.thickness_layers = (unsigned short)m; + layerm->fill_surfaces.append(std::move(combined), templ); + } else { + layerm->fill_surfaces.append(intersection_ex(pieces, combined_with_clearance), stInternalVoid); + } + } + }; + auto combine_runs = [&](size_t m, bool aligned) { + for (size_t layer_idx = m_layers.size(); layer_idx-- > 0; ) { + m_print->throw_if_canceled(); + if (layer_idx + 1 < m) + break; + if (aligned && layer_idx % m != 0) + continue; + // Never absorb the first print layer: it keeps its own height for bed adhesion. + if (m_layers[layer_idx - m + 1]->id() == 0) + continue; + ExPolygons combined = leftover(m_layers[layer_idx]); + if (combined.empty()) + continue; + // Uniform layer heights only (mirrors apply_extruder_layer_heights()). + bool uniform = true; + for (size_t i = layer_idx - m + 1; uniform && i < layer_idx; ++ i) + uniform = std::abs(m_layers[i]->height - m_layers[layer_idx]->height) < EPSILON; + if (! uniform) + continue; + for (size_t i = layer_idx - m + 1; i < layer_idx && ! combined.empty(); ++ i) + combined = intersection_ex(leftover(m_layers[i]), combined); + remove_small_expolygons(combined, m_layers[layer_idx]->regions()[region_id]->infill_area_threshold()); + if (combined.empty()) + continue; + // Where a band ends exactly one layer above an aligned run (free runs anchor at band + // tops), committing would strand that layer below the minimum: leave the area to the + // shorter runs, which split the m + 1 layers into printable groups. + if (aligned && min_mult > 1 && mult > min_mult && layer_idx + 1 < m_layers.size()) { + ExPolygons above = intersection_ex(leftover(m_layers[layer_idx + 1]), combined); + if (! above.empty() && layer_idx + 2 < m_layers.size()) + above = diff_ex(above, leftover(m_layers[layer_idx + 2])); + if (! above.empty()) { + combined = diff_ex(combined, above); + if (combined.empty()) + continue; + } + } + // Where the band continues exactly one layer below the run, take one layer less so + // the leftover pair re-groups instead of stranding below the minimum. + ExPolygons shrunk; + if (min_mult > 1 && m > min_mult && layer_idx >= m && m_layers[layer_idx - m]->id() != 0) { + shrunk = intersection_ex(leftover(m_layers[layer_idx - m]), combined); + if (! shrunk.empty() && layer_idx > m) + shrunk = diff_ex(shrunk, leftover(m_layers[layer_idx - m - 1])); + if (! shrunk.empty()) + combined = diff_ex(combined, shrunk); + } + commit_run(layer_idx, m, std::move(combined)); + commit_run(layer_idx, m - 1, std::move(shrunk)); + } + }; + combine_runs(mult, grid_aligned); + if (min_mult > 1) + for (unsigned int m = mult - 1; m >= min_mult; -- m) + combine_runs(m, false); +} + // Per-extruder layer height: print the internal solid infill left over after // combine_top_surfaces() - shell backing and other solid interior - with the preferred layer -// height of its filament by combining runs of solid layers into one thick pass, leaving VOID -// surfaces behind like the other combining passes. Groups anchor top-down at the top of each -// solid column, because shell bands start wherever their surface sits, not on a fixed grid. -// Leftovers that cannot reach the full preferred height re-group over ever shorter runs down to -// the filament's min layer height (isolated single layers stay at the object layer height and -// Print::validate() warns). +// height of its filament. void PrintObject::combine_internal_solid_infill() { for (size_t region_id = 0; region_id < this->num_printing_regions(); ++ region_id) { @@ -4892,87 +5004,10 @@ void PrintObject::combine_internal_solid_infill() (unsigned int)std::max(0, config.internal_solid_filament_id.value)); if (mult <= 1) continue; - // Surfaces not combined yet (groups formed by an earlier sweep have their thickness set). - auto leftover_solid = [](const LayerRegion *layerm) { - ExPolygons out; - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == stInternalSolid && surface.thickness < 0.) - out.emplace_back(surface.expolygon); - return out; - }; - auto combine_solid_runs = [&](size_t m) { - for (size_t layer_idx = m_layers.size(); layer_idx-- > 0; ) { - m_print->throw_if_canceled(); - if (layer_idx + 1 < m) - break; - // Never absorb the first print layer: it keeps its own height for bed adhesion. - if (m_layers[layer_idx - m + 1]->id() == 0) - continue; - LayerRegion *top_layerm = m_layers[layer_idx]->regions()[region_id]; - ExPolygons combined = leftover_solid(top_layerm); - if (combined.empty()) - continue; - // Uniform layer heights only (mirrors apply_extruder_layer_heights()). - bool uniform = true; - for (size_t i = layer_idx - m + 1; uniform && i < layer_idx; ++ i) - uniform = std::abs(m_layers[i]->height - m_layers[layer_idx]->height) < EPSILON; - if (! uniform) - continue; - std::vector layerms; // the whole group, bottom-up - for (size_t i = layer_idx - m + 1; i <= layer_idx; ++ i) - layerms.emplace_back(m_layers[i]->regions()[region_id]); - for (size_t i = 0; i + 1 < layerms.size() && ! combined.empty(); ++ i) - combined = intersection_ex(leftover_solid(layerms[i]), combined); - remove_small_expolygons(combined, top_layerm->infill_area_threshold()); - if (combined.empty()) - continue; - // Clearance against the group's remaining solid infill, which is grown later to - // overlap perimeters (mirrors combine_top_surfaces()'s clearance). - Polygons combined_with_clearance; - combined_with_clearance.reserve(combined.size()); - const float clearance_offset = 0.5f * top_layerm->flow(frPerimeter).scaled_width() + - 1.5f * top_layerm->flow(frSolidInfill).scaled_width(); - for (const ExPolygon &expoly : combined) - polygons_append(combined_with_clearance, offset(expoly, clearance_offset)); - for (LayerRegion *layerm : layerms) { - // Take out only the uncombined surfaces; earlier groups keep their heights. - Polygons leftover; - Surfaces kept; - kept.reserve(layerm->fill_surfaces.surfaces.size()); - for (Surface &surface : layerm->fill_surfaces.surfaces) { - if (surface.surface_type == stInternalSolid && surface.thickness < 0.) - polygons_append(leftover, to_polygons(std::move(surface.expolygon))); - else - kept.emplace_back(std::move(surface)); - } - layerm->fill_surfaces.surfaces = std::move(kept); - layerm->fill_surfaces.append(diff_ex(leftover, combined_with_clearance), stInternalSolid); - if (layerm == layerms.back()) { - // The combined areas extrude once with the whole group's thickness (Fill - // resolves the flow from Surface::thickness). - Surface templ(stInternalSolid, ExPolygon()); - templ.thickness = 0.; - for (const LayerRegion *layerm2 : layerms) - templ.thickness += layerm2->layer()->height; - templ.thickness_layers = (unsigned short)m; - layerm->fill_surfaces.append(std::move(combined), templ); - } else { - layerm->fill_surfaces.append(intersection_ex(leftover, combined_with_clearance), stInternalVoid); - } - } - } - }; - combine_solid_runs(mult); - // Where the layer stack cannot reach the full preferred height it falls back to the - // object layer height, possibly below the solid filament's minimum layer height: re-group - // with ever shorter runs down to the smallest count reaching the minimum, so odd-length - // bands don't strand a below-minimum single layer. const double min_layer_height = m_print->config().min_layer_height.get_at( m_print->extruder_index_of(feature_filament_idx(config.internal_solid_filament_id.value))); const auto min_mult = (unsigned int)std::ceil(min_layer_height / m_config.layer_height.value - EPSILON); - if (min_mult > 1) - for (unsigned int m = mult - 1; m >= min_mult; -- m) - combine_solid_runs(m); + this->combine_surface_runs(region_id, stInternalSolid, mult, min_mult, 1.5f, false); } } @@ -5023,9 +5058,27 @@ void PrintObject::combine_infill() // Per-extruder layer height: the preferred height is an explicit target overriding the // caps above, limited only by the bore of the nozzle extruding it (max_layer_height is a // soft limit, Print::validate() warns). Plain infill_combination keeps its own cap. - if (preferred_infill_height > 0.) + if (preferred_infill_height > 0.) { nozzle_diameter = std::min(preferred_infill_height, this->print()->config().nozzle_diameter.get_at(combine_extruder_idx)); + // An explicit preference combines through combine_surface_runs() instead of the + // fixed window grid below, honoring the extruder's min layer height at the band + // edges the grid would strand at the object layer height. + const auto mult = (unsigned int)std::max(1, int(std::floor(nozzle_diameter / m_config.layer_height.value + EPSILON))); + const auto min_mult = (unsigned int)std::ceil( + this->print()->config().min_layer_height.get_at(combine_extruder_idx) / m_config.layer_height.value - EPSILON); + if (mult > 1) + this->combine_surface_runs(region_id, surface_type, mult, min_mult, + (infill_pattern == ipRectilinear || + infill_pattern == ipMonotonic || + infill_pattern == ipGrid || + infill_pattern == ipLateralLattice|| + infill_pattern == ipLine || + infill_pattern == ipHoneycomb || + infill_pattern == ipLateralHoneycomb) ? 1.5f : 0.5f, + true); + continue; + } // define the combinations std::vector combine(m_layers.size(), 0); @@ -5117,103 +5170,6 @@ void PrintObject::combine_infill() } } } - - // Per-extruder layer height: leftovers the pass above could not combine to the full group height print at the object layer height. - // Where that is below the infill extruder's min layer height, re-group them over the smallest layer count reaching the minimum (isolated leftovers with no matching neighbors stay as-is; Print::validate() warns). - const double combine_min_layer_height = this->print()->config().min_layer_height.get_at(combine_extruder_idx); - const double min_window = combine_min_layer_height > 0. ? - std::ceil(combine_min_layer_height / m_config.layer_height.value - EPSILON) * m_config.layer_height.value : 0.; - // Re-grouped windows must stay within the main pass' caps (a min_layer_height above them is - // a misconfiguration). - if (preferred_infill_height > 0. && combine_min_layer_height > m_config.layer_height.value + EPSILON && - min_window <= nozzle_diameter + EPSILON) { - // Assign leftover windows exactly like the pass above, just with the shorter target. - std::vector recombine(m_layers.size(), 0); - { - double current_height = 0.; - size_t num_layers = 0; - for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { - m_print->throw_if_canceled(); - const Layer *layer = m_layers[layer_idx]; - if (layer->id() == 0) - // Skip first print layer (which may not be first layer in array because of raft). - continue; - if (current_height + layer->height >= min_window + EPSILON) { - recombine[layer_idx - 1] = num_layers; - current_height = 0.; - num_layers = 0; - } - current_height += layer->height; - ++ num_layers; - } - recombine[m_layers.size() - 1] = num_layers; - } - // Leftovers are surfaces of the combined type whose thickness is still unset. - auto leftover_expolygons = [surface_type](const LayerRegion *layerm) { - ExPolygons out; - for (const Surface &surface : layerm->fill_surfaces.surfaces) - if (surface.surface_type == surface_type && surface.thickness < 0.) - out.emplace_back(surface.expolygon); - return out; - }; - for (size_t layer_idx = 0; layer_idx < m_layers.size(); ++ layer_idx) { - m_print->throw_if_canceled(); - size_t num_layers = recombine[layer_idx]; - if (num_layers <= 1) - continue; - std::vector layerms; - layerms.reserve(num_layers); - for (size_t i = layer_idx + 1 - num_layers; i <= layer_idx; ++ i) - layerms.emplace_back(m_layers[i]->regions()[region_id]); - ExPolygons intersection = leftover_expolygons(layerms.front()); - for (size_t i = 1; i < layerms.size() && ! intersection.empty(); ++ i) - intersection = intersection_ex(leftover_expolygons(layerms[i]), intersection); - remove_small_expolygons(intersection, layerms.front()->infill_area_threshold()); - if (intersection.empty()) - continue; - // Same clearance as the pass above: the leftover fills also grow to overlap perimeters. - Polygons intersection_with_clearance; - intersection_with_clearance.reserve(intersection.size()); - float clearance_offset = - 0.5f * layerms.back()->flow(frPerimeter).scaled_width() + - ((infill_pattern == ipRectilinear || - infill_pattern == ipMonotonic || - infill_pattern == ipGrid || - infill_pattern == ipLateralLattice|| - infill_pattern == ipLine || - infill_pattern == ipHoneycomb || - infill_pattern == ipLateralHoneycomb) ? 1.5f : 0.5f) * - layerms.back()->flow(frSolidInfill).scaled_width(); - for (ExPolygon &expoly : intersection) - polygons_append(intersection_with_clearance, offset(expoly, clearance_offset)); - for (LayerRegion *layerm : layerms) { - // Take out only the leftover surfaces; surfaces combined above keep their heights. - Polygons leftover; - Surfaces kept; - kept.reserve(layerm->fill_surfaces.surfaces.size()); - for (Surface &surface : layerm->fill_surfaces.surfaces) { - if (surface.surface_type == surface_type && surface.thickness < 0.) - polygons_append(leftover, to_polygons(std::move(surface.expolygon))); - else - kept.emplace_back(std::move(surface)); - } - layerm->fill_surfaces.surfaces = std::move(kept); - layerm->fill_surfaces.append(diff_ex(leftover, intersection_with_clearance), surface_type); - if (layerm == layerms.back()) { - Surface templ(surface_type, ExPolygon()); - templ.thickness = 0.; - for (LayerRegion *layerm2 : layerms) - templ.thickness += layerm2->layer()->height; - templ.thickness_layers = (unsigned short)layerms.size(); - layerm->fill_surfaces.append(intersection, templ); - } else { - layerm->fill_surfaces.append( - intersection_ex(leftover, intersection_with_clearance), - stInternalVoid); - } - } - } - } } } diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index 776ab99c7c1..580e41f34eb 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -1294,9 +1294,19 @@ SCENARIO("Internal solid infill combines to its filament's pitch", "[MultiNozzle [](const Surface &surface) { return surface.surface_type == stInternalBridge; }); }; size_t tall_solid_paths = 0, below_min_paths = 0; + size_t tall_sparse_paths = 0, below_min_sparse_paths = 0; std::vector below_min_layers; for (size_t idx = 1; idx < object.layer_count(); ++ idx) for_each_path(object.get_layer(int(idx))->get_region(0)->fills, [&](const ExtrusionPath &path) { + if (path.role() == erInternalInfill) { + // The sparse infill (filament 3, preferring 0.36 mm) must honor its own + // 0.14 mm minimum too: band-edge leftovers re-group instead of stranding. + if (path.height > 0.36f - 1e-3f) + ++ tall_sparse_paths; + else if (path.height < 0.14f - 1e-3f) + ++ below_min_sparse_paths; + return; + } if (path.role() != erSolidInfill) return; if (path.height > 0.48f - 1e-3f) @@ -1307,9 +1317,23 @@ SCENARIO("Internal solid infill combines to its filament's pitch", "[MultiNozzle below_min_layers.push_back(idx); } }); + // Phase coherence: the uniform interior must extrude on the same layers everywhere; + // phase-shifted areas would leave a permanent one-course step along their seam. + size_t sparse_layers = 0; + for (size_t idx = 10; idx < 40; ++ idx) { + bool has_sparse = false; + for_each_path(object.get_layer(int(idx))->get_region(0)->fills, [&](const ExtrusionPath &path) { + has_sparse |= path.role() == erInternalInfill; + }); + if (has_sparse) + ++ sparse_layers; + } CAPTURE(below_min_layers); CHECK(tall_solid_paths > 0); CHECK(below_min_paths == 0); + CHECK(tall_sparse_paths > 0); + CHECK(below_min_sparse_paths == 0); + CHECK(sparse_layers <= 12); } } } From a2d580649b4fbda6da48a20eb73e0000810f398a Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 3 Aug 2026 17:52:43 +0000 Subject: [PATCH 17/43] Support material selection for mixed nozzle sizes Enabling support on a printer whose extruders have differing nozzle sizes opens a dialog asking which nozzle size prints the support and which loaded filament types serve as the raft/base and the interface. The choices land in the support nozzle diameter option and two new print options, "Support/raft base material" and "Support/raft interface material", shown in Support -> Filament for Supports; the legacy base/interface filament selectors keep working behind a "Show legacy filament selection" toggle so older projects stay valid. The material options act as exclusion filters, mirroring the support nozzle diameter restriction: extruders loaded with another material type are not used for the role, a "default" (0) support filament still resolves per layer (a passing filament already printing the layer is preferred, with a deterministic fallback otherwise), and an explicitly selected support filament stays authoritative. Validation rejects a material no loaded filament matches and an explicit selection that conflicts with the material. --- src/libslic3r/GCode.cpp | 14 +-- src/libslic3r/GCode/ToolOrdering.cpp | 20 +-- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 31 ++++- src/libslic3r/Print.hpp | 19 +-- src/libslic3r/PrintConfig.cpp | 26 ++++ src/libslic3r/PrintConfig.hpp | 2 + src/libslic3r/PrintObject.cpp | 29 +++-- src/slic3r/GUI/ConfigManipulation.cpp | 115 +++++++++++++++++- src/slic3r/GUI/ConfigManipulation.hpp | 3 + src/slic3r/GUI/OptionsGroup.cpp | 4 + src/slic3r/GUI/Tab.cpp | 31 +++++ .../test_multi_nozzle_layer_height.cpp | 69 +++++++++++ 13 files changed, 322 insertions(+), 43 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 9865cdd943b..971dc8bf384 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -5420,15 +5420,15 @@ LayerResult GCode::process_layer(const Print& print, if (interface_dontcare) interface_extruder = dontcare_extruder; } - // ORCA: with a support nozzle diameter restriction, ("don't care") support/interface may only use a nozzle-matching extruder; prefer one scheduled on this layer (as ToolOrdering did). - if (object.config().support_nozzle_diameter.value > 0.) { - auto restrict_to_support_nozzle = [&print, &object, &layer_tools](unsigned int extruder_id) -> unsigned int { - if (object.support_filament_allowed(extruder_id + 1)) + // ORCA: with support nozzle diameter / material restrictions, ("don't care") support/interface may only use a passing extruder; prefer one scheduled on this layer (as ToolOrdering did). + if (object.has_support_filament_restriction()) { + auto restrict_to_support_filaments = [&print, &object, &layer_tools](unsigned int extruder_id, bool interface_role) -> unsigned int { + if (object.support_filament_allowed(extruder_id + 1, interface_role)) return extruder_id; unsigned int fallback = extruder_id; bool have_fallback = false; for (unsigned int candidate : layer_tools.extruders) // 0 based at this point - if (object.support_filament_allowed(candidate + 1)) { + if (object.support_filament_allowed(candidate + 1, interface_role)) { if (! print.config().filament_soluble.get_at(candidate)) return candidate; if (! have_fallback) { @@ -5439,9 +5439,9 @@ LayerResult GCode::process_layer(const Print& print, return fallback; }; if (support_dontcare) - support_extruder = restrict_to_support_nozzle(support_extruder); + support_extruder = restrict_to_support_filaments(support_extruder, false); if (interface_dontcare) - interface_extruder = restrict_to_support_nozzle(interface_extruder); + interface_extruder = restrict_to_support_filaments(interface_extruder, true); } // Both the support and the support interface are printed with the same extruder, therefore // the interface may be interleaved with the support base. diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 43af289e9cf..133bfec0123 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -786,25 +786,25 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto float(support_layer->print_z), float(support_layer->height), &object); - // support nozzle diameter restriction, resolve a "default" (0) support filament to a nozzle-matching one here, preferring a filament this layer already prints with to save toolchanges. + // support nozzle diameter / material restrictions, resolve a "default" (0) support filament to a passing one here, preferring a filament this layer already prints with to save toolchanges. // Note: this fork collects support before the object's own layers, so the preference scan // only sees filaments of previously collected objects and otherwise settles on the // deterministic resolved_default_support_filament(); GCode::process_layer() re-prefers // layer-scheduled extruders for "don't care" support when the G-code is emitted. - if (object.config().support_nozzle_diameter.value > 0.) { - auto restrict_default_filament = [&object, &layer_tools](unsigned int configured) -> unsigned int { + if (object.has_support_filament_restriction()) { + auto restrict_default_filament = [&object, &layer_tools](unsigned int configured, bool interface_role) -> unsigned int { if (configured != 0) return configured; const PrintConfig &print_config = object.print()->config(); for (unsigned int filament : layer_tools.extruders) // 1 based at this point - if (filament > 0 && object.support_filament_allowed(filament) && + if (filament > 0 && object.support_filament_allowed(filament, interface_role) && ! print_config.filament_soluble.get_at(filament - 1)) return filament; - unsigned int resolved = object.resolved_default_support_filament(); + unsigned int resolved = object.resolved_default_support_filament(interface_role); return resolved > 0 ? resolved : configured; }; - extruder_support = restrict_default_filament(extruder_support); - extruder_interface = restrict_default_filament(extruder_interface); + extruder_support = restrict_default_filament(extruder_support, false); + extruder_interface = restrict_default_filament(extruder_interface, true); } if (has_support) layer_tools.extruders.push_back(extruder_support); @@ -1739,9 +1739,9 @@ float WipingExtrusions::mark_wiping_extrusions(const Print& print, unsigned int if (this_support_layer == nullptr) break; - // Only a nozzle matching the object's support nozzle diameter restriction may flush into its support. - bool support_overriddable = object_config.support_filament == 0 && object->support_filament_allowed(new_extruder + 1); - bool support_intf_overriddable = object_config.support_interface_filament == 0 && object->support_filament_allowed(new_extruder + 1); + // Only a filament passing the object's support restrictions (nozzle diameter, material) may flush into its support. + bool support_overriddable = object_config.support_filament == 0 && object->support_filament_allowed(new_extruder + 1, false); + bool support_intf_overriddable = object_config.support_interface_filament == 0 && object->support_filament_allowed(new_extruder + 1, true); if (!support_overriddable && !support_intf_overriddable) break; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index b4f8616bb9d..4157a265e6a 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -893,7 +893,7 @@ static std::vector s_Preset_print_options { "support_interface_pattern", "support_interface_spacing", "support_interface_loop_pattern", "support_top_z_distance", "support_on_build_plate_only","support_critical_regions_only", "bridge_no_support", "thick_bridges", "thick_internal_bridges","dont_filter_internal_bridges","enable_extra_bridge_layer", "max_bridge_length", "print_sequence", "print_order", "support_remove_small_overhang", "filename_format", "outer_wall_filament_id", "inner_wall_filament_id", "support_bottom_z_distance", - "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_nozzle_diameter", "support_interface_filament","support_interface_not_for_body", + "sparse_infill_filament_id", "internal_solid_filament_id", "top_surface_filament_id", "bottom_surface_filament_id", "support_filament", "support_nozzle_diameter", "support_base_material", "support_interface_material", "support_interface_filament","support_interface_not_for_body", "ooze_prevention", "standby_temperature_delta", "preheat_time","delta_temperature","preheat_steps", "interface_shells", "line_width", "initial_layer_line_width", "inner_wall_line_width", "outer_wall_line_width", "sparse_infill_line_width", "internal_solid_infill_line_width", "skin_infill_line_width","skeleton_infill_line_width", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 41c1c16b7b9..9ab984b8fd2 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -916,10 +916,10 @@ std::vector Print::support_material_extruders() const for (PrintObject *object : m_objects) { if (object->has_support_material()) { - // Under a support nozzle diameter restriction a "default" (0) support filament resolves to a matching-nozzle filament: one of the object's own or the deterministic fallback. - const unsigned int resolved_default = object->resolved_default_support_filament(); - auto add_support_filament = [&](int configured) { + // Under support restrictions (nozzle diameter, material) a "default" (0) support filament resolves to a passing filament: one of the object's own or the deterministic fallback. + auto add_support_filament = [&](int configured, bool interface_role) { assert(configured >= 0); + const unsigned int resolved_default = object->resolved_default_support_filament(interface_role); if (configured == 0 && resolved_default == 0) { support_uses_current_extruder = true; } else { @@ -927,8 +927,8 @@ std::vector Print::support_material_extruders() const extruders.emplace_back((i >= num_extruders) ? 0 : i); } }; - add_support_filament(object->config().support_filament); - add_support_filament(object->config().support_interface_filament); + add_support_filament(object->config().support_filament, false); + add_support_filament(object->config().support_interface_filament, true); } } @@ -1863,6 +1863,27 @@ StringObjectException Print::validate(StringObjectException *warning, Polygons* warned_support_mixed_nozzles = true; } + // ORCA: support material selections ("support_base_material" / "support_interface_material") + // exclude filaments of other types; a selection must leave at least one usable filament and + // agree with an explicitly selected support filament. + for (const bool interface_role : {false, true}) { + if ((interface_role ? object->config().support_interface_material : + object->config().support_base_material).value.empty()) + continue; + if (object->resolved_default_support_filament(interface_role) == 0) + return {interface_role ? + L("No loaded filament matches the support/raft interface material (and the support nozzle diameter).") : + L("No loaded filament matches the support/raft base material (and the support nozzle diameter)."), + object, interface_role ? "support_interface_material" : "support_base_material"}; + if (const int configured = (interface_role ? object->config().support_interface_filament : + object->config().support_filament).value; + configured > 0 && ! object->support_filament_allowed((unsigned int)configured, interface_role)) + return {interface_role ? + L("The support/raft interface filament is not of the support/raft interface material.") : + L("The support/raft base filament is not of the support/raft base material."), + object, interface_role ? "support_interface_filament" : "support_filament"}; + } + // Prusa: Fixing crashes with invalid tip diameter or branch diameter // https://github.com/prusa3d/PrusaSlicer/commit/96b3ae85013ac363cd1c3e98ec6b7938aeacf46d if (is_tree(object->config().support_type.value) && (object->config().support_style == smsTreeOrganic || diff --git a/src/libslic3r/Print.hpp b/src/libslic3r/Print.hpp index ae58b7a0180..dc7118b788b 100644 --- a/src/libslic3r/Print.hpp +++ b/src/libslic3r/Print.hpp @@ -526,13 +526,18 @@ class PrintObject : public PrintObjectBaseWithState 1? bool has_combined_layer_regions() const; - // Multi-nozzle support restriction ("support_nozzle_diameter" print option): may the given 1-based filament print this object's support / raft? - // Always true when the restriction is disabled or for the "default" filament 0. - bool support_filament_allowed(unsigned int filament_id) const; - // 1-based filament a "default" (0) support filament resolves to under the support nozzle - // diameter restriction when none of a layer's own filaments match: the first non-soluble - // matching-nozzle filament, else the first matching one; 0 when the restriction is disabled or nothing matches. - unsigned int resolved_default_support_filament() const; + // Multi-nozzle support restrictions ("support_nozzle_diameter" plus the "support_base_material" / + // "support_interface_material" print options): may the given 1-based filament print this object's + // support / raft base (interface_role false) or interface (interface_role true)? A filament passes + // when its nozzle matches the support nozzle diameter and its type matches the role's material; + // an unset restriction does not exclude. Always true for the "default" filament 0. + bool support_filament_allowed(unsigned int filament_id, bool interface_role = false) const; + // Any support filament restriction configured (nozzle diameter or either material)? + bool has_support_filament_restriction() const; + // 1-based filament a "default" (0) support filament of the role resolves to when none of a layer's + // own filaments pass the restrictions: the first non-soluble passing filament, else the first + // passing one; 0 when the role is unrestricted or nothing passes. + unsigned int resolved_default_support_filament(bool interface_role = false) const; // Called by make_perimeters() void slice(); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index f95a0d2e88e..eca1a2ea0c6 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -5492,6 +5492,32 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("support_base_material", coString); + def->label = L("Support/raft base material"); + def->category = L("Support"); + def->tooltip = L("Print the support and raft base only with filaments of this material type; " + "extruders loaded with other types are not used for it. Combines with the " + "support nozzle diameter restriction. Leave empty for no restriction; an " + "explicitly selected support/raft base filament still takes precedence."); + def->gui_type = ConfigOptionDef::GUIType::select_open; + def->mode = comSimple; + for (const char *material : { "PLA", "PETG", "ABS", "ASA", "TPU", "PC", "PA", "PVA", "HIPS" }) + def->enum_values.emplace_back(material); + def->set_default_value(new ConfigOptionString("")); + + def = this->add("support_interface_material", coString); + def->label = L("Support/raft interface material"); + def->category = L("Support"); + def->tooltip = L("Print the support and raft interface only with filaments of this material " + "type; extruders loaded with other types are not used for it. Combines with " + "the support nozzle diameter restriction. Leave empty for no restriction; an " + "explicitly selected support/raft interface filament still takes precedence."); + def->gui_type = ConfigOptionDef::GUIType::select_open; + def->mode = comSimple; + for (const char *material : { "PLA", "PETG", "ABS", "ASA", "TPU", "PC", "PA", "PVA", "HIPS" }) + def->enum_values.emplace_back(material); + def->set_default_value(new ConfigOptionString("")); + def = this->add("support_line_width", coFloatOrPercent); def->label = L("Support"); def->category = L("Quality"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 1cd59f77788..f2d0de46cc8 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -889,6 +889,8 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionInt, support_filament)) // ORCA: restrict support/raft/interface printing to filaments of this nozzle diameter (0 = no restriction). ((ConfigOptionFloat, support_nozzle_diameter)) + ((ConfigOptionString, support_base_material)) + ((ConfigOptionString, support_interface_material)) ((ConfigOptionFloatOrPercent, support_line_width)) ((ConfigOptionBool, support_interface_not_for_body)) ((ConfigOptionBool, support_interface_loop_pattern)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 236e2fcf407..4e47bbb02f4 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1064,6 +1064,8 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "enforce_support_layers" || opt_key == "support_filament" || opt_key == "support_nozzle_diameter" + || opt_key == "support_base_material" + || opt_key == "support_interface_material" || opt_key == "support_line_width" || opt_key == "support_interface_top_layers" || opt_key == "support_interface_bottom_layers" @@ -3821,24 +3823,37 @@ bool PrintObject::has_combined_layer_regions() const return false; } -bool PrintObject::support_filament_allowed(unsigned int filament_id) const +bool PrintObject::support_filament_allowed(unsigned int filament_id, bool interface_role) const { - const double restriction = m_config.support_nozzle_diameter.value; - if (restriction <= 0. || filament_id == 0) + if (filament_id == 0) return true; - return std::abs(m_print->config().nozzle_diameter.get_at(filament_id - 1) - restriction) < EPSILON; + if (const double nozzle = m_config.support_nozzle_diameter.value; nozzle > 0. && + std::abs(m_print->config().nozzle_diameter.get_at(filament_id - 1) - nozzle) > EPSILON) + return false; + const std::string &material = (interface_role ? m_config.support_interface_material : + m_config.support_base_material).value; + return material.empty() || m_print->config().filament_type.get_at(filament_id - 1) == material; +} + +bool PrintObject::has_support_filament_restriction() const +{ + return m_config.support_nozzle_diameter.value > 0. || + ! m_config.support_base_material.value.empty() || + ! m_config.support_interface_material.value.empty(); } -unsigned int PrintObject::resolved_default_support_filament() const +unsigned int PrintObject::resolved_default_support_filament(bool interface_role) const { - if (m_config.support_nozzle_diameter.value <= 0.) + if (m_config.support_nozzle_diameter.value <= 0. && + (interface_role ? m_config.support_interface_material : + m_config.support_base_material).value.empty()) return 0; const PrintConfig &print_config = m_print->config(); const size_t num_filaments = std::max(print_config.nozzle_diameter.values.size(), print_config.filament_diameter.values.size()); unsigned int soluble_fallback = 0; for (size_t i = 0; i < num_filaments; ++ i) - if (this->support_filament_allowed((unsigned int)(i + 1))) { + if (this->support_filament_allowed((unsigned int)(i + 1), interface_role)) { if (! print_config.filament_soluble.get_at(i)) return (unsigned int)(i + 1); if (soluble_fallback == 0) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 3240dcafcb3..cf7003aafea 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -9,7 +9,13 @@ #include "MsgDialog.hpp" #include "libslic3r/PrintConfig.hpp" +#include + #include +#include +#include +#include +#include namespace Slic3r { namespace GUI { @@ -29,6 +35,18 @@ void ConfigManipulation::apply(DynamicPrintConfig* config, DynamicPrintConfig* n bool ConfigManipulation::is_applying() const { return is_msg_dlg_already_exist; } +// ORCA: printers whose extruders have differing nozzle diameters. +bool ConfigManipulation::printer_has_mixed_nozzle_sizes() +{ + const auto *diameters = wxGetApp().preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); + if (diameters == nullptr || diameters->values.empty()) + return false; + for (double d : diameters->values) + if (std::abs(d - diameters->values.front()) > EPSILON) + return true; + return false; +} + t_config_option_keys const &ConfigManipulation::applying_keys() const { return m_applying_keys; @@ -742,13 +760,17 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("inner_wall_line_width", have_perimeters || have_skirt || have_brim); toggle_field("support_filament", have_support_material || have_skirt); - // ORCA: support_nozzle_diameter only applies to printers whose extruders have differing nozzle diameters. - bool mixed_nozzle_sizes = false; - if (const auto *nozzle_diameters = preset_bundle->printers.get_edited_preset().config.option("nozzle_diameter"); - nozzle_diameters != nullptr && ! nozzle_diameters->values.empty()) - for (double d : nozzle_diameters->values) - mixed_nozzle_sizes |= std::abs(d - nozzle_diameters->values.front()) > EPSILON; + // ORCA: support_nozzle_diameter only applies to printers whose extruders have differing + // nozzle diameters. There the support filament dialog owns the base/interface choice and the + // legacy selectors hide behind the "Show legacy filament selection" toggle, staying editable + // for older projects. + const bool mixed_nozzle_sizes = printer_has_mixed_nozzle_sizes(); toggle_line("support_nozzle_diameter", have_support_material && mixed_nozzle_sizes); + toggle_line("support_base_material", have_support_material && mixed_nozzle_sizes); + toggle_line("support_interface_material", have_support_material && mixed_nozzle_sizes); + const bool legacy_support_selectors = ! mixed_nozzle_sizes || wxGetApp().app_config->get_bool("show_legacy_support_filament"); + toggle_line("support_filament", legacy_support_selectors); + toggle_line("support_interface_filament", legacy_support_selectors); toggle_line("raft_contact_distance", have_raft && !have_support_soluble); @@ -984,6 +1006,87 @@ void ConfigManipulation::toggle_print_sla_options(DynamicPrintConfig* config) toggle_field("pad_object_connector_penetration", zero_elev); } +// ORCA: dialog raised when the user enables support on a printer with differing nozzle sizes: +// the nozzle size that prints the support, and the loaded filament types used for the raft/base +// and the interface. Writes support_nozzle_diameter and the two support material options, which +// exclude extruders of other types at slice time; the legacy selectors stay untouched. +int ConfigManipulation::show_support_filament_dialog(DynamicPrintConfig* config, DynamicPrintConfig* new_conf) +{ + PresetBundle &bundle = *wxGetApp().preset_bundle; + const auto *nozzle_opt = bundle.printers.get_edited_preset().config.option("nozzle_diameter"); + if (nozzle_opt == nullptr || nozzle_opt->values.empty()) + return wxID_CANCEL; + const std::vector &nozzles = nozzle_opt->values; + + // The distinct nozzle sizes and the loaded filaments' types, keeping extruder / slot order. + std::vector sizes; + for (double d : nozzles) + if (std::find_if(sizes.begin(), sizes.end(), [d](double s) { return std::abs(s - d) < EPSILON; }) == sizes.end()) + sizes.emplace_back(d); + std::vector types; + for (const std::string &name : bundle.filament_presets) { + const Preset *preset = bundle.filaments.find_preset(name); + const std::string type = preset != nullptr ? preset->config.opt_string("filament_type", 0u) : std::string(); + if (! type.empty() && std::find(types.begin(), types.end(), type) == types.end()) + types.emplace_back(type); + } + + wxDialog dlg(m_msg_dlg_parent, wxID_ANY, _(L("Support for mixed nozzle sizes"))); + auto *sizer = new wxBoxSizer(wxVERTICAL); + auto *intro = new wxStaticText(&dlg, wxID_ANY, + _(L("This printer uses different nozzle sizes. Select the nozzle size that prints the " + "support, and the filament types used for the raft and the support interface."))); + intro->Wrap(dlg.FromDIP(400)); + sizer->Add(intro, 0, wxALL, 10); + auto add_choice = [&dlg, sizer](const wxString &label, const wxArrayString &items, int selection) { + auto *row = new wxBoxSizer(wxHORIZONTAL); + row->Add(new wxStaticText(&dlg, wxID_ANY, label), 1, wxALIGN_CENTER_VERTICAL | wxRIGHT, 8); + auto *choice = new wxComboBox(&dlg, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, items, wxCB_READONLY); + choice->SetSelection(selection); + row->Add(choice, 1, wxALIGN_CENTER_VERTICAL); + sizer->Add(row, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 10); + return choice; + }; + + wxArrayString size_items; + int size_selection = 0; + for (size_t i = 0; i < sizes.size(); ++ i) { + size_items.Add(wxString::Format("%g mm", sizes[i])); + if (std::abs(sizes[i] - config->opt_float("support_nozzle_diameter")) < EPSILON) + size_selection = int(i); + } + wxArrayString type_items; + type_items.Add(_(L("Default"))); + for (const std::string &type : types) + type_items.Add(wxString::FromUTF8(type)); + // Preselect the currently configured materials. + auto type_selection = [&](const char *key) { + const std::string &material = config->opt_string(key); + for (size_t i = 0; i < types.size(); ++ i) + if (types[i] == material) + return int(i) + 1; + return 0; + }; + auto *size_choice = add_choice(_(L("Support nozzle size")), size_items, size_selection); + auto *base_choice = add_choice(_(L("Raft and support base")), type_items, type_selection("support_base_material")); + auto *interface_choice = add_choice(_(L("Support interface")), type_items, type_selection("support_interface_material")); + sizer->Add(dlg.CreateSeparatedButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 10); + dlg.SetSizerAndFit(sizer); + dlg.CentreOnScreen(); + const int answer = dlg.ShowModal(); + if (answer != wxID_OK) + return answer; + + const double size = sizes[std::max(0, size_choice->GetSelection())]; + auto material_of = [&types](int choice) { + return choice <= 0 ? std::string() : types[choice - 1]; + }; + new_conf->set_key_value("support_nozzle_diameter", new ConfigOptionFloat(size)); + new_conf->set_key_value("support_base_material", new ConfigOptionString(material_of(base_choice->GetSelection()))); + new_conf->set_key_value("support_interface_material", new ConfigOptionString(material_of(interface_choice->GetSelection()))); + return answer; +} + int ConfigManipulation::show_spiral_mode_settings_dialog(bool is_object_config) { wxString msg_text = _(L("Spiral mode only works when wall loops is 1, support is disabled, top shell layers is 0, sparse infill density is 0 and timelapse type is traditional.")); diff --git a/src/slic3r/GUI/ConfigManipulation.hpp b/src/slic3r/GUI/ConfigManipulation.hpp index fec8d08f4da..5e9549e0922 100644 --- a/src/slic3r/GUI/ConfigManipulation.hpp +++ b/src/slic3r/GUI/ConfigManipulation.hpp @@ -91,6 +91,9 @@ class ConfigManipulation m_support_material_overhangs_queried = queried; } int show_spiral_mode_settings_dialog(bool is_object_config = false); + // ORCA: support filament dialog for printers with differing nozzle sizes. + int show_support_filament_dialog(DynamicPrintConfig* config, DynamicPrintConfig* new_conf); + static bool printer_has_mixed_nozzle_sizes(); private: bool get_temperature_range(DynamicPrintConfig *config, int &range_low, int &range_high); diff --git a/src/slic3r/GUI/OptionsGroup.cpp b/src/slic3r/GUI/OptionsGroup.cpp index 8ef4751dd25..0d4edbb86e8 100644 --- a/src/slic3r/GUI/OptionsGroup.cpp +++ b/src/slic3r/GUI/OptionsGroup.cpp @@ -233,6 +233,10 @@ Line* OptionsGroup::get_line(const std::string& opt_key) { if(l.is_separator()) continue; + // ORCA: widget-only lines (e.g. the legacy support selection toggle) carry no options; + // get_first_option_key() would index an empty vector. + if (l.get_options().empty()) + continue; if (l.get_first_option_key() == opt_key) return &l; } diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 5635353cec3..8b5c1f7e887 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1504,6 +1504,16 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value) } } + // ORCA: support enabled by the user on a printer with differing nozzle sizes: ask which + // nozzle size prints the support and which materials serve as raft/base and interface. + // Only here, on a real edit - preset and project loading must not raise dialogs. + if (opt_key == "enable_support" && m_type == Preset::TYPE_PRINT && m_config->opt_bool("enable_support") && + ConfigManipulation::printer_has_mixed_nozzle_sizes()) { + DynamicPrintConfig new_conf = *m_config; + if (m_config_manipulation.show_support_filament_dialog(m_config, &new_conf) == wxID_OK) + m_config_manipulation.apply(m_config, &new_conf); + } + if (opt_key == "single_extruder_multi_material" || opt_key == "extruders_count" ) update_wiping_button_visibility(); @@ -2502,9 +2512,30 @@ void TabPrint::build() optgroup = page->new_optgroup(L("Support filament"), L"param_support_filament"); optgroup->append_single_option_line("support_nozzle_diameter", "support_settings_filament"); + optgroup->append_single_option_line("support_base_material", "support_settings_filament"); + optgroup->append_single_option_line("support_interface_material", "support_settings_filament"); optgroup->append_single_option_line("support_filament", "support_settings_filament#base"); optgroup->append_single_option_line("support_interface_filament", "support_settings_filament#interface"); optgroup->append_single_option_line("support_interface_not_for_body", "support_settings_filament#avoid-interface-filament-for-base"); + // ORCA: on mixed-nozzle printers the support filament dialog owns the base/interface + // choice; this toggle reveals the legacy selectors above for older projects. + auto legacy_support_toggle = [this](wxWindow* parent) { + auto *sizer = new wxBoxSizer(wxHORIZONTAL); + auto *check = new ::CheckBox(parent); + check->SetValue(wxGetApp().app_config->get_bool("show_legacy_support_filament")); + check->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &evt) { + wxGetApp().app_config->set_bool("show_legacy_support_filament", evt.IsChecked()); + update(); + evt.Skip(); + }); + sizer->Add(check, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); + sizer->Add(new wxStaticText(parent, wxID_ANY, _(L("Show legacy filament selection"))), 0, wxALIGN_CENTER_VERTICAL); + return sizer; + }; + Line legacy_support_line = Line{ "", "" }; + legacy_support_line.full_width = 1; + legacy_support_line.append_widget(legacy_support_toggle); + optgroup->append_line(legacy_support_line); optgroup = page->new_optgroup(L("Support ironing"), L"param_ironing"); optgroup->append_single_option_line("support_ironing", "support_settings_ironing"); diff --git a/tests/fff_print/test_multi_nozzle_layer_height.cpp b/tests/fff_print/test_multi_nozzle_layer_height.cpp index 580e41f34eb..2adde77b602 100644 --- a/tests/fff_print/test_multi_nozzle_layer_height.cpp +++ b/tests/fff_print/test_multi_nozzle_layer_height.cpp @@ -1338,6 +1338,75 @@ SCENARIO("Internal solid infill combines to its filament's pitch", "[MultiNozzle } } +SCENARIO("Support materials exclude filaments of other types", "[MultiNozzleLayerHeight]") { + GIVEN("PETG loaded twice, the support base material set to PETG") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PETG", "PETG", "PLA"})); + config.set_key_value("enable_support", new ConfigOptionBool(true)); + config.set_key_value("support_base_material", new ConfigOptionString("PETG")); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("only the PETG filaments may print the base and the selector stays on default") { + const PrintObject &object = *print.objects().front(); + CHECK(object.config().support_filament.value == 0); + CHECK(! object.support_filament_allowed(1, false)); + CHECK(object.support_filament_allowed(2, false)); + CHECK(object.support_filament_allowed(3, false)); + CHECK(! object.support_filament_allowed(4, false)); + CHECK(object.resolved_default_support_filament(false) == 2); + } + THEN("the interface without a material stays unrestricted") { + const PrintObject &object = *print.objects().front(); + CHECK(object.support_filament_allowed(1, true)); + CHECK(object.support_filament_allowed(4, true)); + CHECK(object.resolved_default_support_filament(true) == 0); + } + } + GIVEN("the support nozzle size and the interface material combined") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PETG", "PETG", "PLA"})); + config.set_key_value("enable_support", new ConfigOptionBool(true)); + config.set_key_value("support_nozzle_diameter", new ConfigOptionFloat(0.6)); + config.set_key_value("support_interface_material", new ConfigOptionString("PETG")); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("only the PETG filament on the 0.6 mm nozzle may print the interface") { + const PrintObject &object = *print.objects().front(); + CHECK(! object.support_filament_allowed(2, true)); + CHECK(object.support_filament_allowed(3, true)); + CHECK(object.resolved_default_support_filament(true) == 3); + CHECK(print.validate().string.empty()); + } + } + GIVEN("a material no loaded filament matches") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PETG", "PETG", "PLA"})); + config.set_key_value("enable_support", new ConfigOptionBool(true)); + config.set_key_value("support_base_material", new ConfigOptionString("TPU")); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("validation rejects the setup") { + CHECK(print.validate().string.find("base material") != std::string::npos); + } + } + GIVEN("an explicit base filament of another type") { + DynamicPrintConfig config = four_nozzle_config(); + config.set_key_value("filament_type", new ConfigOptionStrings({"PLA", "PETG", "PETG", "PLA"})); + config.set_key_value("enable_support", new ConfigOptionBool(true)); + config.set_key_value("support_base_material", new ConfigOptionString("PETG")); + config.set_key_value("support_filament", new ConfigOptionInt(1)); + Print print; + Model model; + init_cube_print(print, model, config); + THEN("validation flags the conflict") { + CHECK(print.validate().string.find("base filament") != std::string::npos); + } + } +} + SCENARIO("A preference-less fine-nozzle wall filament vetoes the walls-only pitch", "[MultiNozzleLayerHeight]") { GIVEN("Outer walls on the 0.8 mm nozzle preferring 0.48 mm, inner walls on a 0.2 mm nozzle with no preference") { DynamicPrintConfig config = four_nozzle_config(); From 1552ce89b5ca1ed75c3eb46fa7a93f7bb795ac69 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:44:22 +0000 Subject: [PATCH 18/43] Translate the multi-nozzle feature strings Add translations to every shipped language for the UI strings the multi-nozzle work introduced: the per-extruder layer height options and their warnings and validation messages, the split-wall options, the support nozzle diameter restriction, the support material options, the mixed-nozzle support dialog and the legacy filament selection toggle. Translated with AI so there might be incorrect translation. --- localization/i18n/ca/Snapmaker_Orca_ca.po | 218 +++++++++++++++++- localization/i18n/cs/Snapmaker_Orca_cs.po | 218 +++++++++++++++++- localization/i18n/de/Snapmaker_Orca_de.po | 218 +++++++++++++++++- localization/i18n/en/Snapmaker_Orca_en.po | 217 +++++++++++++++++ localization/i18n/es/Snapmaker_Orca_es.po | 218 +++++++++++++++++- localization/i18n/fr/Snapmaker_Orca_fr.po | 218 +++++++++++++++++- localization/i18n/hu/Snapmaker_Orca_hu.po | 218 +++++++++++++++++- localization/i18n/it/Snapmaker_Orca_it.po | 218 +++++++++++++++++- localization/i18n/ja/Snapmaker_Orca_ja.po | 218 +++++++++++++++++- localization/i18n/ko/Snapmaker_Orca_ko.po | 218 +++++++++++++++++- localization/i18n/lt/Snapmaker_Orca_lt.po | 218 +++++++++++++++++- localization/i18n/nl/Snapmaker_Orca_nl.po | 218 +++++++++++++++++- localization/i18n/pl/Snapmaker_Orca_pl.po | 218 +++++++++++++++++- .../i18n/pt_BR/Snapmaker_Orca_pt_BR.po | 218 +++++++++++++++++- localization/i18n/ru/Snapmaker_Orca_ru.po | 218 +++++++++++++++++- localization/i18n/sv/Snapmaker_Orca_sv.po | 218 +++++++++++++++++- localization/i18n/tr/Snapmaker_Orca_tr.po | 218 +++++++++++++++++- localization/i18n/uk/Snapmaker_Orca_uk.po | 218 +++++++++++++++++- .../i18n/zh_CN/Snapmaker_Orca_zh_CN.po | 218 +++++++++++++++++- .../i18n/zh_TW/Snapmaker_Orca_zh_TW.po | 218 +++++++++++++++++- 20 files changed, 4340 insertions(+), 19 deletions(-) diff --git a/localization/i18n/ca/Snapmaker_Orca_ca.po b/localization/i18n/ca/Snapmaker_Orca_ca.po index 0737b5c756a..d118529bfb1 100644 --- a/localization/i18n/ca/Snapmaker_Orca_ca.po +++ b/localization/i18n/ca/Snapmaker_Orca_ca.po @@ -20181,4 +20181,220 @@ msgstr "" #~ "s'especifiquen explícitament." #~ msgid "End end: " -#~ msgstr "Final: " \ No newline at end of file +#~ msgstr "Final: " +msgid "Adjust wall layer height" +msgstr "Ajustar l'alçada de capa dels perímetres" + +msgid "Adjusted walls" +msgstr "Perímetres ajustats" + +msgid "Adjustment direction" +msgstr "Direcció de l'ajust" + +msgid "Consistent" +msgstr "Consistent" + +msgid "Decrease" +msgstr "Disminuir" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament per imprimir la superfície inferior.\n\"Per defecte\" usa el filament actiu de l'objecte/peça." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament per imprimir els perímetres interiors.\n\"Per defecte\" usa el filament actiu de l'objecte/peça." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament per imprimir el farciment sòlid intern.\n\"Per defecte\" usa el filament actiu de l'objecte/peça." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament per imprimir el farciment poc dens intern.\n\"Per defecte\" usa el filament actiu de l'objecte/peça." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament per imprimir els perímetres exteriors.\n\"Per defecte\" usa el filament actiu de l'objecte/peça." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament per imprimir base del suport i Vora d'Adherència. \"Per defecte\" significa que no s'utilitza cap filament específic per al suport i s'usarà el filament actual" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament per imprimir interfície de suport. \"Per defecte\" vol dir que no hi ha filament específic per a la interfície de suport i s'utilitza el filament actual" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament per imprimir la superfície superior.\n\"Per defecte\" usa el filament actiu de l'objecte/peça." + +msgid "Fixed" +msgstr "Fixa" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Amb quina agressivitat les peces assignades a un extrusor amb una alçada de capa preferida més gruixuda es combinen en capes gruixudes.\nConsistent: les peces s'imprimeixen amb com a màxim dues alçades de capa - l'alçada de capa de l'extrusor on hi caben sèries senceres de capes i l'alçada de capa de l'objecte a la resta. Això dóna els perímetres més uniformes.\nAdaptativa: les sèries també es poden combinar en múltiples intermedis de l'alçada de capa de l'objecte, de manera que més part de la peça s'imprimeix amb capes gruixudes, a canvi de bandes d'alçades de capa variables als contorns corbats.\nFixa: les peces sempre s'imprimeixen a l'alçada de capa de l'extrusor, fins i tot on la forma canvia a través de les capes combinades o sobresurt; els contorns corbats es converteixen en esglaons i es perd el detall més fi que les capes gruixudes. Només la geometria massa curta per a una capa gruixuda sencera (les parts superiors i la primera capa) s'imprimeix més fina." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Fins on pot desplaçar-se lateralment el contorn d'una peça assignada a un extrusor amb una alçada de capa preferida més gruixuda a través de les capes d'una sèrie gruixuda i encara combinar-se, com a percentatge del diàmetre del broquet d'aquell extrusor. Valors més alts combinen més contorns corbats en capes gruixudes, a canvi de perímetres de contorn més rugosos: les desviacions fins a aquesta fracció del diàmetre del broquet queden absorbides per les extrusions gruixudes." + +msgid "Increase" +msgstr "Augmentar" + +msgid "Inner walls" +msgstr "Perímetres interiors" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Alçada de capa amb què hauria d'imprimir aquest extrusor, per a impressores amb extrusors de mides de broquet diferents. Ha de ser un múltiple enter de l'alçada de capa de l'objecte. Una peça amb totes les característiques seguint aquest extrusor s'imprimeix només cada N capes amb extrusions corresponentment més gruixudes, allà on la seva geometria ho permet; a la resta torna a l'alçada de capa de l'objecte. Quan la resta de la peça no pot seguir-lo, els perímetres assignats a aquest extrusor es combinen igualment pel seu compte a aquesta alçada, les superfícies superiors totalment denses absorbeixen les capes sòlides de sota, i el farciment poc dens o dens al 100% es combina de manera independent a aquesta alçada. 0 significa usar l'alçada de capa de l'objecte." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Alçada de capa amb què hauria d'imprimir aquest extrusor: un múltiple enter de l'alçada de capa de l'objecte dins dels límits d'alçada de capa d'aquest extrusor. Per defecte manté l'alçada de capa de l'objecte." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Cap extrusor té un broquet que coincideixi amb el diàmetre del broquet dels suports." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Cap filament carregat coincideix amb el material de la base dels suports/vora d'adherència (i amb el diàmetre del broquet dels suports)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Cap filament carregat coincideix amb el material de la interfície dels suports/vora d'adherència (i amb el diàmetre del broquet dels suports)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Broquet %1%: límits d'alçada de capa fixats a %2%-%3% mm, de \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "En impressores amb extrusors de diàmetres de broquet diferents, només els filaments d'aquest diàmetre de broquet s'usen per imprimir suports, vora d'adherència i interfície dels suports. Això manté els filaments d'altres mides de broquet - amb les seves amplades de línia i límits d'alçada de capa diferents - fora dels suports. Els filaments de suport amb un valor no predeterminat han de coincidir amb aquest diàmetre. El valor 0 permet que qualsevol filament imprimeixi suports." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Els perímetres exteriors i interiors s'imprimeixen automàticament amb les seves pròpies alçades de capa preferides quan una alçada és un múltiple enter de l'altra. Quan les alçades no es divideixen exactament, aquesta opció ajusta l'alçada de capa dels perímetres d'un dels dos filaments de perímetre (triat a sota) al múltiple o divisor més proper de l'altra, perquè els perímetres encara puguin separar-se. L'alçada ajustada només s'aplica als perímetres d'aquell filament; les altres característiques mantenen l'alçada de capa preferida. Els ajustos mai surten dels límits d'alçada de capa del filament: si no hi ha cap alçada permesa en la direcció triada, els perímetres s'imprimeixen junts a l'alçada menor com sempre." + +msgid "Outer walls" +msgstr "Perímetres exteriors" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Les alçades de capa per extrusor no són compatibles amb el mode gerro en espiral." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Les alçades de capa per extrusor no són compatibles amb les carcasses d'interfície." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Les alçades de capa per extrusor no són compatibles amb l'alçada de capa variable." + +msgid "Preferred layer height" +msgstr "Alçada de capa preferida" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Imprimir la base dels suports i la vora d'adherència només amb filaments d'aquest tipus de material; els extrusors carregats amb altres tipus no s'hi usen. Es combina amb la restricció del diàmetre del broquet dels suports. Deixeu-ho buit per no restringir; un filament de base dels suports/vora d'adherència triat explícitament segueix tenint prioritat." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Imprimir la interfície dels suports i de la vora d'adherència només amb filaments d'aquest tipus de material; els extrusors carregats amb altres tipus no s'hi usen. Es combina amb la restricció del diàmetre del broquet dels suports. Deixeu-ho buit per no restringir; un filament d'interfície triat explícitament segueix tenint prioritat." + +msgid "Raft and support base" +msgstr "Vora d'adherència i base dels suports" + +msgid "Show legacy filament selection" +msgstr "Mostra la selecció de filament antiga" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Algunes peces prefereixen capes de %1% mm, però el broquet del filament %2% que imprimeix altres característiques de la peça és massa petit per extrudir aquesta alçada. Aquestes peces s'imprimeixen amb l'alçada de capa de l'objecte." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Algunes peces prefereixen capes de %1% mm, però el broquet del filament de perímetres %2% és massa petit per extrudir aquesta alçada. Els perímetres exteriors i interiors s'imprimeixen junts, així que aquests perímetres mantenen l'alçada de capa de l'objecte. Assigneu ambdues característiques de perímetre a filaments amb broquets prou grans." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Algunes peces imprimeixen capes de %1% mm amb el filament %2%, l'alçada de capa màxima del qual és %3% mm. Assigneu les característiques de la peça als filaments del broquet més gruixut, augmenteu l'alçada de capa màxima del filament o accepteu imprimir per sobre." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Algunes peces imprimeixen capes de %1% mm amb el filament %2%, l'alçada de capa mínima del qual és %3% mm. Augmenteu l'alçada de capa de l'objecte, useu un filament amb un broquet més fi per a aquestes característiques o accepteu imprimir per sota del mínim de l'extrusor." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Algunes peces usen filaments les alçades de capa preferides dels quals no es poden respectar totes: una peça imprimeix les seves característiques amb un sol pas de capa (fixat pels seus filaments de perímetre o, quan cap filament de perímetre té preferència, per l'acord de les altres característiques); els perímetres, les superfícies superiors i el farciment poden combinar-se cadascun a la seva pròpia alçada quan la resta de la peça no els pot seguir, però les característiques restants s'imprimeixen amb el pas de la peça." + +msgid "Support for mixed nozzle sizes" +msgstr "Suports amb mides de broquet mixtes" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Els suports podrien imprimir-se amb extrusors de diàmetres de broquet diferents. Fixeu el diàmetre del broquet dels suports (o filaments explícits de suport i interfície) per mantenir els suports en una sola mida de broquet." + +msgid "Support nozzle diameter" +msgstr "Diàmetre del broquet dels suports" + +msgid "Support nozzle size" +msgstr "Mida del broquet dels suports" + +msgid "Support/raft base material" +msgstr "Material de la base dels suports/vora d'adherència" + +msgid "Support/raft interface material" +msgstr "Material de la interfície dels suports/vora d'adherència" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "L'amplada de línia de farciment de %1% mm és massa petita per a farciment combinat en capes de %2% mm d'alçada. Augmenteu l'amplada de línia o reduïu l'alçada de capa preferida del filament de farciment." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "L'amplada de línia de %1% mm és massa petita per a l'alçada de capa de %2% mm del seu extrusor. Augmenteu l'amplada de línia o reduïu l'alçada de capa de l'extrusor." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "L'alçada de capa de l'extrusor %1% (%2% mm) no pot superar el diàmetre del seu broquet." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "L'alçada de capa de l'extrusor %1% (%2% mm) s'ignora per a algunes peces: ha de ser un múltiple enter de l'alçada de capa de l'objecte (%3% mm), no inferior, i no ha de superar el diàmetre del broquet de l'extrusor." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "L'alçada de capa de l'extrusor %1% (%2% mm) és menor que l'alçada de capa de l'objecte (%3% mm). Reduïu l'alçada de capa de l'objecte a l'alçada de capa d'extrusor més fina." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "L'alçada de capa de l'extrusor %1% (%2% mm) ha de ser un múltiple enter de l'alçada de capa de l'objecte (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "L'alçada de capa imprimible més baixa de l'extrusor. S'usa per limitar l'alçada de capa mínima quan l'alçada de capa adaptativa està activada. Les peces impreses amb una alçada de capa d'extrusor preferida més gruixuda tampoc no baixen mai d'aquesta alçada (excepte la primera capa)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "L'alçada de capa preferida del broquet %1% ja no hi passa i s'ha restablert a Per defecte." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "El filament de la base dels suports/vora d'adherència no és del material de la base dels suports/vora d'adherència." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "El filament de la base dels suports/vora d'adherència imprimeix amb un broquet que no coincideix amb el diàmetre del broquet dels suports." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "El filament de la interfície dels suports/vora d'adherència no és del material de la interfície dels suports/vora d'adherència." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "El filament de la interfície dels suports/vora d'adherència imprimeix amb un broquet que no coincideix amb el diàmetre del broquet dels suports." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Els filaments de perímetre d'algunes peces prefereixen alçades de capa diferents. Els perímetres exteriors i interiors s'imprimeixen junts, així que aquests perímetres mantenen l'alçada de capa de l'objecte. Assigneu ambdues característiques de perímetre a filaments que prefereixin la mateixa alçada per imprimir perímetres més gruixuts." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Els filaments de perímetre d'algunes peces prefereixen alçades de capa diferents. Els perímetres exteriors i interiors s'imprimeixen junts, així que aquests perímetres s'imprimeixen amb l'alçada menor (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "L'alçada de capa dels perímetres del filament %1% s'ha ajustat dels %2% mm preferits a %3% mm perquè els perímetres exteriors i interiors puguin imprimir-se a alçades de capa compatibles (\"Ajustar l'alçada de capa dels perímetres\"). Només els perímetres d'aquest filament imprimeixen l'alçada ajustada; les seves altres característiques mantenen la preferida." + +msgid "Thick layer regions" +msgstr "Regions de capes gruixudes" + +msgid "Thick layer tolerance" +msgstr "Tolerància de capes gruixudes" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Aquesta impressora no té perfil per a un broquet de %1% mm. Reviseu els límits d'alçada de capa del broquet %2% als ajustos de la impressora." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Aquesta impressora usa mides de broquet diferents. Seleccioneu la mida del broquet que imprimeix els suports i els tipus de filament per a la vora d'adherència i la interfície dels suports." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Si l'alçada de capa dels perímetres del filament ajustat es disminueix o s'augmenta per assolir una alçada compatible amb l'altre filament de perímetre. Mai s'usen alçades fora dels límits d'alçada de capa del filament ajustat: si no hi ha cap alçada permesa en aquesta direcció, els perímetres s'imprimeixen junts a l'alçada menor com sempre." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Quin dels dos filaments de perímetre rep l'ajust de la seva alçada de capa dels perímetres quan les alçades de capa preferides no es divideixen exactament." diff --git a/localization/i18n/cs/Snapmaker_Orca_cs.po b/localization/i18n/cs/Snapmaker_Orca_cs.po index d633f3367bc..c4ae1a0976e 100644 --- a/localization/i18n/cs/Snapmaker_Orca_cs.po +++ b/localization/i18n/cs/Snapmaker_Orca_cs.po @@ -18969,4 +18969,220 @@ msgstr "" #~ msgstr "" #~ "Zadejte prosím platné hodnoty:\n" #~ "start > 10 kroků >= 0\n" -#~ "konec > začátek + krok)" \ No newline at end of file +#~ "konec > začátek + krok)" +msgid "Adjust wall layer height" +msgstr "Upravit výšku vrstvy stěn" + +msgid "Adjusted walls" +msgstr "Upravované stěny" + +msgid "Adjustment direction" +msgstr "Směr úpravy" + +msgid "Consistent" +msgstr "Jednotné" + +msgid "Decrease" +msgstr "Snížit" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament pro tisk spodního povrchu.\n\"Výchozí\" použije aktivní filament objektu/části." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament pro tisk vnitřních stěn.\n\"Výchozí\" použije aktivní filament objektu/části." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament pro tisk vnitřní plné výplně.\n\"Výchozí\" použije aktivní filament objektu/části." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament pro tisk vnitřní řídké výplně.\n\"Výchozí\" použije aktivní filament objektu/části." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament pro tisk vnějších stěn.\n\"Výchozí\" použije aktivní filament objektu/části." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament pro tisk základny podpor a raftu.\n\"Výchozí\" znamená žádný zvláštní filament pro podpory; použije se aktuální filament." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament pro tisk kontaktních vrstev podpor.\n\"Výchozí\" znamená žádný zvláštní filament pro kontaktní vrstvy; použije se aktuální filament." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament pro tisk horního povrchu.\n\"Výchozí\" použije aktivní filament objektu/části." + +msgid "Fixed" +msgstr "Pevné" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Jak agresivně se části přiřazené extruderu s tlustší preferovanou výškou vrstvy slučují do tlustých vrstev.\nJednotné: části se tisknou nejvýše dvěma výškami vrstvy - výškou vrstvy extruderu tam, kde se vejdou celé série vrstev, a výškou vrstvy objektu všude jinde. To dává nejrovnoměrnější stěny.\nAdaptivní: série lze slučovat i na mezilehlých násobcích výšky vrstvy objektu, takže se větší část dílu tiskne tlustšími vrstvami - za cenu pásů proměnlivé výšky vrstvy na zakřivených okrajích.\nPevné: části se vždy tisknou výškou vrstvy extruderu, i tam, kde se tvar přes sloučené vrstvy mění nebo přesahuje; zakřivené okraje se mění ve schody a detaily jemnější než tlusté vrstvy se ztrácejí. Jen geometrie příliš krátká na celou tlustou vrstvu (vršky dílů a první vrstva) se tiskne tenčeji." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Jak daleko se smí obrys části přiřazené extruderu s tlustší preferovanou výškou vrstvy posunout do strany napříč vrstvami jedné tlusté série a přesto být sloučen, jako procento průměru trysky tohoto extruderu. Vyšší hodnoty sloučí více zakřivených okrajů do tlustých vrstev za cenu hrubších okrajových stěn: odchylky do tohoto zlomku průměru trysky pohltí tlusté extruze." + +msgid "Increase" +msgstr "Zvýšit" + +msgid "Inner walls" +msgstr "Vnitřní stěny" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Výška vrstvy, kterou má tento extruder tisknout; pro tiskárny, jejichž extrudery mají různé velikosti trysek. Musí být celočíselným násobkem výšky vrstvy objektu. Díl, jehož všechny prvky následují tento extruder, se tiskne jen každou N-tou vrstvu odpovídajícím způsobem tlustšími extruzemi tam, kde to jeho geometrie dovolí; jinde se vrací k výšce vrstvy objektu. Když zbytek dílu následovat nemůže, stěny přiřazené tomuto extruderu se přesto samostatně sloučí na tuto výšku, plně husté horní povrchy pohltí plné vrstvy pod sebou a řídká i 100% hustá výplň se na tuto výšku sloučí nezávisle. 0 znamená použít výšku vrstvy objektu." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Výška vrstvy, kterou má tento extruder tisknout: celočíselný násobek výšky vrstvy objektu v mezích omezení výšky vrstvy tohoto extruderu. Výchozí zachovává výšku vrstvy objektu." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Žádný extruder nemá trysku odpovídající průměru trysky podpor." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Žádný nahraný filament neodpovídá materiálu základny podpor/raftu (a průměru trysky podpor)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Žádný nahraný filament neodpovídá materiálu kontaktních vrstev podpor/raftu (a průměru trysky podpor)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Tryska %1%: omezení výšky vrstvy nastavena na %2%-%3% mm, z \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Na tiskárnách, jejichž extrudery mají různé průměry trysek, se k tisku podpor, raftu a kontaktních vrstev používají jen filamenty tohoto průměru trysky. To drží filamenty jiných velikostí trysek - s jejich odlišnými šířkami čar a omezeními výšky vrstvy - mimo podpory. Filamenty podpor nastavené na jinou než výchozí hodnotu musí tomuto průměru odpovídat. Hodnota 0 dovolí tisknout podpory libovolným filamentem." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Vnější a vnitřní stěny se automaticky tisknou vlastními preferovanými výškami vrstvy, když je jedna výška celočíselným násobkem druhé. Když se výšky nedělí beze zbytku, tato volba upraví výšku vrstvy stěn jednoho ze dvou filamentů stěn (zvoleného níže) na nejbližší násobek nebo dělitel druhé, aby se stěny stále mohly rozdělit. Upravená výška platí jen pro stěny tohoto filamentu; ostatní prvky si zachovají preferovanou výšku vrstvy. Úpravy nikdy neopustí omezení výšky vrstvy filamentu: pokud ve zvoleném směru žádná povolená výška neexistuje, stěny se jako obvykle tisknou společně nižší výškou." + +msgid "Outer walls" +msgstr "Vnější stěny" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Výšky vrstev pro jednotlivé extrudery nejsou podporovány v režimu spirálové vázy." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Výšky vrstev pro jednotlivé extrudery nejsou podporovány spolu s kontaktními skořepinami." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Výšky vrstev pro jednotlivé extrudery nejsou podporovány spolu s proměnlivou výškou vrstvy." + +msgid "Preferred layer height" +msgstr "Preferovaná výška vrstvy" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Tisknout podpory a základnu raftu jen filamenty tohoto typu materiálu; extrudery s jinými typy se k tomu nepoužívají. Působí spolu s omezením průměru trysky podpor. Ponechte prázdné pro žádné omezení; výslovně zvolený filament základny podpor/raftu má stále přednost." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Tisknout kontaktní vrstvy podpor a raftu jen filamenty tohoto typu materiálu; extrudery s jinými typy se k tomu nepoužívají. Působí spolu s omezením průměru trysky podpor. Ponechte prázdné pro žádné omezení; výslovně zvolený filament kontaktních vrstev má stále přednost." + +msgid "Raft and support base" +msgstr "Raft a základna podpor" + +msgid "Show legacy filament selection" +msgstr "Zobrazit starý výběr filamentu" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Některé díly preferují vrstvy %1% mm, ale tryska filamentu %2%, který tiskne jiné prvky dílu, je příliš malá na vytlačení této výšky. Tyto díly se místo toho tisknou výškou vrstvy objektu." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Některé díly preferují vrstvy %1% mm, ale tryska filamentu stěn %2% je příliš malá na vytlačení této výšky. Vnější a vnitřní stěny se tisknou společně, takže tyto stěny si zachovají výšku vrstvy objektu. Přiřaďte oba prvky stěn filamentům s dostatečně velkými tryskami." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Některé díly tisknou vrstvy %1% mm filamentem %2%, jehož maximální výška vrstvy je %3% mm. Přiřaďte prvky dílu filamentům hrubší trysky, zvyšte maximální výšku vrstvy filamentu, nebo přijměte tisk nad ní." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Některé díly tisknou vrstvy %1% mm filamentem %2%, jehož minimální výška vrstvy je %3% mm. Zvyšte výšku vrstvy objektu, použijte pro tyto prvky filament s jemnější tryskou, nebo přijměte tisk pod minimem extruderu." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Některé díly používají filamenty, jejichž preferované výšky vrstev nelze všechny dodržet: díl tiskne své prvky jedním krokem vrstvy (určeným jeho filamenty stěn, nebo dohodou ostatních prvků, když žádný filament stěn preferenci nemá); stěny, horní povrchy a výplň se mohou každý sloučit na vlastní výšku, když je zbytek dílu nemůže následovat, ale zbývající prvky se tisknou krokem dílu." + +msgid "Support for mixed nozzle sizes" +msgstr "Podpory při smíšených velikostech trysek" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Podpory se mohou tisknout extrudery s různými průměry trysek. Nastavte průměr trysky podpor (nebo výslovné filamenty podpor a kontaktních vrstev), aby podpory zůstaly na jedné velikosti trysky." + +msgid "Support nozzle diameter" +msgstr "Průměr trysky podpor" + +msgid "Support nozzle size" +msgstr "Velikost trysky podpor" + +msgid "Support/raft base material" +msgstr "Materiál základny podpor/raftu" + +msgid "Support/raft interface material" +msgstr "Materiál kontaktních vrstev podpor/raftu" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "Šířka čáry výplně %1% mm je příliš malá pro výplň sloučenou do vrstev vysokých %2% mm. Zvětšete šířku čáry, nebo snižte preferovanou výšku vrstvy filamentu výplně." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "Šířka čáry %1% mm je příliš malá pro výšku vrstvy %2% mm jejího extruderu. Zvětšete šířku čáry, nebo snižte výšku vrstvy extruderu." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Výška vrstvy extruderu %1% (%2% mm) nesmí překročit průměr jeho trysky." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Výška vrstvy extruderu %1% (%2% mm) se u některých dílů ignoruje: musí být celočíselným násobkem výšky vrstvy objektu (%3% mm), ne menší, a nesmí překročit průměr trysky extruderu." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Výška vrstvy extruderu %1% (%2% mm) je menší než výška vrstvy objektu (%3% mm). Snižte výšku vrstvy objektu na nejjemnější výšku vrstvy extruderu." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Výška vrstvy extruderu %1% (%2% mm) musí být celočíselným násobkem výšky vrstvy objektu (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Nejnižší tisknutelná výška vrstvy extruderu. Omezuje minimální výšku vrstvy při zapnuté adaptivní výšce vrstvy. Díly tištěné tlustší preferovanou výškou vrstvy extruderu také nikdy neklesnou pod tuto výšku (kromě první vrstvy)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Preferovaná výška vrstvy trysky %1% jí už neprojde a byla obnovena na Výchozí." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Filament základny podpor/raftu není z materiálu základny podpor/raftu." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Filament základny podpor/raftu tiskne tryskou, která neodpovídá průměru trysky podpor." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Filament kontaktních vrstev podpor/raftu není z materiálu kontaktních vrstev podpor/raftu." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Filament kontaktních vrstev podpor/raftu tiskne tryskou, která neodpovídá průměru trysky podpor." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Filamenty stěn některých dílů preferují různé výšky vrstev. Vnější a vnitřní stěny se tisknou společně, takže tyto stěny si zachovají výšku vrstvy objektu. Pro tisk tlustších stěn přiřaďte oba prvky stěn filamentům preferujícím stejnou výšku." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Filamenty stěn některých dílů preferují různé výšky vrstev. Vnější a vnitřní stěny se tisknou společně, takže se tyto stěny tisknou nižší výškou (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Výška vrstvy stěn filamentu %1% byla upravena z preferovaných %2% mm na %3% mm, aby se vnější a vnitřní stěny mohly tisknout kompatibilními výškami vrstev (\"Upravit výšku vrstvy stěn\"). Upravenou výškou se tisknou jen stěny tohoto filamentu; jeho ostatní prvky si zachovají preferovanou." + +msgid "Thick layer regions" +msgstr "Oblasti tlustých vrstev" + +msgid "Thick layer tolerance" +msgstr "Tolerance tlustých vrstev" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Tato tiskárna nemá profil pro trysku %1% mm. Zkontrolujte prosím omezení výšky vrstvy trysky %2% v nastavení tiskárny." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Tato tiskárna používá různé velikosti trysek. Vyberte velikost trysky, která tiskne podpory, a typy filamentů použité pro raft a kontaktní vrstvy podpor." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Zda se výška vrstvy stěn upravovaného filamentu snižuje, nebo zvyšuje, aby dosáhla výšky kompatibilní s druhým filamentem stěn. Výšky mimo omezení výšky vrstvy upravovaného filamentu se nikdy nepoužijí: pokud v tomto směru žádná povolená výška neexistuje, stěny se jako obvykle tisknou společně nižší výškou." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Kterému ze dvou filamentů stěn se upraví výška vrstvy stěn, když se preferované výšky vrstev nedělí beze zbytku." diff --git a/localization/i18n/de/Snapmaker_Orca_de.po b/localization/i18n/de/Snapmaker_Orca_de.po index 2e8de07b287..86ec6d448a7 100644 --- a/localization/i18n/de/Snapmaker_Orca_de.po +++ b/localization/i18n/de/Snapmaker_Orca_de.po @@ -21705,4 +21705,220 @@ msgstr "" #~ "Bei Baumstützen führt der schlanke Stil zu einer aggressiveren " #~ "Zusammenführung der Äste und spart viel Material (Standard), während der " #~ "Hybridmodus bei großen überhängenden Flächen eine ähnliche Struktur wie " -#~ "bei normalen Stützstrukturen erzeugt." \ No newline at end of file +#~ "bei normalen Stützstrukturen erzeugt." +msgid "Adjust wall layer height" +msgstr "Wand-Schichthöhe anpassen" + +msgid "Adjusted walls" +msgstr "Angepasste Wände" + +msgid "Adjustment direction" +msgstr "Anpassungsrichtung" + +msgid "Consistent" +msgstr "Konsistent" + +msgid "Decrease" +msgstr "Verringern" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament zum Drucken der unteren Oberfläche.\n\"Standard\" verwendet das Filament des aktiven Objekts/Teils." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament zum Drucken der inneren Wände.\n\"Standard\" verwendet das Filament des aktiven Objekts/Teils." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament zum Drucken der internen massiven Füllung.\n\"Standard\" verwendet das Filament des aktiven Objekts/Teils." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament zum Drucken der internen spärlichen Füllung.\n\"Standard\" verwendet das Filament des aktiven Objekts/Teils." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament zum Drucken der äußeren Wände.\n\"Standard\" verwendet das Filament des aktiven Objekts/Teils." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament zum Drucken der Stützstruktur-Basis und des Rafts.\n\"Standard\" bedeutet kein spezifisches Filament für die Stützstruktur und das aktuelle Filament wird verwendet." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament zum Drucken der Stützstruktur-Schnittstelle.\n\"Standard\" bedeutet kein spezifisches Filament für die Stützstruktur-Schnittstelle und das aktuelle Filament wird verwendet." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament zum Drucken der oberen Oberfläche.\n\"Standard\" verwendet das Filament des aktiven Objekts/Teils." + +msgid "Fixed" +msgstr "Fest" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Wie aggressiv Objektteile, die einem Extruder mit dickerer bevorzugter Schichthöhe zugewiesen sind, zu dicken Schichten zusammengefasst werden.\nKonsistent: Teile drucken mit höchstens zwei Schichthöhen - der Extruder-Schichthöhe, wo ganze Schichtfolgen hineinpassen, und der Objekt-Schichthöhe überall sonst. Das ergibt die gleichmäßigsten Wände.\nAdaptiv: Schichtfolgen dürfen auch bei Zwischenvielfachen der Objekt-Schichthöhe zusammengefasst werden, sodass mehr des Teils mit dickeren Schichten druckt - um den Preis von Bändern wechselnder Schichthöhen an gekrümmten Teilgrenzen.\nFest: Teile drucken immer mit der Extruder-Schichthöhe, auch wo sich die Form über die zusammengefassten Schichten ändert oder überhängt; gekrümmte Grenzen werden zu Stufen, und Details feiner als die dicken Schichten gehen verloren. Nur Geometrie, die für eine ganze dicke Schicht zu kurz ist (Teiloberseiten und die erste Schicht), druckt dünner." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Wie weit die Kontur eines Objektteils, das einem Extruder mit dickerer bevorzugter Schichthöhe zugewiesen ist, über die Schichten einer dicken Folge seitlich wandern darf und trotzdem zusammengefasst wird, als Prozentsatz des Düsendurchmessers dieses Extruders. Höhere Werte fassen mehr der gekrümmten Teilgrenzen zu dicken Schichten zusammen - um den Preis raueren Grenzwänden: Abweichungen bis zu diesem Bruchteil des Düsendurchmessers werden von den dicken Extrusionen geschluckt." + +msgid "Increase" +msgstr "Erhöhen" + +msgid "Inner walls" +msgstr "Innere Wände" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Schichthöhe, mit der dieser Extruder drucken soll; für Drucker, deren Extruder unterschiedliche Düsengrößen haben. Sie muss ein ganzzahliges Vielfaches der Objekt-Schichthöhe sein. Ein Teil, dessen Merkmale alle diesem Extruder folgen, druckt nur auf jeder N-ten Schicht mit entsprechend dickeren Extrusionen, wo seine Geometrie es erlaubt; sonst fällt es auf die Objekt-Schichthöhe zurück. Kann der Rest des Teils nicht folgen, kombinieren sich diesem Extruder zugewiesene Wände trotzdem eigenständig auf diese Höhe, volldichte Deckflächen absorbieren die massiven Schichten darunter, und Füllung (auch 100% dichte) kombiniert sich unabhängig auf diese Höhe. 0 bedeutet, die Objekt-Schichthöhe zu verwenden." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Schichthöhe, mit der dieser Extruder drucken soll: ein ganzzahliges Vielfaches der Objekt-Schichthöhe innerhalb der Schichthöhenbegrenzungen dieses Extruders. Standard behält die Objekt-Schichthöhe bei." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Kein Extruder hat eine Düse, die dem Stützen-Düsendurchmesser entspricht." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Kein geladenes Filament entspricht dem Material für Stütz-/Raft-Basis (und dem Stützen-Düsendurchmesser)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Kein geladenes Filament entspricht dem Material für Stütz-/Raft-Schnittstellen (und dem Stützen-Düsendurchmesser)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Düse %1%: Schichthöhenbegrenzungen auf %2%-%3% mm gesetzt, aus \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Auf Druckern, deren Extruder unterschiedliche Düsendurchmesser haben, werden nur Filamente dieses Düsendurchmessers zum Drucken von Stützen, Raft und Stütz-Schnittstellen verwendet. Das hält Filamente anderer Düsengrößen - mit ihren anderen Linienbreiten und Schichthöhenbegrenzungen - aus den Stützen heraus. Auf einen nicht standardmäßigen Wert gesetzte Stützfilamente müssen diesem Durchmesser entsprechen. Der Wert 0 erlaubt jedem Filament, Stützen zu drucken." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Außen- und Innenwände drucken automatisch mit ihren eigenen bevorzugten Schichthöhen, wenn eine Höhe ein ganzzahliges Vielfaches der anderen ist. Wenn die Höhen nicht glatt teilbar sind, passt diese Option die Wand-Schichthöhe eines der beiden Wandfilamente (unten gewählt) auf das nächste Vielfache oder den nächsten Teiler der anderen an, damit sich die Wände weiterhin aufteilen können. Die angepasste Höhe gilt nur für die Wände dieses Filaments; andere Merkmale behalten die bevorzugte Schichthöhe. Anpassungen verlassen nie die Schichthöhenbegrenzungen des Filaments: Existiert in der gewählten Richtung keine erlaubte Höhe, drucken die Wände wie üblich gemeinsam mit der niedrigeren Höhe." + +msgid "Outer walls" +msgstr "Äußere Wände" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Schichthöhen je Extruder werden im Vasenmodus nicht unterstützt." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Schichthöhen je Extruder werden zusammen mit Schnittstellenhüllen nicht unterstützt." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Schichthöhen je Extruder werden zusammen mit variabler Schichthöhe nicht unterstützt." + +msgid "Preferred layer height" +msgstr "Bevorzugte Schichthöhe" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Stützen und Raft-Basis nur mit Filamenten dieses Materialtyps drucken; Extruder mit anderen Typen werden dafür nicht verwendet. Wirkt zusammen mit der Stützen-Düsendurchmesser-Beschränkung. Leer lassen für keine Beschränkung; ein ausdrücklich gewähltes Stütz-/Raft-Basisfilament hat weiterhin Vorrang." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Stütz- und Raft-Schnittstellen nur mit Filamenten dieses Materialtyps drucken; Extruder mit anderen Typen werden dafür nicht verwendet. Wirkt zusammen mit der Stützen-Düsendurchmesser-Beschränkung. Leer lassen für keine Beschränkung; ein ausdrücklich gewähltes Stütz-/Raft-Schnittstellenfilament hat weiterhin Vorrang." + +msgid "Raft and support base" +msgstr "Raft und Stützbasis" + +msgid "Show legacy filament selection" +msgstr "Alte Filamentauswahl anzeigen" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Einige Objektteile bevorzugen %1% mm Schichten, aber die Düse von Filament %2%, das andere Merkmale des Teils druckt, ist zu klein, um diese Höhe zu extrudieren. Diese Teile drucken stattdessen mit der Objekt-Schichthöhe." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Einige Objektteile bevorzugen %1% mm Schichten, aber die Düse von Wandfilament %2% ist zu klein, um diese Höhe zu extrudieren. Außen- und Innenwände drucken zusammen, daher behalten diese Wände die Objekt-Schichthöhe. Weisen Sie beide Wandmerkmale Filamenten mit ausreichend großen Düsen zu." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Einige Objektteile drucken %1% mm Schichten mit Filament %2%, dessen maximale Schichthöhe %3% mm beträgt. Weisen Sie die Merkmale des Teils Filamenten der gröberen Düse zu, erhöhen Sie die maximale Schichthöhe des Filaments, oder akzeptieren Sie das Drucken darüber." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Einige Objektteile drucken %1% mm Schichten mit Filament %2%, dessen minimale Schichthöhe %3% mm beträgt. Erhöhen Sie die Objekt-Schichthöhe, verwenden Sie für diese Merkmale ein Filament mit feinerer Düse, oder akzeptieren Sie das Drucken unter dem Minimum des Extruders." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Einige Objektteile verwenden Filamente, deren bevorzugte Schichthöhen nicht alle eingehalten werden können: Ein Teil druckt seine Merkmale mit einem Schichtraster (bestimmt durch seine Wandfilamente oder, wenn kein Wandfilament eine Präferenz hat, durch die Einigung der übrigen Merkmale); die Wände, die Deckflächen und die Füllung können sich jeweils eigenständig auf ihre eigene Höhe kombinieren, wenn der Rest des Teils ihnen nicht folgen kann, aber die übrigen Merkmale drucken mit dem Raster des Teils." + +msgid "Support for mixed nozzle sizes" +msgstr "Stützen bei gemischten Düsengrößen" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Stützen könnten mit Extrudern unterschiedlicher Düsendurchmesser drucken. Setzen Sie den Stützen-Düsendurchmesser (oder explizite Stütz- und Schnittstellenfilamente), um die Stützen auf einer Düsengröße zu halten." + +msgid "Support nozzle diameter" +msgstr "Stützen-Düsendurchmesser" + +msgid "Support nozzle size" +msgstr "Stützen-Düsengröße" + +msgid "Support/raft base material" +msgstr "Material für Stütz-/Raft-Basis" + +msgid "Support/raft interface material" +msgstr "Material für Stütz-/Raft-Schnittstellen" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "Die Füllungs-Linienbreite von %1% mm ist zu klein für auf %2% mm Höhe kombinierte Füllung. Erhöhen Sie die Linienbreite oder senken Sie die bevorzugte Schichthöhe des Füllfilaments." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "Die Linienbreite von %1% mm ist zu klein für die %2% mm Schichthöhe ihres Extruders. Erhöhen Sie die Linienbreite oder senken Sie die Extruder-Schichthöhe." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Die Schichthöhe von Extruder %1% (%2% mm) darf seinen Düsendurchmesser nicht überschreiten." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Die Schichthöhe von Extruder %1% (%2% mm) wird für einige Objektteile ignoriert: Sie muss ein ganzzahliges Vielfaches der Objekt-Schichthöhe (%3% mm) sein, nicht darunter liegen und darf den Düsendurchmesser des Extruders nicht überschreiten." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Die Schichthöhe von Extruder %1% (%2% mm) ist kleiner als die Objekt-Schichthöhe (%3% mm). Senken Sie die Objekt-Schichthöhe auf die feinste Extruder-Schichthöhe." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Die Schichthöhe von Extruder %1% (%2% mm) muss ein ganzzahliges Vielfaches der Objekt-Schichthöhe (%3% mm) sein." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Die niedrigste druckbare Schichthöhe des Extruders. Begrenzt die minimale Schichthöhe bei aktivierter adaptiver Schichthöhe. Teile, die mit einer dickeren bevorzugten Extruder-Schichthöhe drucken, fallen ebenfalls nie unter diese Höhe zurück (außer der ersten Schicht)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Die bevorzugte Schichthöhe von Düse %1% passt nicht mehr durch sie hindurch und wurde auf Standard zurückgesetzt." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Das Stütz-/Raft-Basisfilament ist nicht vom Material der Stütz-/Raft-Basis." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Das Stütz-/Raft-Basisfilament druckt mit einer Düse, die dem Stützen-Düsendurchmesser nicht entspricht." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Das Stütz-/Raft-Schnittstellenfilament ist nicht vom Material der Stütz-/Raft-Schnittstellen." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Das Stütz-/Raft-Schnittstellenfilament druckt mit einer Düse, die dem Stützen-Düsendurchmesser nicht entspricht." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Die Wandfilamente einiger Objektteile bevorzugen unterschiedliche Schichthöhen. Außen- und Innenwände drucken zusammen, daher behalten diese Wände die Objekt-Schichthöhe. Weisen Sie beide Wandmerkmale Filamenten zu, die dieselbe Höhe bevorzugen, um dickere Wände zu drucken." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Die Wandfilamente einiger Objektteile bevorzugen unterschiedliche Schichthöhen. Außen- und Innenwände drucken zusammen, daher drucken diese Wände mit der niedrigeren Höhe (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Die Wand-Schichthöhe von Filament %1% wurde von den bevorzugten %2% mm auf %3% mm angepasst, damit Außen- und Innenwände mit kompatiblen Schichthöhen drucken können (\"Wand-Schichthöhe anpassen\"). Nur die Wände dieses Filaments drucken die angepasste Höhe; seine anderen Merkmale behalten die bevorzugte." + +msgid "Thick layer regions" +msgstr "Bereiche dicker Schichten" + +msgid "Thick layer tolerance" +msgstr "Toleranz dicker Schichten" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Dieser Drucker hat kein Profil für eine %1% mm Düse. Bitte prüfen Sie die Schichthöhenbegrenzungen von Düse %2% in den Druckereinstellungen." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Dieser Drucker verwendet unterschiedliche Düsengrößen. Wählen Sie die Düsengröße, die die Stützen druckt, und die Filamenttypen für das Raft und die Stütz-Schnittstellen." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Ob die Wand-Schichthöhe des angepassten Wandfilaments verringert oder erhöht wird, um eine mit dem anderen Wandfilament kompatible Höhe zu erreichen. Höhen außerhalb der Schichthöhenbegrenzungen des angepassten Filaments werden nie verwendet: Existiert in dieser Richtung keine erlaubte Höhe, drucken die Wände wie üblich gemeinsam mit der niedrigeren Höhe." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Welches der beiden Wandfilamente seine Wand-Schichthöhe angepasst bekommt, wenn die bevorzugten Schichthöhen nicht glatt teilbar sind." diff --git a/localization/i18n/en/Snapmaker_Orca_en.po b/localization/i18n/en/Snapmaker_Orca_en.po index 4d08576227f..10dcb923ca6 100644 --- a/localization/i18n/en/Snapmaker_Orca_en.po +++ b/localization/i18n/en/Snapmaker_Orca_en.po @@ -17256,3 +17256,220 @@ msgstr "" #~ "It is not recommend for bed temperature of other layers to be lower than " #~ "the first layer by more than this threshold. Too low bed temperature of " #~ "other layer may cause the model to break free from the build plate." + +msgid "Adjust wall layer height" +msgstr "Adjust wall layer height" + +msgid "Adjusted walls" +msgstr "Adjusted walls" + +msgid "Adjustment direction" +msgstr "Adjustment direction" + +msgid "Consistent" +msgstr "Consistent" + +msgid "Decrease" +msgstr "Decrease" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament to print inner walls.\n\"Default\" uses the active object/part filament." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament to print outer walls.\n\"Default\" uses the active object/part filament." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament to print top surface.\n\"Default\" uses the active object/part filament." + +msgid "Fixed" +msgstr "Fixed" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." + +msgid "Increase" +msgstr "Increase" + +msgid "Inner walls" +msgstr "Inner walls" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "No extruder has a nozzle matching the support nozzle diameter." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "No loaded filament matches the support/raft base material (and the support nozzle diameter)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." + +msgid "Outer walls" +msgstr "Outer walls" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Per-extruder layer heights are not supported in spiral vase mode." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Per-extruder layer heights are not supported together with interface shells." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Per-extruder layer heights are not supported together with variable layer height." + +msgid "Preferred layer height" +msgstr "Preferred layer height" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." + +msgid "Raft and support base" +msgstr "Raft and support base" + +msgid "Show legacy filament selection" +msgstr "Show legacy filament selection" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." + +msgid "Support for mixed nozzle sizes" +msgstr "Support for mixed nozzle sizes" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." + +msgid "Support nozzle diameter" +msgstr "Support nozzle diameter" + +msgid "Support nozzle size" +msgstr "Support nozzle size" + +msgid "Support/raft base material" +msgstr "Support/raft base material" + +msgid "Support/raft interface material" +msgstr "Support/raft interface material" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "The support/raft base filament is not of the support/raft base material." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "The support/raft interface filament is not of the support/raft interface material." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." + +msgid "Thick layer regions" +msgstr "Thick layer regions" + +msgid "Thick layer tolerance" +msgstr "Thick layer tolerance" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." diff --git a/localization/i18n/es/Snapmaker_Orca_es.po b/localization/i18n/es/Snapmaker_Orca_es.po index 8c38ff57e1e..0e05473f00d 100644 --- a/localization/i18n/es/Snapmaker_Orca_es.po +++ b/localization/i18n/es/Snapmaker_Orca_es.po @@ -22302,4 +22302,220 @@ msgstr "" #~ msgstr "Valor por defecto" #~ msgid "preparing, export 3mf failed!" -#~ msgstr "Preparando; ¡Error al exportar 3MF!" \ No newline at end of file +#~ msgstr "Preparando; ¡Error al exportar 3MF!" +msgid "Adjust wall layer height" +msgstr "Ajustar la altura de capa de los perímetros" + +msgid "Adjusted walls" +msgstr "Perímetros ajustados" + +msgid "Adjustment direction" +msgstr "Dirección del ajuste" + +msgid "Consistent" +msgstr "Consistente" + +msgid "Decrease" +msgstr "Disminuir" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir la superficie inferior.\nLa opción «Predeterminado» utiliza el filamento del objeto o pieza activa." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir las paredes interiores.\nLa opción «Predeterminado» utiliza el filamento del objeto o pieza activa." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir el relleno sólido interno.\nLa opción «Predeterminado» utiliza el filamento del objeto o pieza activa." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir un relleno interno poco denso.\nLa opción «Predeterminado» utiliza el filamento del objeto o pieza activa." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir las paredes exteriores.\nLa opción «Predeterminado» utiliza el filamento del objeto o pieza activa." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filamento para imprimir la base de soporte y la balsa.\n«Predeterminado» significa que no hay un filamento específico para el soporte y se utiliza el filamento actual." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filamento para la interfaz de soporte de impresión.\n«Predeterminado» significa que no hay ningún filamento específico para la interfaz de soporte y se utiliza el filamento actual." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir la superficie superior.\nLa opción «Predeterminado» utiliza el filamento del objeto o pieza activa." + +msgid "Fixed" +msgstr "Fijo" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Con qué agresividad las piezas asignadas a un extrusor con una altura de capa preferida más gruesa se combinan en capas gruesas.\nConsistente: las piezas se imprimen con como mucho dos alturas de capa: la altura de capa del extrusor donde caben series enteras de capas y la altura de capa del objeto en el resto. Esto da los perímetros más uniformes.\nAdaptativo: las series también pueden combinarse en múltiplos intermedios de la altura de capa del objeto, de modo que más parte de la pieza se imprime con capas gruesas, a costa de bandas de alturas de capa variables en los contornos curvos.\nFijo: las piezas siempre se imprimen a la altura de capa del extrusor, incluso donde la forma cambia a través de las capas combinadas o hay voladizos; los contornos curvos se convierten en escalones y se pierde el detalle más fino que las capas gruesas. Solo la geometría demasiado corta para una capa gruesa entera (las partes superiores y la primera capa) se imprime más fina." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Cuánto puede desplazarse lateralmente el contorno de una pieza asignada a un extrusor con una altura de capa preferida más gruesa a través de las capas de una serie gruesa y aun así combinarse, como porcentaje del diámetro de boquilla de ese extrusor. Valores más altos combinan más contornos curvos en capas gruesas, a costa de perímetros de contorno más rugosos: las desviaciones hasta esta fracción del diámetro de la boquilla quedan absorbidas por las extrusiones gruesas." + +msgid "Increase" +msgstr "Aumentar" + +msgid "Inner walls" +msgstr "Paredes internas" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Altura de capa con la que debe imprimir este extrusor, para impresoras cuyos extrusores tienen boquillas de distintos tamaños. Debe ser un múltiplo entero de la altura de capa del objeto. Una pieza cuyas características siguen todas a este extrusor se imprime solo en una de cada N capas con extrusiones correspondientemente más gruesas, donde su geometría lo permite; en el resto vuelve a la altura de capa del objeto. Cuando el resto de la pieza no puede seguirlo, los perímetros asignados a este extrusor se combinan igualmente por su cuenta a esta altura, las superficies superiores totalmente densas absorben las capas sólidas inferiores, y el relleno poco denso o denso al 100% se combina de forma independiente a esta altura. 0 significa usar la altura de capa del objeto." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Altura de capa con la que debe imprimir este extrusor: un múltiplo entero de la altura de capa del objeto dentro de los límites de altura de capa de este extrusor. Por defecto mantiene la altura de capa del objeto." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Ningún extrusor tiene una boquilla que coincida con el diámetro de boquilla de soportes." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Ningún filamento cargado coincide con el material de la base de soportes/balsa (y con el diámetro de boquilla de soportes)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Ningún filamento cargado coincide con el material de la interfaz de soportes/balsa (y con el diámetro de boquilla de soportes)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Boquilla %1%: límites de altura de capa fijados en %2%-%3% mm, desde \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "En impresoras cuyos extrusores tienen diámetros de boquilla distintos, solo se usan filamentos de este diámetro de boquilla para imprimir soportes, balsa e interfaz de soportes. Esto mantiene los filamentos de otros tamaños de boquilla - con sus distintos anchos de línea y límites de altura de capa - fuera de los soportes. Los filamentos de soporte con un valor distinto del predeterminado deben coincidir con este diámetro. El valor 0 permite que cualquier filamento imprima soportes." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Los perímetros externos e internos se imprimen automáticamente a sus propias alturas de capa preferidas cuando una altura es un múltiplo entero de la otra. Cuando las alturas no se dividen exactamente, esta opción ajusta la altura de capa de los perímetros de uno de los dos filamentos de perímetro (elegido abajo) al múltiplo o divisor más cercano de la otra, para que los perímetros aún puedan separarse. La altura ajustada solo se aplica a los perímetros de ese filamento; las demás características mantienen la altura de capa preferida. Los ajustes nunca salen de los límites de altura de capa del filamento: si no existe una altura permitida en la dirección elegida, los perímetros se imprimen juntos a la altura menor como de costumbre." + +msgid "Outer walls" +msgstr "Paredes exteriores" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Las alturas de capa por extrusor no son compatibles con el modo vaso en espiral." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Las alturas de capa por extrusor no son compatibles con las carcasas de interfaz." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Las alturas de capa por extrusor no son compatibles con la altura de capa variable." + +msgid "Preferred layer height" +msgstr "Altura de capa preferida" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Imprimir la base de soportes y la balsa solo con filamentos de este tipo de material; los extrusores cargados con otros tipos no se usan para ello. Se combina con la restricción del diámetro de boquilla de soportes. Dejar vacío para no restringir; un filamento de base de soportes/balsa elegido explícitamente sigue teniendo prioridad." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Imprimir la interfaz de soportes y balsa solo con filamentos de este tipo de material; los extrusores cargados con otros tipos no se usan para ello. Se combina con la restricción del diámetro de boquilla de soportes. Dejar vacío para no restringir; un filamento de interfaz de soportes/balsa elegido explícitamente sigue teniendo prioridad." + +msgid "Raft and support base" +msgstr "Balsa y base de soportes" + +msgid "Show legacy filament selection" +msgstr "Mostrar la selección de filamento antigua" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Algunas piezas prefieren capas de %1% mm, pero la boquilla del filamento %2% que imprime otras características de la pieza es demasiado pequeña para extruir esa altura. Estas piezas se imprimen con la altura de capa del objeto en su lugar." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Algunas piezas prefieren capas de %1% mm, pero la boquilla del filamento de perímetros %2% es demasiado pequeña para extruir esa altura. Los perímetros externos e internos se imprimen juntos, así que estos perímetros mantienen la altura de capa del objeto. Asigne ambas características de perímetro a filamentos con boquillas suficientemente grandes." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Algunas piezas imprimen capas de %1% mm con el filamento %2%, cuya altura de capa máxima es %3% mm. Asigne las características de la pieza a filamentos de la boquilla más gruesa, aumente la altura de capa máxima del filamento o acepte imprimir por encima." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Algunas piezas imprimen capas de %1% mm con el filamento %2%, cuya altura de capa mínima es %3% mm. Aumente la altura de capa del objeto, use un filamento con una boquilla más fina para estas características o acepte imprimir por debajo del mínimo del extrusor." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Algunas piezas usan filamentos cuyas alturas de capa preferidas no pueden respetarse todas: una pieza imprime sus características con un solo paso de capa (fijado por sus filamentos de perímetro o, cuando ningún filamento de perímetro tiene preferencia, por el acuerdo de las demás características); los perímetros, las superficies superiores y el relleno pueden combinarse cada uno a su propia altura cuando el resto de la pieza no puede seguirlos, pero las características restantes se imprimen con el paso de la pieza." + +msgid "Support for mixed nozzle sizes" +msgstr "Soportes con tamaños de boquilla mixtos" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Los soportes podrían imprimirse con extrusores de diámetros de boquilla distintos. Fije el diámetro de boquilla de soportes (o filamentos explícitos de soporte e interfaz) para mantener los soportes en un solo tamaño de boquilla." + +msgid "Support nozzle diameter" +msgstr "Diámetro de boquilla de soportes" + +msgid "Support nozzle size" +msgstr "Tamaño de boquilla de soportes" + +msgid "Support/raft base material" +msgstr "Material de la base de soportes/balsa" + +msgid "Support/raft interface material" +msgstr "Material de la interfaz de soportes/balsa" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "El ancho de línea de relleno de %1% mm es demasiado pequeño para relleno combinado en capas de %2% mm de alto. Aumente el ancho de línea o reduzca la altura de capa preferida del filamento de relleno." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "El ancho de línea de %1% mm es demasiado pequeño para la altura de capa de %2% mm de su extrusor. Aumente el ancho de línea o reduzca la altura de capa del extrusor." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "La altura de capa del extrusor %1% (%2% mm) no puede superar su diámetro de boquilla." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "La altura de capa del extrusor %1% (%2% mm) se ignora para algunas piezas: debe ser un múltiplo entero de la altura de capa del objeto (%3% mm), no inferior a ella, y no debe superar el diámetro de boquilla del extrusor." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "La altura de capa del extrusor %1% (%2% mm) es menor que la altura de capa del objeto (%3% mm). Reduzca la altura de capa del objeto a la altura de capa de extrusor más fina." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "La altura de capa del extrusor %1% (%2% mm) debe ser un múltiplo entero de la altura de capa del objeto (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "La altura de capa imprimible más baja del extrusor. Se usa para limitar la altura de capa mínima cuando la altura de capa adaptativa está activada. Las piezas impresas con una altura de capa de extrusor preferida más gruesa tampoco bajan nunca de esta altura (excepto la primera capa)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "La altura de capa preferida de la boquilla %1% ya no cabe por ella y se restableció a Por defecto." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "El filamento de la base de soportes/balsa no es del material de la base de soportes/balsa." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "El filamento de la base de soportes/balsa imprime con una boquilla que no coincide con el diámetro de boquilla de soportes." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "El filamento de la interfaz de soportes/balsa no es del material de la interfaz de soportes/balsa." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "El filamento de la interfaz de soportes/balsa imprime con una boquilla que no coincide con el diámetro de boquilla de soportes." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Los filamentos de perímetro de algunas piezas prefieren alturas de capa distintas. Los perímetros externos e internos se imprimen juntos, así que estos perímetros mantienen la altura de capa del objeto. Asigne ambas características de perímetro a filamentos que prefieran la misma altura para imprimir perímetros más gruesos." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Los filamentos de perímetro de algunas piezas prefieren alturas de capa distintas. Los perímetros externos e internos se imprimen juntos, así que estos perímetros se imprimen con la altura menor (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "La altura de capa de perímetros del filamento %1% se ajustó de los %2% mm preferidos a %3% mm para que los perímetros externos e internos puedan imprimirse a alturas de capa compatibles (\"Ajustar la altura de capa de los perímetros\"). Solo los perímetros de este filamento imprimen la altura ajustada; sus demás características mantienen la preferida." + +msgid "Thick layer regions" +msgstr "Regiones de capas gruesas" + +msgid "Thick layer tolerance" +msgstr "Tolerancia de capas gruesas" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Esta impresora no tiene perfil para una boquilla de %1% mm. Revise los límites de altura de capa de la boquilla %2% en los ajustes de la impresora." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Esta impresora usa tamaños de boquilla distintos. Seleccione el tamaño de boquilla que imprime los soportes y los tipos de filamento usados para la balsa y la interfaz de soportes." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Si la altura de capa de perímetros del filamento ajustado se disminuye o se aumenta para alcanzar una altura compatible con el otro filamento de perímetro. Nunca se usan alturas fuera de los límites de altura de capa del filamento ajustado: si no existe una altura permitida en esta dirección, los perímetros se imprimen juntos a la altura menor como de costumbre." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Cuál de los dos filamentos de perímetro recibe el ajuste de su altura de capa de perímetros cuando las alturas de capa preferidas no se dividen exactamente." diff --git a/localization/i18n/fr/Snapmaker_Orca_fr.po b/localization/i18n/fr/Snapmaker_Orca_fr.po index 7810c5d55a5..c5f77beb522 100644 --- a/localization/i18n/fr/Snapmaker_Orca_fr.po +++ b/localization/i18n/fr/Snapmaker_Orca_fr.po @@ -21602,4 +21602,220 @@ msgstr "" #~ msgstr "" #~ "Veuillez saisir des valeurs valides :\n" #~ "Début > 10 intervalles >= 0\n" -#~ "Fin > Début + Intervalle" \ No newline at end of file +#~ "Fin > Début + Intervalle" +msgid "Adjust wall layer height" +msgstr "Ajuster la hauteur de couche des parois" + +msgid "Adjusted walls" +msgstr "Parois ajustées" + +msgid "Adjustment direction" +msgstr "Sens de l'ajustement" + +msgid "Consistent" +msgstr "Cohérent" + +msgid "Decrease" +msgstr "Diminuer" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament pour imprimer la surface inférieure.\n\"Défaut\" utilise le filament actif de l’objet ou de la pièce." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament pour imprimer les parois internes.\n\"Défaut\" utilise le filament actif de l’objet ou de la pièce." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament pour imprimer le remplissage plein interne.\n\"Défaut\" utilise le filament actif de l’objet ou de la pièce." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament pour imprimer le remplissage interne.\n\"Défaut\" utilise le filament actif de l’objet ou de la pièce." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament pour imprimer les parois externes.\n\"Défaut\" utilise le filament actif de l’objet ou de la pièce." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament pour imprimer la base des supports et le radeau.\n\"Défaut\" signifie qu’aucun filament spécifique n’est dédié aux supports : le filament actuel est utilisé." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament pour imprimer l’interface des supports.\n\"Défaut\" signifie qu’aucun filament spécifique n’est dédié à l’interface des supports : le filament actuel est utilisé." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament pour imprimer la surface supérieure.\n\"Défaut\" utilise le filament actif de l’objet ou de la pièce." + +msgid "Fixed" +msgstr "Fixe" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Détermine l'agressivité avec laquelle les pièces affectées à un extrudeur avec une hauteur de couche préférée plus épaisse sont combinées en couches épaisses.\nCohérent : les pièces s'impriment avec au plus deux hauteurs de couche - la hauteur de couche de l'extrudeur partout où des séries entières de couches tiennent, et la hauteur de couche de l'objet partout ailleurs. Cela donne les parois les plus uniformes.\nAdaptatif : les séries peuvent aussi être combinées à des multiples intermédiaires de la hauteur de couche de l'objet, donc une plus grande partie de la pièce s'imprime en couches épaisses, au prix de bandes de hauteurs de couche variables sur les contours courbes.\nFixe : les pièces s'impriment toujours à la hauteur de couche de l'extrudeur, même là où la forme change à travers les couches combinées ou surplombe ; les contours courbes deviennent des marches et les détails plus fins que les couches épaisses sont perdus. Seule la géométrie trop courte pour une couche épaisse entière (les sommets des pièces et la première couche) s'imprime plus finement." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Distance de dérive latérale autorisée du contour d'une pièce affectée à un extrudeur avec une hauteur de couche préférée plus épaisse à travers les couches d'une série épaisse, tout en restant combinable, en pourcentage du diamètre de buse de cet extrudeur. Des valeurs plus élevées combinent davantage de contours courbes en couches épaisses, au prix de parois de contour plus rugueuses : les écarts jusqu'à cette fraction du diamètre de la buse sont absorbés par les extrusions épaisses." + +msgid "Increase" +msgstr "Augmenter" + +msgid "Inner walls" +msgstr "Parois internes" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Hauteur de couche avec laquelle cet extrudeur doit imprimer, pour les imprimantes dont les extrudeurs ont des tailles de buse différentes. Elle doit être un multiple entier de la hauteur de couche de l'objet. Une pièce dont toutes les caractéristiques suivent cet extrudeur ne s'imprime que sur une couche sur N avec des extrusions plus épaisses en conséquence, partout où sa géométrie le permet ; ailleurs, elle revient à la hauteur de couche de l'objet. Quand le reste de la pièce ne peut pas suivre, les parois affectées à cet extrudeur se combinent quand même seules à cette hauteur, les surfaces supérieures pleines absorbent les couches solides en dessous, et le remplissage (clairsemé ou dense à 100%) se combine indépendamment à cette hauteur. 0 signifie utiliser la hauteur de couche de l'objet." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Hauteur de couche avec laquelle cet extrudeur doit imprimer : un multiple entier de la hauteur de couche de l'objet dans les limites de hauteur de couche de cet extrudeur. Défaut conserve la hauteur de couche de l'objet." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Aucun extrudeur n'a de buse correspondant au diamètre de buse des supports." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Aucun filament chargé ne correspond au matériau de la base des supports/du radeau (et au diamètre de buse des supports)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Aucun filament chargé ne correspond au matériau de l'interface des supports/du radeau (et au diamètre de buse des supports)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Buse %1% : limites de hauteur de couche réglées sur %2%-%3% mm, depuis \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Sur les imprimantes dont les extrudeurs ont des diamètres de buse différents, seuls les filaments de ce diamètre de buse sont utilisés pour imprimer les supports, le radeau et l'interface des supports. Cela tient les filaments d'autres tailles de buse - avec leurs largeurs de ligne et limites de hauteur de couche différentes - à l'écart des supports. Les filaments de support réglés sur une valeur non par défaut doivent correspondre à ce diamètre. La valeur 0 permet à tout filament d'imprimer les supports." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Les parois extérieures et intérieures s'impriment automatiquement à leurs propres hauteurs de couche préférées quand une hauteur est un multiple entier de l'autre. Quand les hauteurs ne se divisent pas exactement, cette option ajuste la hauteur de couche des parois de l'un des deux filaments de paroi (choisi ci-dessous) au multiple ou diviseur le plus proche de l'autre, pour que les parois puissent encore se séparer. La hauteur ajustée ne s'applique qu'aux parois de ce filament ; les autres caractéristiques conservent la hauteur de couche préférée. Les ajustements ne sortent jamais des limites de hauteur de couche du filament : si aucune hauteur autorisée n'existe dans le sens choisi, les parois s'impriment ensemble à la hauteur inférieure comme d'habitude." + +msgid "Outer walls" +msgstr "Parois externes" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Les hauteurs de couche par extrudeur ne sont pas prises en charge en mode vase spirale." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Les hauteurs de couche par extrudeur ne sont pas prises en charge avec les coques d'interface." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Les hauteurs de couche par extrudeur ne sont pas prises en charge avec la hauteur de couche variable." + +msgid "Preferred layer height" +msgstr "Hauteur de couche préférée" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "N'imprimer la base des supports et le radeau qu'avec des filaments de ce type de matériau ; les extrudeurs chargés avec d'autres types ne sont pas utilisés pour cela. Se combine avec la restriction du diamètre de buse des supports. Laisser vide pour aucune restriction ; un filament de base des supports/du radeau choisi explicitement garde la priorité." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "N'imprimer l'interface des supports et du radeau qu'avec des filaments de ce type de matériau ; les extrudeurs chargés avec d'autres types ne sont pas utilisés pour cela. Se combine avec la restriction du diamètre de buse des supports. Laisser vide pour aucune restriction ; un filament d'interface des supports/du radeau choisi explicitement garde la priorité." + +msgid "Raft and support base" +msgstr "Radeau et base des supports" + +msgid "Show legacy filament selection" +msgstr "Afficher l'ancienne sélection de filament" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Certaines pièces préfèrent des couches de %1% mm, mais la buse du filament %2% qui imprime d'autres caractéristiques de la pièce est trop petite pour extruder cette hauteur. Ces pièces s'impriment à la hauteur de couche de l'objet à la place." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Certaines pièces préfèrent des couches de %1% mm, mais la buse du filament de paroi %2% est trop petite pour extruder cette hauteur. Les parois extérieures et intérieures s'impriment ensemble, donc ces parois conservent la hauteur de couche de l'objet. Affectez les deux types de parois à des filaments avec des buses assez grandes." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Certaines pièces impriment des couches de %1% mm avec le filament %2% dont la hauteur de couche maximale est %3% mm. Affectez les caractéristiques de la pièce aux filaments de la buse la plus grosse, augmentez la hauteur de couche maximale du filament, ou acceptez d'imprimer au-dessus." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Certaines pièces impriment des couches de %1% mm avec le filament %2% dont la hauteur de couche minimale est %3% mm. Augmentez la hauteur de couche de l'objet, utilisez un filament avec une buse plus fine pour ces caractéristiques, ou acceptez d'imprimer sous le minimum de l'extrudeur." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Certaines pièces utilisent des filaments dont les hauteurs de couche préférées ne peuvent pas toutes être respectées : une pièce imprime ses caractéristiques avec un seul pas de couche (fixé par ses filaments de paroi, ou par l'accord des autres caractéristiques quand aucun filament de paroi n'a de préférence) ; les parois, les surfaces supérieures et le remplissage peuvent chacun se combiner à leur propre hauteur quand le reste de la pièce ne peut pas les suivre, mais les caractéristiques restantes s'impriment avec le pas de la pièce." + +msgid "Support for mixed nozzle sizes" +msgstr "Supports pour tailles de buse mixtes" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Les supports pourraient s'imprimer avec des extrudeurs de diamètres de buse différents. Réglez le diamètre de buse des supports (ou des filaments de support et d'interface explicites) pour garder les supports sur une seule taille de buse." + +msgid "Support nozzle diameter" +msgstr "Diamètre de buse des supports" + +msgid "Support nozzle size" +msgstr "Taille de buse des supports" + +msgid "Support/raft base material" +msgstr "Matériau de la base des supports/du radeau" + +msgid "Support/raft interface material" +msgstr "Matériau de l'interface des supports/du radeau" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "La largeur de ligne de remplissage de %1% mm est trop petite pour un remplissage combiné en couches de %2% mm de haut. Augmentez la largeur de ligne ou diminuez la hauteur de couche préférée du filament de remplissage." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "La largeur de ligne de %1% mm est trop petite pour la hauteur de couche de %2% mm de son extrudeur. Augmentez la largeur de ligne ou diminuez la hauteur de couche de l'extrudeur." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "La hauteur de couche de l'extrudeur %1% (%2% mm) ne peut pas dépasser son diamètre de buse." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "La hauteur de couche de l'extrudeur %1% (%2% mm) est ignorée pour certaines pièces : elle doit être un multiple entier de la hauteur de couche de l'objet (%3% mm), pas en dessous, et ne doit pas dépasser le diamètre de buse de l'extrudeur." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "La hauteur de couche de l'extrudeur %1% (%2% mm) est plus petite que la hauteur de couche de l'objet (%3% mm). Diminuez la hauteur de couche de l'objet à la hauteur de couche d'extrudeur la plus fine." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "La hauteur de couche de l'extrudeur %1% (%2% mm) doit être un multiple entier de la hauteur de couche de l'objet (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "La hauteur de couche imprimable la plus basse de l'extrudeur. Sert à limiter la hauteur de couche minimale quand la hauteur de couche adaptative est activée. Les pièces imprimées avec une hauteur de couche d'extrudeur préférée plus épaisse ne redescendent jamais non plus sous cette hauteur (première couche exceptée)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "La hauteur de couche préférée de la buse %1% ne passe plus à travers elle et a été réinitialisée sur Défaut." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Le filament de la base des supports/du radeau n'est pas du matériau de la base des supports/du radeau." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Le filament de la base des supports/du radeau s'imprime avec une buse qui ne correspond pas au diamètre de buse des supports." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Le filament de l'interface des supports/du radeau n'est pas du matériau de l'interface des supports/du radeau." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Le filament de l'interface des supports/du radeau s'imprime avec une buse qui ne correspond pas au diamètre de buse des supports." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Les filaments de paroi de certaines pièces préfèrent des hauteurs de couche différentes. Les parois extérieures et intérieures s'impriment ensemble, donc ces parois conservent la hauteur de couche de l'objet. Affectez les deux types de parois à des filaments préférant la même hauteur pour imprimer des parois plus épaisses." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Les filaments de paroi de certaines pièces préfèrent des hauteurs de couche différentes. Les parois extérieures et intérieures s'impriment ensemble, donc ces parois s'impriment à la hauteur inférieure (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "La hauteur de couche des parois du filament %1% a été ajustée de %2% mm (préférée) à %3% mm pour que les parois extérieures et intérieures puissent s'imprimer à des hauteurs de couche compatibles (\"Ajuster la hauteur de couche des parois\"). Seules les parois de ce filament impriment la hauteur ajustée ; ses autres caractéristiques conservent la hauteur préférée." + +msgid "Thick layer regions" +msgstr "Régions à couches épaisses" + +msgid "Thick layer tolerance" +msgstr "Tolérance des couches épaisses" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Cette imprimante n'a pas de profil pour une buse de %1% mm. Veuillez vérifier les limites de hauteur de couche de la buse %2% dans les réglages de l'imprimante." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Cette imprimante utilise des tailles de buse différentes. Sélectionnez la taille de buse qui imprime les supports, et les types de filament utilisés pour le radeau et l'interface des supports." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Détermine si la hauteur de couche des parois du filament ajusté est diminuée ou augmentée pour atteindre une hauteur compatible avec l'autre filament de paroi. Les hauteurs hors des limites de hauteur de couche du filament ajusté ne sont jamais utilisées : si aucune hauteur autorisée n'existe dans ce sens, les parois s'impriment ensemble à la hauteur inférieure comme d'habitude." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Lequel des deux filaments de paroi voit sa hauteur de couche des parois ajustée quand les hauteurs de couche préférées ne se divisent pas exactement." diff --git a/localization/i18n/hu/Snapmaker_Orca_hu.po b/localization/i18n/hu/Snapmaker_Orca_hu.po index f8bb441f9c4..68eb30ec104 100644 --- a/localization/i18n/hu/Snapmaker_Orca_hu.po +++ b/localization/i18n/hu/Snapmaker_Orca_hu.po @@ -18267,4 +18267,220 @@ msgstr "" #~ "leválik az asztalról nyomtatás közben" #~ msgid "Orient the model" -#~ msgstr "Modell orientációja" \ No newline at end of file +#~ msgstr "Modell orientációja" +msgid "Adjust wall layer height" +msgstr "Fal rétegmagasság igazítása" + +msgid "Adjusted walls" +msgstr "Igazított falak" + +msgid "Adjustment direction" +msgstr "Igazítás iránya" + +msgid "Consistent" +msgstr "Egyenletes" + +msgid "Decrease" +msgstr "Csökkentés" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament az alsó felület nyomtatásához.\nAz \"Alapértelmezett\" a tárgy/alkatrész aktív filamentjét használja." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament a belső falak nyomtatásához.\nAz \"Alapértelmezett\" a tárgy/alkatrész aktív filamentjét használja." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament a belső tömör kitöltés nyomtatásához.\nAz \"Alapértelmezett\" a tárgy/alkatrész aktív filamentjét használja." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament a belső ritkás kitöltés nyomtatásához.\nAz \"Alapértelmezett\" a tárgy/alkatrész aktív filamentjét használja." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament a külső falak nyomtatásához.\nAz \"Alapértelmezett\" a tárgy/alkatrész aktív filamentjét használja." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "A támasz alapjához és a tutaj nyomtatásához használt filament. Az \"Alapértelmezett\" beállítás választásakor a jelenleg használt filament kerül felhasználásra." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament a támasz érintkező felületének nyomtatásához. Az \"Alapértelmezett\" beállítás választásakor a jelenleg használt filament kerül felhasználásra." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament a felső felület nyomtatásához.\nAz \"Alapértelmezett\" a tárgy/alkatrész aktív filamentjét használja." + +msgid "Fixed" +msgstr "Rögzített" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Mennyire agresszíven vonjuk össze vastag rétegekbe azokat az alkatrészeket, amelyek vastagabb preferált rétegmagasságú extruderhez vannak rendelve.\nEgyenletes: az alkatrészek legfeljebb két rétegmagassággal nyomtatódnak - az extruder rétegmagasságával ott, ahová teljes rétegsorozatok beférnek, és a tárgy rétegmagasságával mindenhol máshol. Ez adja a legegyenletesebb falakat.\nAdaptív: a sorozatok a tárgy rétegmagasságának köztes többszörösein is összevonhatók, így az alkatrész nagyobb része nyomtatódik vastagabb rétegekkel - cserébe az íves alkatrészhatárokon váltakozó rétegmagasságú sávok jelennek meg.\nRögzített: az alkatrészek mindig az extruder rétegmagasságával nyomtatódnak, ott is, ahol az alak az összevont rétegeken át változik vagy túlnyúlik; az íves határok lépcsőkké válnak, és a vastag rétegeknél finomabb részletek elvesznek. Csak az egy teljes vastag réteghez túl rövid geometria (az alkatrészek teteje és az első réteg) nyomtatódik vékonyabban." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Mennyit sodródhat oldalra egy vastagabb preferált rétegmagasságú extruderhez rendelt alkatrész körvonala egy vastag sorozat rétegein át úgy, hogy még összevonható maradjon, az extruder fúvókaátmérőjének százalékában. Nagyobb értékek több íves alkatrészhatárt vonnak össze vastag rétegekbe, cserébe durvább határfalakkal: a fúvókaátmérő ekkora hányadáig terjedő eltéréseket elnyelik a vastag szálak." + +msgid "Increase" +msgstr "Növelés" + +msgid "Inner walls" +msgstr "Belső falak" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "A rétegmagasság, amellyel ennek az extrudernek nyomtatnia kell; olyan nyomtatókhoz, amelyek extruderei különböző fúvókaméretűek. A tárgy rétegmagasságának egész számú többszörösének kell lennie. Az az alkatrész, amelynek minden eleme ezt az extrudert követi, csak minden N-edik rétegen nyomtat ennek megfelelően vastagabb szálakkal, ahol a geometriája engedi; máshol visszaáll a tárgy rétegmagasságára. Ha az alkatrész többi része nem tud követni, az ehhez az extruderhez rendelt falak akkor is önállóan összevonódnak erre a magasságra, a teljes sűrűségű felső felületek elnyelik az alattuk lévő tömör rétegeket, és a ritkás vagy 100% sűrűségű kitöltés függetlenül összevonódik erre a magasságra. A 0 a tárgy rétegmagasságának használatát jelenti." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "A rétegmagasság, amellyel ennek az extrudernek nyomtatnia kell: a tárgy rétegmagasságának egész számú többszöröse az extruder rétegmagasság-korlátain belül. Az Alapértelmezett megtartja a tárgy rétegmagasságát." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Egyik extruder fúvókája sem egyezik a támasz fúvókaátmérőjével." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Egyik betöltött filament sem felel meg a támasz/tutaj alap anyagának (és a támasz fúvókaátmérőjének)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Egyik betöltött filament sem felel meg a támasz/tutaj érintkezőréteg anyagának (és a támasz fúvókaátmérőjének)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "%1%. fúvóka: a rétegmagasság-korlátok %2%-%3% mm-re állítva a(z) \"%4%\" alapján." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Az olyan nyomtatókon, amelyek extruderei különböző fúvókaátmérőjűek, csak az ilyen fúvókaátmérőjű filamentek nyomtatják a támaszt, a tutajt és a támasz-érintkezőréteget. Ez távol tartja a más fúvókaméretű filamenteket - eltérő vonalszélességükkel és rétegmagasság-korlátaikkal - a támasztól. A nem alapértelmezettre állított támaszfilamenteknek egyezniük kell ezzel az átmérővel. A 0 érték bármely filamentnek engedi a támasz nyomtatását." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "A külső és belső falak automatikusan a saját preferált rétegmagasságukkal nyomtatódnak, ha az egyik magasság a másik egész számú többszöröse. Ha a magasságok nem oszthatók maradék nélkül, ez a beállítás a két falfilament egyikének (lent kiválasztott) fal-rétegmagasságát a másik legközelebbi többszörösére vagy osztójára igazítja, hogy a falak továbbra is szétválhassanak. Az igazított magasság csak annak a filamentnek a falaira vonatkozik; a többi elem megtartja a preferált rétegmagasságot. Az igazítás sosem lépi át a filament rétegmagasság-korlátait: ha a választott irányban nincs megengedett magasság, a falak a szokásos módon együtt, az alacsonyabb magassággal nyomtatódnak." + +msgid "Outer walls" +msgstr "Külső falak" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Az extruderenkénti rétegmagasság nem támogatott spirál (váza) módban." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Az extruderenkénti rétegmagasság nem támogatott érintkező héjakkal együtt." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Az extruderenkénti rétegmagasság nem támogatott változó rétegmagassággal együtt." + +msgid "Preferred layer height" +msgstr "Preferált rétegmagasság" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "A támasz és a tutaj alapja csak ilyen anyagtípusú filamentekkel nyomtatódik; a más típussal töltött extruderek ehhez nem lesznek használva. A támasz fúvókaátmérő-korlátozásával együtt érvényesül. Hagyja üresen, ha nem akar korlátozni; a kifejezetten kiválasztott támasz/tutaj alap filament továbbra is elsőbbséget élvez." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "A támasz és a tutaj érintkezőrétege csak ilyen anyagtípusú filamentekkel nyomtatódik; a más típussal töltött extruderek ehhez nem lesznek használva. A támasz fúvókaátmérő-korlátozásával együtt érvényesül. Hagyja üresen, ha nem akar korlátozni; a kifejezetten kiválasztott érintkezőréteg-filament továbbra is elsőbbséget élvez." + +msgid "Raft and support base" +msgstr "Tutaj és támasz alap" + +msgid "Show legacy filament selection" +msgstr "Régi filament-kiválasztás megjelenítése" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Néhány alkatrész %1% mm-es rétegeket preferál, de az alkatrész más elemeit nyomtató %2%. filament fúvókája túl kicsi ekkora magasság extrudálásához. Ezek az alkatrészek helyette a tárgy rétegmagasságával nyomtatódnak." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Néhány alkatrész %1% mm-es rétegeket preferál, de a %2%. falfilament fúvókája túl kicsi ekkora magasság extrudálásához. A külső és belső falak együtt nyomtatódnak, így ezek a falak a tárgy rétegmagasságát tartják. Rendelje mindkét falelemet elég nagy fúvókájú filamentekhez." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Néhány alkatrész %1% mm-es rétegeket nyomtat a %2%. filamenttel, amelynek maximális rétegmagassága %3% mm. Rendelje az alkatrész elemeit a durvább fúvóka filamentjeihez, növelje a filament maximális rétegmagasságát, vagy fogadja el a fölötte nyomtatást." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Néhány alkatrész %1% mm-es rétegeket nyomtat a %2%. filamenttel, amelynek minimális rétegmagassága %3% mm. Növelje a tárgy rétegmagasságát, használjon finomabb fúvókájú filamentet ezekhez az elemekhez, vagy fogadja el az extruder minimuma alatti nyomtatást." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Néhány alkatrész olyan filamenteket használ, amelyek preferált rétegmagasságai nem teljesíthetők mind: egy alkatrész az elemeit egyetlen réteglépéssel nyomtatja (ezt a falfilamentjei határozzák meg, vagy ha egyik falfilamentnek sincs preferenciája, a többi elem megegyezése); a falak, a felső felületek és a kitöltés külön-külön összevonódhatnak a saját magasságukra, ha az alkatrész többi része nem tudja követni őket, de a maradék elemek az alkatrész lépésével nyomtatódnak." + +msgid "Support for mixed nozzle sizes" +msgstr "Támasz vegyes fúvókaméretekhez" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "A támasz különböző fúvókaátmérőjű extruderekkel nyomtatódhat. Állítsa be a támasz fúvókaátmérőjét (vagy kifejezett támasz- és érintkezőréteg-filamenteket), hogy a támasz egy fúvókaméreten maradjon." + +msgid "Support nozzle diameter" +msgstr "Támasz fúvókaátmérő" + +msgid "Support nozzle size" +msgstr "Támasz fúvókaméret" + +msgid "Support/raft base material" +msgstr "Támasz/tutaj alap anyaga" + +msgid "Support/raft interface material" +msgstr "Támasz/tutaj érintkezőréteg anyaga" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "A %1% mm-es kitöltési vonalszélesség túl kicsi a %2% mm magas rétegekbe összevont kitöltéshez. Növelje a vonalszélességet, vagy csökkentse a kitöltőfilament preferált rétegmagasságát." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "A %1% mm-es vonalszélesség túl kicsi az extruder %2% mm-es rétegmagasságához. Növelje a vonalszélességet, vagy csökkentse az extruder rétegmagasságát." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "A(z) %1%. extruder rétegmagassága (%2% mm) nem haladhatja meg a fúvókaátmérőjét." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "A(z) %1%. extruder rétegmagassága (%2% mm) néhány alkatrésznél figyelmen kívül marad: a tárgy rétegmagasságának (%3% mm) egész számú többszörösének kell lennie, nem lehet alatta, és nem haladhatja meg az extruder fúvókaátmérőjét." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "A(z) %1%. extruder rétegmagassága (%2% mm) kisebb a tárgy rétegmagasságánál (%3% mm). Csökkentse a tárgy rétegmagasságát a legfinomabb extruder-rétegmagasságra." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "A(z) %1%. extruder rétegmagasságának (%2% mm) a tárgy rétegmagasságának (%3% mm) egész számú többszörösének kell lennie." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Az extruder legkisebb nyomtatható rétegmagassága. Az adaptív rétegmagasság bekapcsolásakor a minimális rétegmagasság korlátozására szolgál. A vastagabb preferált extruder-rétegmagassággal nyomtatott alkatrészek sem esnek soha ez alá a magasság alá (az első réteg kivételével)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "A(z) %1%. fúvóka preferált rétegmagassága már nem fér át rajta, ezért Alapértelmezettre lett visszaállítva." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "A támasz/tutaj alap filamentje nem a támasz/tutaj alap anyagából való." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "A támasz/tutaj alap filamentje olyan fúvókával nyomtat, amely nem egyezik a támasz fúvókaátmérőjével." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "A támasz/tutaj érintkezőréteg filamentje nem a támasz/tutaj érintkezőréteg anyagából való." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "A támasz/tutaj érintkezőréteg filamentje olyan fúvókával nyomtat, amely nem egyezik a támasz fúvókaátmérőjével." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Néhány alkatrész falfilamentjei eltérő rétegmagasságokat preferálnak. A külső és belső falak együtt nyomtatódnak, így ezek a falak a tárgy rétegmagasságát tartják. Vastagabb falak nyomtatásához rendelje mindkét falelemet azonos magasságot preferáló filamentekhez." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Néhány alkatrész falfilamentjei eltérő rétegmagasságokat preferálnak. A külső és belső falak együtt nyomtatódnak, így ezek a falak az alacsonyabb magassággal (%1% mm) nyomtatódnak." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "A(z) %1%. filament fal-rétegmagassága a preferált %2% mm-ről %3% mm-re lett igazítva, hogy a külső és belső falak összeférő rétegmagasságokkal nyomtatódhassanak (\"Fal rétegmagasság igazítása\"). Az igazított magassággal csak ennek a filamentnek a falai nyomtatódnak; a többi eleme megtartja a preferáltat." + +msgid "Thick layer regions" +msgstr "Vastag rétegű területek" + +msgid "Thick layer tolerance" +msgstr "Vastag réteg tűrése" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Ennek a nyomtatónak nincs profilja %1% mm-es fúvókához. Ellenőrizze a(z) %2%. fúvóka rétegmagasság-korlátait a nyomtató beállításaiban." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Ez a nyomtató különböző fúvókaméreteket használ. Válassza ki a támaszt nyomtató fúvókaméretet, valamint a tutajhoz és a támasz-érintkezőréteghez használt filamenttípusokat." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Az igazított falfilament fal-rétegmagassága csökkenjen vagy nőjön-e, hogy a másik falfilamenttel összeférő magasságot érjen el. Az igazított filament rétegmagasság-korlátain kívüli magasságok sosem lesznek használva: ha ebben az irányban nincs megengedett magasság, a falak a szokásos módon együtt, az alacsonyabb magassággal nyomtatódnak." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "A két falfilament közül melyiknek legyen igazítva a fal-rétegmagassága, ha a preferált rétegmagasságok nem oszthatók maradék nélkül." diff --git a/localization/i18n/it/Snapmaker_Orca_it.po b/localization/i18n/it/Snapmaker_Orca_it.po index 925baf5108f..8b724dd6026 100644 --- a/localization/i18n/it/Snapmaker_Orca_it.po +++ b/localization/i18n/it/Snapmaker_Orca_it.po @@ -21001,4 +21001,220 @@ msgstr "" #~ "Non è consigliabile che la temperatura del piano degli altri layer sia " #~ "inferiore a quella del primo layer di oltre questa soglia. Una " #~ "temperatura del piano troppo bassa degli altri layer può causare il " -#~ "distacco dell'oggetto dal piatto." \ No newline at end of file +#~ "distacco dell'oggetto dal piatto." +msgid "Adjust wall layer height" +msgstr "Regola l'altezza strato delle pareti" + +msgid "Adjusted walls" +msgstr "Pareti regolate" + +msgid "Adjustment direction" +msgstr "Direzione della regolazione" + +msgid "Consistent" +msgstr "Coerente" + +msgid "Decrease" +msgstr "Diminuisci" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filamento per stampare la superficie inferiore.\n\"Predefinito\" usa il filamento attivo dell'oggetto/parte." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filamento per stampare le pareti interne.\n\"Predefinito\" usa il filamento attivo dell'oggetto/parte." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filamento per stampare il riempimento solido interno.\n\"Predefinito\" usa il filamento attivo dell'oggetto/parte." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filamento per stampare il riempimento sparso interno.\n\"Predefinito\" usa il filamento attivo dell'oggetto/parte." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filamento per stampare le pareti esterne.\n\"Predefinito\" usa il filamento attivo dell'oggetto/parte." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filamento per stampare basi di supporto e zattere. \"Predefinito\" indica che non verrà utilizzato alcun filamento specifico per il supporto e che sarà utilizzato il filamento corrente." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filamento per la stampa delle interfacce di supporto. \"Predefinito\" indica che non verrà utilizzato alcun filamento specifico per l'interfaccia di supporto e che sarà utilizzato il filamento corrente." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filamento per stampare la superficie superiore.\n\"Predefinito\" usa il filamento attivo dell'oggetto/parte." + +msgid "Fixed" +msgstr "Fissa" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Quanto aggressivamente le parti assegnate a un estrusore con un'altezza strato preferita più spessa vengono combinate in strati spessi.\nCoerente: le parti stampano con al massimo due altezze strato - l'altezza strato dell'estrusore dove entrano serie intere di strati e l'altezza strato dell'oggetto ovunque altrove. Questo dà le pareti più uniformi.\nAdattiva: le serie possono anche essere combinate a multipli intermedi dell'altezza strato dell'oggetto, così una parte maggiore del pezzo stampa con strati più spessi, al prezzo di fasce di altezze strato variabili sui bordi curvi.\nFissa: le parti stampano sempre all'altezza strato dell'estrusore, anche dove la forma cambia attraverso gli strati combinati o sporge; i bordi curvi diventano gradini e i dettagli più fini degli strati spessi vanno persi. Solo la geometria troppo corta per un intero strato spesso (le sommità delle parti e il primo strato) stampa più sottile." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Di quanto il profilo di una parte assegnata a un estrusore con un'altezza strato preferita più spessa può spostarsi lateralmente attraverso gli strati di una serie spessa restando comunque combinabile, come percentuale del diametro ugello di quell'estrusore. Valori più alti combinano più bordi curvi in strati spessi, al prezzo di pareti di bordo più ruvide: le deviazioni fino a questa frazione del diametro dell'ugello vengono assorbite dalle estrusioni spesse." + +msgid "Increase" +msgstr "Aumenta" + +msgid "Inner walls" +msgstr "Pareti interne" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Altezza strato con cui questo estrusore dovrebbe stampare, per stampanti i cui estrusori hanno ugelli di dimensioni diverse. Deve essere un multiplo intero dell'altezza strato dell'oggetto. Una parte le cui caratteristiche seguono tutte questo estrusore stampa solo ogni N strati con estrusioni corrispondentemente più spesse, dove la sua geometria lo consente; altrove torna all'altezza strato dell'oggetto. Quando il resto della parte non può seguirla, le pareti assegnate a questo estrusore si combinano comunque da sole a questa altezza, le superfici superiori a piena densità assorbono gli strati solidi sottostanti, e il riempimento sparso o denso al 100% si combina indipendentemente a questa altezza. 0 significa usare l'altezza strato dell'oggetto." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Altezza strato con cui questo estrusore dovrebbe stampare: un multiplo intero dell'altezza strato dell'oggetto entro i limiti di altezza strato di questo estrusore. Predefinito mantiene l'altezza strato dell'oggetto." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Nessun estrusore ha un ugello corrispondente al diametro ugello dei supporti." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Nessun filamento caricato corrisponde al materiale della base supporti/zattera (e al diametro ugello dei supporti)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Nessun filamento caricato corrisponde al materiale dell'interfaccia supporti/zattera (e al diametro ugello dei supporti)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Ugello %1%: limiti altezza strato impostati a %2%-%3% mm, da \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Sulle stampanti i cui estrusori hanno diametri ugello diversi, solo i filamenti di questo diametro ugello vengono usati per stampare supporti, zattera e interfaccia dei supporti. Questo tiene i filamenti di altre dimensioni ugello - con le loro diverse larghezze linea e limiti di altezza strato - fuori dai supporti. I filamenti di supporto impostati su un valore non predefinito devono corrispondere a questo diametro. Il valore 0 consente a qualsiasi filamento di stampare i supporti." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Le pareti esterne e interne stampano automaticamente alle proprie altezze strato preferite quando un'altezza è un multiplo intero dell'altra. Quando le altezze non si dividono esattamente, questa opzione regola l'altezza strato delle pareti di uno dei due filamenti parete (scelto sotto) al multiplo o divisore più vicino dell'altra, così le pareti possono ancora separarsi. L'altezza regolata si applica solo alle pareti di quel filamento; le altre caratteristiche mantengono l'altezza strato preferita. Le regolazioni non escono mai dai limiti di altezza strato del filamento: se nella direzione scelta non esiste un'altezza consentita, le pareti stampano insieme all'altezza inferiore come al solito." + +msgid "Outer walls" +msgstr "Pareti esterne" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Le altezze strato per estrusore non sono supportate in modalità vaso a spirale." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Le altezze strato per estrusore non sono supportate insieme ai gusci di interfaccia." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Le altezze strato per estrusore non sono supportate insieme all'altezza strato variabile." + +msgid "Preferred layer height" +msgstr "Altezza strato preferita" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Stampa la base dei supporti e la zattera solo con filamenti di questo tipo di materiale; gli estrusori caricati con altri tipi non vengono usati per essa. Si combina con la restrizione del diametro ugello dei supporti. Lasciare vuoto per nessuna restrizione; un filamento base supporti/zattera selezionato esplicitamente ha comunque la precedenza." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Stampa l'interfaccia dei supporti e della zattera solo con filamenti di questo tipo di materiale; gli estrusori caricati con altri tipi non vengono usati per essa. Si combina con la restrizione del diametro ugello dei supporti. Lasciare vuoto per nessuna restrizione; un filamento interfaccia supporti/zattera selezionato esplicitamente ha comunque la precedenza." + +msgid "Raft and support base" +msgstr "Zattera e base dei supporti" + +msgid "Show legacy filament selection" +msgstr "Mostra la selezione filamento precedente" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Alcune parti preferiscono strati di %1% mm, ma l'ugello del filamento %2% che stampa altre caratteristiche della parte è troppo piccolo per estrudere quell'altezza. Queste parti stampano invece con l'altezza strato dell'oggetto." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Alcune parti preferiscono strati di %1% mm, ma l'ugello del filamento parete %2% è troppo piccolo per estrudere quell'altezza. Le pareti esterne e interne stampano insieme, quindi queste pareti mantengono l'altezza strato dell'oggetto. Assegnare entrambe le caratteristiche parete a filamenti con ugelli abbastanza grandi." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Alcune parti stampano strati di %1% mm con il filamento %2%, la cui altezza strato massima è %3% mm. Assegnare le caratteristiche della parte ai filamenti dell'ugello più grosso, aumentare l'altezza strato massima del filamento, oppure accettare di stampare oltre." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Alcune parti stampano strati di %1% mm con il filamento %2%, la cui altezza strato minima è %3% mm. Aumentare l'altezza strato dell'oggetto, usare un filamento con un ugello più fine per queste caratteristiche, oppure accettare di stampare sotto il minimo dell'estrusore." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Alcune parti usano filamenti le cui altezze strato preferite non possono essere tutte rispettate: una parte stampa le sue caratteristiche con un solo passo di strato (fissato dai suoi filamenti parete o, quando nessun filamento parete ha una preferenza, dall'accordo delle altre caratteristiche); le pareti, le superfici superiori e il riempimento possono combinarsi ciascuno alla propria altezza quando il resto della parte non può seguirli, ma le caratteristiche rimanenti stampano con il passo della parte." + +msgid "Support for mixed nozzle sizes" +msgstr "Supporti con dimensioni ugello miste" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "I supporti potrebbero stampare con estrusori di diametri ugello diversi. Impostare il diametro ugello dei supporti (o filamenti espliciti di supporto e interfaccia) per mantenere i supporti su una sola dimensione ugello." + +msgid "Support nozzle diameter" +msgstr "Diametro ugello dei supporti" + +msgid "Support nozzle size" +msgstr "Dimensione ugello dei supporti" + +msgid "Support/raft base material" +msgstr "Materiale base supporti/zattera" + +msgid "Support/raft interface material" +msgstr "Materiale interfaccia supporti/zattera" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "La larghezza linea del riempimento di %1% mm è troppo piccola per riempimento combinato in strati alti %2% mm. Aumentare la larghezza linea o abbassare l'altezza strato preferita del filamento di riempimento." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "La larghezza linea di %1% mm è troppo piccola per l'altezza strato di %2% mm del suo estrusore. Aumentare la larghezza linea o abbassare l'altezza strato dell'estrusore." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "L'altezza strato dell'estrusore %1% (%2% mm) non può superare il suo diametro ugello." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "L'altezza strato dell'estrusore %1% (%2% mm) viene ignorata per alcune parti: deve essere un multiplo intero dell'altezza strato dell'oggetto (%3% mm), non inferiore, e non deve superare il diametro ugello dell'estrusore." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "L'altezza strato dell'estrusore %1% (%2% mm) è più piccola dell'altezza strato dell'oggetto (%3% mm). Abbassare l'altezza strato dell'oggetto all'altezza strato di estrusore più fine." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "L'altezza strato dell'estrusore %1% (%2% mm) deve essere un multiplo intero dell'altezza strato dell'oggetto (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "L'altezza strato stampabile più bassa dell'estrusore. Serve a limitare l'altezza strato minima quando l'altezza strato adattiva è attiva. Anche le parti stampate con un'altezza strato di estrusore preferita più spessa non scendono mai sotto questa altezza (eccetto il primo strato)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "L'altezza strato preferita dell'ugello %1% non passa più attraverso di esso ed è stata ripristinata a Predefinito." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Il filamento della base supporti/zattera non è del materiale della base supporti/zattera." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Il filamento della base supporti/zattera stampa con un ugello che non corrisponde al diametro ugello dei supporti." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Il filamento dell'interfaccia supporti/zattera non è del materiale dell'interfaccia supporti/zattera." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Il filamento dell'interfaccia supporti/zattera stampa con un ugello che non corrisponde al diametro ugello dei supporti." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "I filamenti parete di alcune parti preferiscono altezze strato diverse. Le pareti esterne e interne stampano insieme, quindi queste pareti mantengono l'altezza strato dell'oggetto. Assegnare entrambe le caratteristiche parete a filamenti che preferiscono la stessa altezza per stampare pareti più spesse." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "I filamenti parete di alcune parti preferiscono altezze strato diverse. Le pareti esterne e interne stampano insieme, quindi queste pareti stampano con l'altezza inferiore (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "L'altezza strato delle pareti del filamento %1% è stata regolata dai %2% mm preferiti a %3% mm perché le pareti esterne e interne possano stampare ad altezze strato compatibili (\"Regola l'altezza strato delle pareti\"). Solo le pareti di questo filamento stampano l'altezza regolata; le sue altre caratteristiche mantengono quella preferita." + +msgid "Thick layer regions" +msgstr "Regioni a strati spessi" + +msgid "Thick layer tolerance" +msgstr "Tolleranza strati spessi" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Questa stampante non ha un profilo per un ugello da %1% mm. Verificare i limiti di altezza strato dell'ugello %2% nelle impostazioni della stampante." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Questa stampante usa dimensioni ugello diverse. Selezionare la dimensione ugello che stampa i supporti e i tipi di filamento usati per la zattera e l'interfaccia dei supporti." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Se l'altezza strato delle pareti del filamento regolato viene diminuita o aumentata per raggiungere un'altezza compatibile con l'altro filamento parete. Le altezze fuori dai limiti di altezza strato del filamento regolato non vengono mai usate: se in questa direzione non esiste un'altezza consentita, le pareti stampano insieme all'altezza inferiore come al solito." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Quale dei due filamenti parete riceve la regolazione dell'altezza strato delle pareti quando le altezze strato preferite non si dividono esattamente." diff --git a/localization/i18n/ja/Snapmaker_Orca_ja.po b/localization/i18n/ja/Snapmaker_Orca_ja.po index dc5b113cbcf..057dd0f52c8 100644 --- a/localization/i18n/ja/Snapmaker_Orca_ja.po +++ b/localization/i18n/ja/Snapmaker_Orca_ja.po @@ -17727,4 +17727,220 @@ msgstr "" #~ "ると、造形失敗する可能性があります。" #~ msgid "Orient the model" -#~ msgstr "モデルの向きを調整" \ No newline at end of file +#~ msgstr "モデルの向きを調整" +msgid "Adjust wall layer height" +msgstr "ウォールの積層ピッチを調整" + +msgid "Adjusted walls" +msgstr "調整されるウォール" + +msgid "Adjustment direction" +msgstr "調整方向" + +msgid "Consistent" +msgstr "一貫" + +msgid "Decrease" +msgstr "下げる" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "底面を印刷するフィラメント。\n\"デフォルト\"はオブジェクト/パーツの現在のフィラメントを使用します。" + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "内壁を印刷するフィラメント。\n\"デフォルト\"はオブジェクト/パーツの現在のフィラメントを使用します。" + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "内部ソリッドインフィルを印刷するフィラメント。\n\"デフォルト\"はオブジェクト/パーツの現在のフィラメントを使用します。" + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "内部スパースインフィルを印刷するフィラメント。\n\"デフォルト\"はオブジェクト/パーツの現在のフィラメントを使用します。" + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "外壁を印刷するフィラメント。\n\"デフォルト\"はオブジェクト/パーツの現在のフィラメントを使用します。" + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "サポートとラフトを造形用のフィラメント。「デフォルト」では当時のフィラメントを使用する意味です。" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "サポートの接触面用のフィラメントです。「デフォルト」では指定しなく、当時のフィラメントを使用する意味です。" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "トップ面を印刷するフィラメント。\n\"デフォルト\"はオブジェクト/パーツの現在のフィラメントを使用します。" + +msgid "Fixed" +msgstr "固定" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "太い優先積層ピッチのエクストルーダーに割り当てられたパーツを、どの程度積極的に厚い層へ結合するかを決めます。\n一貫: パーツは最大2種類の積層ピッチで印刷されます。完全な層のまとまりが収まる場所ではエクストルーダーの積層ピッチ、それ以外ではオブジェクトの積層ピッチです。壁面が最も均一になります。\nアダプティブ: まとまりをオブジェクト積層ピッチの中間の倍数でも結合できるため、パーツのより多くの部分が厚い層で印刷されますが、曲面の境界に積層ピッチが変化する帯が現れます。\n固定: 結合された層の間で形状が変化したりオーバーハングしていても、パーツは常にエクストルーダーの積層ピッチで印刷されます。曲面の境界は段差になり、厚い層より細かいディテールは失われます。厚い層1つ分に満たない形状(パーツの頂部と最初の層)のみ薄く印刷されます。" + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "太い優先積層ピッチのエクストルーダーに割り当てられたパーツの輪郭が、1つの厚いまとまりの層間で横方向にどこまでずれても結合されるかを、そのエクストルーダーのノズル直径に対する割合で指定します。値を大きくすると曲面の境界がより多く厚い層に結合されますが、境界の壁面は粗くなります。ノズル直径のこの割合までのずれは太い押出に吸収されます。" + +msgid "Increase" +msgstr "上げる" + +msgid "Inner walls" +msgstr "内壁" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "このエクストルーダーが印刷すべき積層ピッチ。ノズルサイズが異なるエクストルーダーを持つプリンター用です。オブジェクトの積層ピッチの整数倍である必要があります。すべての形状がこのエクストルーダーに従うパーツは、ジオメトリが許す場所ではN層ごとにその分厚い押出で印刷され、それ以外ではオブジェクトの積層ピッチに戻ります。パーツの残りが従えない場合でも、このエクストルーダーに割り当てられたウォールは単独でこの高さに結合され、全密度のトップ面は直下のソリッド層を吸収し、スパースまたは100%密度のインフィルも独立してこの高さに結合されます。0はオブジェクトの積層ピッチを使用します。" + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "このエクストルーダーが印刷すべき積層ピッチ: このエクストルーダーの積層ピッチ制限内で、オブジェクトの積層ピッチの整数倍。デフォルトはオブジェクトの積層ピッチを維持します。" + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "サポートノズル直径に一致するノズルを持つエクストルーダーがありません。" + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "サポート/ラフトベースの材料(およびサポートノズル直径)に一致するフィラメントがロードされていません。" + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "サポート/ラフトインターフェイスの材料(およびサポートノズル直径)に一致するフィラメントがロードされていません。" + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "ノズル%1%: 積層ピッチの制限を%2%-%3% mmに設定しました(\"%4%\"より)。" + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "エクストルーダーのノズル直径が異なるプリンターでは、このノズル直径のフィラメントのみがサポート、ラフト、サポートインターフェイスの印刷に使用されます。これにより、線幅や積層ピッチ制限が異なる他のノズルサイズのフィラメントがサポートに使われなくなります。デフォルト以外に設定されたサポートフィラメントは、この直径に一致する必要があります。0はどのフィラメントでもサポートを印刷できます。" + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "一方の高さが他方の整数倍のとき、外壁と内壁は自動的にそれぞれの優先積層ピッチで印刷されます。高さが割り切れない場合、このオプションは2つのウォールフィラメントの一方(下で選択)のウォール積層ピッチを他方の最も近い倍数または約数に調整し、ウォールを分割できるようにします。調整された高さはそのフィラメントのウォールにのみ適用され、他の形状は優先積層ピッチを維持します。調整がフィラメントの積層ピッチ制限を超えることはありません。選んだ方向に許容される高さがない場合、ウォールは通常どおり低い方の高さで一緒に印刷されます。" + +msgid "Outer walls" +msgstr "外壁" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "エクストルーダーごとの積層ピッチはスパイラルモードでは使用できません。" + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "エクストルーダーごとの積層ピッチはインターフェイスシェルと併用できません。" + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "エクストルーダーごとの積層ピッチは可変積層ピッチと併用できません。" + +msgid "Preferred layer height" +msgstr "優先積層ピッチ" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "サポートとラフトベースをこの材料タイプのフィラメントのみで印刷します。他のタイプがロードされたエクストルーダーは使用されません。サポートノズル直径の制限と併せて機能します。空欄で制限なし。明示的に選択されたサポート/ラフトベースフィラメントが引き続き優先されます。" + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "サポートとラフトのインターフェイスをこの材料タイプのフィラメントのみで印刷します。他のタイプがロードされたエクストルーダーは使用されません。サポートノズル直径の制限と併せて機能します。空欄で制限なし。明示的に選択されたサポート/ラフトインターフェイスフィラメントが引き続き優先されます。" + +msgid "Raft and support base" +msgstr "ラフトとサポートベース" + +msgid "Show legacy filament selection" +msgstr "旧フィラメント選択を表示" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "一部のパーツは%1% mmの層を優先しますが、そのパーツの他の形状を印刷するフィラメント%2%のノズルが小さすぎてその高さを押し出せません。これらのパーツは代わりにオブジェクトの積層ピッチで印刷されます。" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "一部のパーツは%1% mmの層を優先しますが、ウォールフィラメント%2%のノズルが小さすぎてその高さを押し出せません。外壁と内壁は一緒に印刷されるため、これらのウォールはオブジェクトの積層ピッチを維持します。両方のウォールを十分大きいノズルのフィラメントに割り当ててください。" + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "一部のパーツはフィラメント%2%で%1% mmの層を印刷しますが、その最大積層ピッチは%3% mmです。パーツの形状を太いノズルのフィラメントに割り当てるか、フィラメントの最大積層ピッチを上げるか、それを超えて印刷することを受け入れてください。" + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "一部のパーツはフィラメント%2%で%1% mmの層を印刷しますが、その最小積層ピッチは%3% mmです。オブジェクトの積層ピッチを上げるか、これらの形状に細いノズルのフィラメントを使うか、エクストルーダーの最小値未満で印刷することを受け入れてください。" + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "一部のパーツで、フィラメントの優先積層ピッチをすべて満たすことができません。パーツはその形状を1つの層ピッチで印刷します(ウォールフィラメントが決めるか、ウォールフィラメントに優先がない場合は他の形状の合意で決まります)。パーツの残りが従えないとき、ウォール、トップ面、インフィルはそれぞれ独自の高さに結合できますが、残りの形状はパーツのピッチで印刷されます。" + +msgid "Support for mixed nozzle sizes" +msgstr "混合ノズルサイズのサポート" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "サポートが異なるノズル直径のエクストルーダーで印刷される可能性があります。サポートノズル直径(または明示的なサポート/インターフェイスフィラメント)を設定して、サポートを1つのノズルサイズに保ってください。" + +msgid "Support nozzle diameter" +msgstr "サポートノズル直径" + +msgid "Support nozzle size" +msgstr "サポートノズルサイズ" + +msgid "Support/raft base material" +msgstr "サポート/ラフトベース材料" + +msgid "Support/raft interface material" +msgstr "サポート/ラフトインターフェイス材料" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "%1% mmのインフィル線幅は、%2% mmの高さに結合されたインフィルには小さすぎます。線幅を増やすか、インフィルフィラメントの優先積層ピッチを下げてください。" + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "%1% mmの線幅は、そのエクストルーダーの%2% mmの積層ピッチには小さすぎます。線幅を増やすか、エクストルーダーの積層ピッチを下げてください。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "エクストルーダー%1%の積層ピッチ(%2% mm)はノズル直径を超えられません。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "エクストルーダー%1%の積層ピッチ(%2% mm)は一部のパーツで無視されます。オブジェクトの積層ピッチ(%3% mm)の整数倍かつそれ以上で、エクストルーダーのノズル直径を超えてはなりません。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "エクストルーダー%1%の積層ピッチ(%2% mm)がオブジェクトの積層ピッチ(%3% mm)より小さいです。オブジェクトの積層ピッチを最も細かいエクストルーダー積層ピッチまで下げてください。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "エクストルーダー%1%の積層ピッチ(%2% mm)はオブジェクトの積層ピッチ(%3% mm)の整数倍である必要があります。" + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "このエクストルーダーで印刷可能な最低積層ピッチ。アダプティブ積層ピッチ有効時の最小積層ピッチの制限に使われます。太い優先エクストルーダー積層ピッチで印刷されるパーツも、この高さ未満には決して戻りません(最初の層を除く)。" + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "ノズル%1%の優先積層ピッチがノズルを通らなくなったため、デフォルトにリセットされました。" + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "サポート/ラフトベースフィラメントがサポート/ラフトベース材料ではありません。" + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "サポート/ラフトベースフィラメントはサポートノズル直径に一致しないノズルで印刷されます。" + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "サポート/ラフトインターフェイスフィラメントがサポート/ラフトインターフェイス材料ではありません。" + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "サポート/ラフトインターフェイスフィラメントはサポートノズル直径に一致しないノズルで印刷されます。" + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "一部のパーツのウォールフィラメントは異なる積層ピッチを優先しています。外壁と内壁は一緒に印刷されるため、これらのウォールはオブジェクトの積層ピッチを維持します。厚いウォールを印刷するには、両方のウォールを同じ高さを優先するフィラメントに割り当ててください。" + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "一部のパーツのウォールフィラメントは異なる積層ピッチを優先しています。外壁と内壁は一緒に印刷されるため、これらのウォールは低い方の高さ(%1% mm)で印刷されます。" + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "フィラメント%1%のウォール積層ピッチが、外壁と内壁が互換性のある積層ピッチで印刷できるよう、優先の%2% mmから%3% mmに調整されました(\"ウォールの積層ピッチを調整\")。調整された高さで印刷されるのはこのフィラメントのウォールだけで、他の形状は優先積層ピッチを維持します。" + +msgid "Thick layer regions" +msgstr "厚い層の領域" + +msgid "Thick layer tolerance" +msgstr "厚い層の許容差" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "このプリンターには%1% mmノズルのプロファイルがありません。プリンター設定でノズル%2%の積層ピッチ制限を確認してください。" + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "このプリンターは異なるノズルサイズを使用しています。サポートを印刷するノズルサイズと、ラフトおよびサポートインターフェイスに使うフィラメントタイプを選択してください。" + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "調整されるウォールフィラメントのウォール積層ピッチを、他方のウォールフィラメントと互換性のある高さへ下げるか上げるか。調整されるフィラメントの積層ピッチ制限外の高さは決して使用されません。この方向に許容される高さがない場合、ウォールは通常どおり低い方の高さで一緒に印刷されます。" + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "優先積層ピッチが割り切れないとき、2つのウォールフィラメントのどちらのウォール積層ピッチを調整するか。" diff --git a/localization/i18n/ko/Snapmaker_Orca_ko.po b/localization/i18n/ko/Snapmaker_Orca_ko.po index 9d02bec2c92..4f346e94a85 100644 --- a/localization/i18n/ko/Snapmaker_Orca_ko.po +++ b/localization/i18n/ko/Snapmaker_Orca_ko.po @@ -20143,4 +20143,220 @@ msgstr "" #~ msgstr "" #~ "유효한 값을 입력하십시오:\n" #~ "시작 > 10 단계 >= 0\n" -#~ "끝 > 시작 + 단계)" \ No newline at end of file +#~ "끝 > 시작 + 단계)" +msgid "Adjust wall layer height" +msgstr "벽 레이어 높이 조정" + +msgid "Adjusted walls" +msgstr "조정되는 벽" + +msgid "Adjustment direction" +msgstr "조정 방향" + +msgid "Consistent" +msgstr "일관" + +msgid "Decrease" +msgstr "낮추기" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "하단 표면을 출력할 필라멘트.\n\"기본값\"은 개체/부품의 현재 필라멘트를 사용합니다." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "내벽을 출력할 필라멘트.\n\"기본값\"은 개체/부품의 현재 필라멘트를 사용합니다." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "내부 솔리드 채우기를 출력할 필라멘트.\n\"기본값\"은 개체/부품의 현재 필라멘트를 사용합니다." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "내부 드문 채우기를 출력할 필라멘트.\n\"기본값\"은 개체/부품의 현재 필라멘트를 사용합니다." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "외벽을 출력할 필라멘트.\n\"기본값\"은 개체/부품의 현재 필라멘트를 사용합니다." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "기본 서포트 및 라프트를 출력하기 위한 필라멘트. \"기본값\"은 지원을 위한 특정 필라멘트가 없으며 현재 필라멘트가 사용됨을 의미합니다" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "서포트 및 라프트 접점을 출력하기 위한 필라멘트. \"기본값\"은 지원을 위한 특정 필라멘트가 없으며 현재 필라멘트가 사용됨을 의미합니다" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "상단 표면을 출력할 필라멘트.\n\"기본값\"은 개체/부품의 현재 필라멘트를 사용합니다." + +msgid "Fixed" +msgstr "고정" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "더 두꺼운 선호 레이어 높이의 압출기에 할당된 부품을 얼마나 적극적으로 두꺼운 레이어로 결합할지 정합니다.\n일관: 부품은 최대 두 가지 레이어 높이로 출력됩니다. 완전한 레이어 묶음이 들어가는 곳은 압출기 레이어 높이, 그 외에는 개체 레이어 높이입니다. 벽이 가장 균일해집니다.\n적응형: 묶음을 개체 레이어 높이의 중간 배수로도 결합할 수 있어 부품의 더 많은 부분이 두꺼운 레이어로 출력되지만, 곡면 경계에 레이어 높이가 변하는 띠가 생깁니다.\n고정: 결합된 레이어 사이에서 형상이 변하거나 오버행이 있어도 부품은 항상 압출기 레이어 높이로 출력됩니다. 곡면 경계는 계단이 되고 두꺼운 레이어보다 미세한 디테일은 사라집니다. 두꺼운 레이어 하나에 못 미치는 형상(부품 상단과 첫 레이어)만 더 얇게 출력됩니다." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "더 두꺼운 선호 레이어 높이의 압출기에 할당된 부품의 윤곽이 하나의 두꺼운 묶음의 레이어들 사이에서 옆으로 얼마나 이동해도 결합될 수 있는지를 해당 압출기 노즐 직경의 백분율로 지정합니다. 값이 클수록 곡면 경계가 더 많이 두꺼운 레이어로 결합되지만 경계 벽은 거칠어집니다. 노즐 직경의 이 비율까지의 편차는 두꺼운 압출선에 흡수됩니다." + +msgid "Increase" +msgstr "높이기" + +msgid "Inner walls" +msgstr "내벽" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "이 압출기가 출력해야 하는 레이어 높이로, 압출기마다 노즐 크기가 다른 프린터용입니다. 개체 레이어 높이의 정수배여야 합니다. 모든 피처가 이 압출기를 따르는 부품은 형상이 허용하는 곳에서 N번째 레이어마다 그만큼 두꺼운 압출로 출력되며, 그 외에는 개체 레이어 높이로 돌아갑니다. 부품의 나머지가 따라갈 수 없으면 이 압출기에 할당된 벽은 그래도 스스로 이 높이로 결합되고, 전체 밀도의 상단 표면은 그 아래 솔리드 레이어를 흡수하며, 드문 채우기나 100% 밀도 채우기도 독립적으로 이 높이로 결합됩니다. 0은 개체 레이어 높이를 사용합니다." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "이 압출기가 출력해야 하는 레이어 높이: 이 압출기의 레이어 높이 한도 내에서 개체 레이어 높이의 정수배. 기본값은 개체 레이어 높이를 유지합니다." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "서포트 노즐 직경과 일치하는 노즐을 가진 압출기가 없습니다." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "로드된 필라멘트 중 서포트/라프트 베이스 재료(및 서포트 노즐 직경)와 일치하는 것이 없습니다." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "로드된 필라멘트 중 서포트/라프트 인터페이스 재료(및 서포트 노즐 직경)와 일치하는 것이 없습니다." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "노즐 %1%: 레이어 높이 한도가 \"%4%\"에서 %2%-%3% mm로 설정되었습니다." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "압출기마다 노즐 직경이 다른 프린터에서는 이 노즐 직경의 필라멘트만 서포트, 라프트, 서포트 인터페이스 출력에 사용됩니다. 선 너비와 레이어 높이 한도가 다른 다른 노즐 크기의 필라멘트를 서포트에서 배제합니다. 기본값이 아닌 값으로 설정된 서포트 필라멘트는 이 직경과 일치해야 합니다. 0이면 모든 필라멘트가 서포트를 출력할 수 있습니다." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "한쪽 높이가 다른 쪽의 정수배이면 외벽과 내벽은 자동으로 각자의 선호 레이어 높이로 출력됩니다. 높이가 나누어떨어지지 않으면 이 옵션이 두 벽 필라멘트 중 하나(아래에서 선택)의 벽 레이어 높이를 다른 쪽의 가장 가까운 배수나 약수로 조정하여 벽이 여전히 분리될 수 있게 합니다. 조정된 높이는 그 필라멘트의 벽에만 적용되며, 다른 피처는 선호 레이어 높이를 유지합니다. 조정은 필라멘트의 레이어 높이 한도를 절대 벗어나지 않습니다. 선택한 방향에 허용되는 높이가 없으면 벽은 평소처럼 더 낮은 높이로 함께 출력됩니다." + +msgid "Outer walls" +msgstr "외벽" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "압출기별 레이어 높이는 나선형 꽃병 모드에서 지원되지 않습니다." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "압출기별 레이어 높이는 인터페이스 셸과 함께 지원되지 않습니다." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "압출기별 레이어 높이는 가변 레이어 높이와 함께 지원되지 않습니다." + +msgid "Preferred layer height" +msgstr "선호 레이어 높이" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "서포트와 라프트 베이스를 이 재료 유형의 필라멘트로만 출력합니다. 다른 유형이 로드된 압출기는 사용되지 않습니다. 서포트 노즐 직경 제한과 함께 작동합니다. 비워 두면 제한이 없습니다. 명시적으로 선택한 서포트/라프트 베이스 필라멘트가 여전히 우선합니다." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "서포트와 라프트 인터페이스를 이 재료 유형의 필라멘트로만 출력합니다. 다른 유형이 로드된 압출기는 사용되지 않습니다. 서포트 노즐 직경 제한과 함께 작동합니다. 비워 두면 제한이 없습니다. 명시적으로 선택한 서포트/라프트 인터페이스 필라멘트가 여전히 우선합니다." + +msgid "Raft and support base" +msgstr "라프트 및 서포트 베이스" + +msgid "Show legacy filament selection" +msgstr "이전 필라멘트 선택 표시" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "일부 부품은 %1% mm 레이어를 선호하지만, 그 부품의 다른 피처를 출력하는 필라멘트 %2%의 노즐이 너무 작아 그 높이를 압출할 수 없습니다. 이 부품들은 대신 개체 레이어 높이로 출력됩니다." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "일부 부품은 %1% mm 레이어를 선호하지만, 벽 필라멘트 %2%의 노즐이 너무 작아 그 높이를 압출할 수 없습니다. 외벽과 내벽은 함께 출력되므로 이 벽들은 개체 레이어 높이를 유지합니다. 두 벽 피처 모두 충분히 큰 노즐의 필라멘트에 할당하세요." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "일부 부품이 최대 레이어 높이가 %3% mm인 필라멘트 %2%로 %1% mm 레이어를 출력합니다. 부품 피처를 더 굵은 노즐의 필라멘트에 할당하거나, 필라멘트의 최대 레이어 높이를 올리거나, 초과 출력을 감수하세요." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "일부 부품이 최소 레이어 높이가 %3% mm인 필라멘트 %2%로 %1% mm 레이어를 출력합니다. 개체 레이어 높이를 올리거나, 이 피처들에 더 가는 노즐의 필라멘트를 쓰거나, 압출기 최소값 아래 출력을 감수하세요." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "일부 부품이 사용하는 필라멘트들의 선호 레이어 높이를 모두 만족할 수 없습니다. 부품은 하나의 레이어 피치로 피처를 출력합니다(벽 필라멘트가 정하며, 벽 필라멘트에 선호가 없으면 나머지 피처의 합의로 정해집니다). 부품의 나머지가 따라갈 수 없을 때 벽, 상단 표면, 채우기는 각자 자기 높이로 결합할 수 있지만, 나머지 피처는 부품의 피치로 출력됩니다." + +msgid "Support for mixed nozzle sizes" +msgstr "혼합 노즐 크기의 서포트" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "서포트가 서로 다른 노즐 직경의 압출기로 출력될 수 있습니다. 서포트 노즐 직경(또는 명시적인 서포트 및 인터페이스 필라멘트)을 설정해 서포트를 하나의 노즐 크기로 유지하세요." + +msgid "Support nozzle diameter" +msgstr "서포트 노즐 직경" + +msgid "Support nozzle size" +msgstr "서포트 노즐 크기" + +msgid "Support/raft base material" +msgstr "서포트/라프트 베이스 재료" + +msgid "Support/raft interface material" +msgstr "서포트/라프트 인터페이스 재료" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "%1% mm의 채우기 선 너비는 %2% mm 높이로 결합된 채우기에 너무 작습니다. 선 너비를 늘리거나 채우기 필라멘트의 선호 레이어 높이를 낮추세요." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "%1% mm의 선 너비는 해당 압출기의 %2% mm 레이어 높이에 너무 작습니다. 선 너비를 늘리거나 압출기 레이어 높이를 낮추세요." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "압출기 %1%의 레이어 높이(%2% mm)는 노즐 직경을 초과할 수 없습니다." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "압출기 %1%의 레이어 높이(%2% mm)는 일부 부품에서 무시됩니다. 개체 레이어 높이(%3% mm)의 정수배이면서 그보다 작지 않아야 하고, 압출기의 노즐 직경을 초과해서는 안 됩니다." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "압출기 %1%의 레이어 높이(%2% mm)가 개체 레이어 높이(%3% mm)보다 작습니다. 개체 레이어 높이를 가장 가는 압출기 레이어 높이로 낮추세요." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "압출기 %1%의 레이어 높이(%2% mm)는 개체 레이어 높이(%3% mm)의 정수배여야 합니다." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "이 압출기로 출력할 수 있는 최저 레이어 높이. 적응형 레이어 높이 사용 시 최소 레이어 높이를 제한하는 데 쓰입니다. 더 두꺼운 선호 압출기 레이어 높이로 출력되는 부품도 이 높이 아래로는 절대 내려가지 않습니다(첫 레이어 제외)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "노즐 %1%의 선호 레이어 높이가 더 이상 노즐을 통과하지 못해 기본값으로 재설정되었습니다." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "서포트/라프트 베이스 필라멘트가 서포트/라프트 베이스 재료가 아닙니다." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "서포트/라프트 베이스 필라멘트가 서포트 노즐 직경과 일치하지 않는 노즐로 출력됩니다." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "서포트/라프트 인터페이스 필라멘트가 서포트/라프트 인터페이스 재료가 아닙니다." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "서포트/라프트 인터페이스 필라멘트가 서포트 노즐 직경과 일치하지 않는 노즐로 출력됩니다." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "일부 부품의 벽 필라멘트가 서로 다른 레이어 높이를 선호합니다. 외벽과 내벽은 함께 출력되므로 이 벽들은 개체 레이어 높이를 유지합니다. 더 두꺼운 벽을 출력하려면 두 벽 피처를 같은 높이를 선호하는 필라멘트에 할당하세요." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "일부 부품의 벽 필라멘트가 서로 다른 레이어 높이를 선호합니다. 외벽과 내벽은 함께 출력되므로 이 벽들은 더 낮은 높이(%1% mm)로 출력됩니다." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "필라멘트 %1%의 벽 레이어 높이가 외벽과 내벽이 호환되는 레이어 높이로 출력될 수 있도록 선호값 %2% mm에서 %3% mm로 조정되었습니다(\"벽 레이어 높이 조정\"). 이 필라멘트의 벽만 조정된 높이로 출력되며, 다른 피처는 선호값을 유지합니다." + +msgid "Thick layer regions" +msgstr "두꺼운 레이어 영역" + +msgid "Thick layer tolerance" +msgstr "두꺼운 레이어 허용 오차" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "이 프린터에는 %1% mm 노즐용 프로파일이 없습니다. 프린터 설정에서 노즐 %2%의 레이어 높이 한도를 확인하세요." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "이 프린터는 서로 다른 노즐 크기를 사용합니다. 서포트를 출력할 노즐 크기와 라프트 및 서포트 인터페이스에 사용할 필라멘트 유형을 선택하세요." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "조정되는 벽 필라멘트의 벽 레이어 높이를 낮출지 높일지를 정해 다른 벽 필라멘트와 호환되는 높이에 도달합니다. 조정되는 필라멘트의 레이어 높이 한도를 벗어나는 높이는 절대 사용되지 않습니다. 이 방향에 허용되는 높이가 없으면 벽은 평소처럼 더 낮은 높이로 함께 출력됩니다." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "선호 레이어 높이가 나누어떨어지지 않을 때 두 벽 필라멘트 중 어느 쪽의 벽 레이어 높이를 조정할지 정합니다." diff --git a/localization/i18n/lt/Snapmaker_Orca_lt.po b/localization/i18n/lt/Snapmaker_Orca_lt.po index 85c8dcbb56a..7c0ce24cc97 100644 --- a/localization/i18n/lt/Snapmaker_Orca_lt.po +++ b/localization/i18n/lt/Snapmaker_Orca_lt.po @@ -19746,4 +19746,220 @@ msgstr "" #, c-format, boost-format #~ msgid "Support: propagate branches at layer %d" -#~ msgstr "Atramos: skleisti šakas sluoksnyje %d" \ No newline at end of file +#~ msgstr "Atramos: skleisti šakas sluoksnyje %d" +msgid "Adjust wall layer height" +msgstr "Koreguoti sienų sluoksnio aukštį" + +msgid "Adjusted walls" +msgstr "Koreguojamos sienos" + +msgid "Adjustment direction" +msgstr "Koregavimo kryptis" + +msgid "Consistent" +msgstr "Vientisas" + +msgid "Decrease" +msgstr "Mažinti" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Gija apatiniam paviršiui spausdinti.\n\"Numatytas\" naudoja aktyvią objekto/dalies giją." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Gija vidinėms sienoms spausdinti.\n\"Numatytas\" naudoja aktyvią objekto/dalies giją." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Gija vidiniam vientisam užpildymui spausdinti.\n\"Numatytas\" naudoja aktyvią objekto/dalies giją." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Gija vidiniam retam užpildymui spausdinti.\n\"Numatytas\" naudoja aktyvią objekto/dalies giją." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Gija išorinėms sienoms spausdinti.\n\"Numatytas\" naudoja aktyvią objekto/dalies giją." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Gija, skirta spausdinti atraminį pagrindą ir platformą. \"Numatytoji\" reiškia, kad nėra konkrečios gijos atramai ir naudojama dabartinė gija." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Gija skirta spausdinti atramos sąsają. \"Numatytoji\" reiškia, kad atramos sąsajai nėra konkrečios gijos ir naudojama dabartinė gija." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Gija viršutiniam paviršiui spausdinti.\n\"Numatytas\" naudoja aktyvią objekto/dalies giją." + +msgid "Fixed" +msgstr "Fiksuotas" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Kaip agresyviai dalys, priskirtos ekstruderiui su storesniu pageidaujamu sluoksnio aukščiu, jungiamos į storus sluoksnius.\nVientisas: dalys spausdinamos daugiausiai dviem sluoksnio aukščiais - ekstruderio sluoksnio aukščiu ten, kur telpa ištisos sluoksnių serijos, ir objekto sluoksnio aukščiu visur kitur. Tai duoda lygiausias sienas.\nPrisitaikantis: serijos gali būti jungiamos ir tarpiniais objekto sluoksnio aukščio kartotiniais, todėl didesnė dalies dalis spausdinama storesniais sluoksniais - kreivose dalių ribose atsiranda kintamo sluoksnio aukščio juostos.\nFiksuotas: dalys visada spausdinamos ekstruderio sluoksnio aukščiu, net kur forma keičiasi per sujungtus sluoksnius ar kabo; kreivos ribos virsta laipteliais, o smulkesnės nei stori sluoksniai detalės prarandamos. Tik geometrija, per trumpa visam storam sluoksniui (dalių viršūnės ir pirmas sluoksnis), spausdinama ploniau." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Kiek dalies, priskirtos ekstruderiui su storesniu pageidaujamu sluoksnio aukščiu, kontūras gali pasislinkti į šoną per vienos storos serijos sluoksnius ir vis tiek būti sujungtas, procentais nuo to ekstruderio purkštuko skersmens. Didesnės reikšmės sujungia daugiau kreivų ribų į storus sluoksnius, bet ribų sienos tampa grubesnės: nuokrypius iki šios purkštuko skersmens dalies praryja storos linijos." + +msgid "Increase" +msgstr "Didinti" + +msgid "Inner walls" +msgstr "Vidinės sienos" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Sluoksnio aukštis, kuriuo turėtų spausdinti šis ekstruderis; spausdintuvams, kurių ekstruderių purkštukų dydžiai skiriasi. Jis turi būti sveikasis objekto sluoksnio aukščio kartotinis. Dalis, kurios visi elementai seka šį ekstruderį, spausdinama tik kas N-tą sluoksnį atitinkamai storesnėmis linijomis ten, kur leidžia geometrija; kitur grįžtama prie objekto sluoksnio aukščio. Kai likusi dalies dalis negali sekti, šiam ekstruderiui priskirtos sienos vis tiek savarankiškai susijungia iki šio aukščio, visiško tankio viršutiniai paviršiai sugeria po jais esančius vientisus sluoksnius, o retas ar 100% tankio užpildymas susijungia iki šio aukščio nepriklausomai. 0 reiškia naudoti objekto sluoksnio aukštį." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Sluoksnio aukštis, kuriuo turėtų spausdinti šis ekstruderis: sveikasis objekto sluoksnio aukščio kartotinis šio ekstruderio sluoksnio aukščio ribose. Numatytas išlaiko objekto sluoksnio aukštį." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Nė vienas ekstruderis neturi purkštuko, atitinkančio atramų purkštuko skersmenį." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Nė viena įdėta gija neatitinka atramų/platformos pagrindo medžiagos (ir atramų purkštuko skersmens)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Nė viena įdėta gija neatitinka atramų/platformos sąsajos medžiagos (ir atramų purkštuko skersmens)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Purkštukas %1%: sluoksnio aukščio ribos nustatytos į %2%-%3% mm iš \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Spausdintuvuose, kurių ekstruderių purkštukų skersmenys skiriasi, atramoms, platformai ir atramų sąsajai spausdinti naudojamos tik šio purkštuko skersmens gijos. Tai neleidžia kitų purkštukų dydžių gijoms - su kitokiais linijų pločiais ir sluoksnio aukščio ribomis - patekti į atramas. Atramų gijos, nustatytos ne į numatytą reikšmę, turi atitikti šį skersmenį. Reikšmė 0 leidžia atramas spausdinti bet kuria gija." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Išorinės ir vidinės sienos automatiškai spausdinamos savo pageidaujamais sluoksnių aukščiais, kai vienas aukštis yra sveikasis kito kartotinis. Kai aukščiai nesidalija lygiai, ši parinktis koreguoja vienos iš dviejų sienų gijų (pasirenkamos žemiau) sienų sluoksnio aukštį iki artimiausio kito kartotinio ar daliklio, kad sienos vis tiek galėtų atsiskirti. Koreguotas aukštis taikomas tik tos gijos sienoms; kiti elementai išlaiko pageidaujamą sluoksnio aukštį. Koregavimai niekada neperžengia gijos sluoksnio aukščio ribų: jei pasirinkta kryptimi leidžiamo aukščio nėra, sienos kaip įprasta spausdinamos kartu žemesniu aukščiu." + +msgid "Outer walls" +msgstr "Išorinės sienos" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Sluoksnių aukščiai atskiriems ekstruderiams nepalaikomi spiralinės vazos režime." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Sluoksnių aukščiai atskiriems ekstruderiams nepalaikomi kartu su sąsajos apvalkalais." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Sluoksnių aukščiai atskiriems ekstruderiams nepalaikomi kartu su kintamu sluoksnio aukščiu." + +msgid "Preferred layer height" +msgstr "Pageidaujamas sluoksnio aukštis" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Atramas ir platformos pagrindą spausdinti tik šio tipo medžiagos gijomis; ekstruderiai su kitų tipų gijomis tam nenaudojami. Veikia kartu su atramų purkštuko skersmens apribojimu. Palikite tuščią, jei riboti nereikia; aiškiai pasirinkta atramų/platformos pagrindo gija vis tiek turi pirmenybę." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Atramų ir platformos sąsają spausdinti tik šio tipo medžiagos gijomis; ekstruderiai su kitų tipų gijomis tam nenaudojami. Veikia kartu su atramų purkštuko skersmens apribojimu. Palikite tuščią, jei riboti nereikia; aiškiai pasirinkta atramų/platformos sąsajos gija vis tiek turi pirmenybę." + +msgid "Raft and support base" +msgstr "Platforma ir atramų pagrindas" + +msgid "Show legacy filament selection" +msgstr "Rodyti seną gijos pasirinkimą" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Kai kurios dalys pageidauja %1% mm sluoksnių, bet gijos %2%, spausdinančios kitus dalies elementus, purkštukas per mažas tokiam aukščiui išspausti. Šios dalys spausdinamos objekto sluoksnio aukščiu." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Kai kurios dalys pageidauja %1% mm sluoksnių, bet sienų gijos %2% purkštukas per mažas tokiam aukščiui išspausti. Išorinės ir vidinės sienos spausdinamos kartu, todėl šios sienos išlaiko objekto sluoksnio aukštį. Priskirkite abu sienų elementus gijoms su pakankamai dideliais purkštukais." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Kai kurios dalys spausdina %1% mm sluoksnius gija %2%, kurios didžiausias sluoksnio aukštis yra %3% mm. Priskirkite dalies elementus storesnio purkštuko gijoms, padidinkite gijos didžiausią sluoksnio aukštį arba sutikite spausdinti virš jo." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Kai kurios dalys spausdina %1% mm sluoksnius gija %2%, kurios mažiausias sluoksnio aukštis yra %3% mm. Padidinkite objekto sluoksnio aukštį, šiems elementams naudokite giją su plonesniu purkštuku arba sutikite spausdinti žemiau ekstruderio minimumo." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Kai kurios dalys naudoja gijas, kurių pageidaujamų sluoksnių aukščių neįmanoma patenkinti visų: dalis savo elementus spausdina vienu sluoksnio žingsniu (jį nustato sienų gijos, o kai nė viena sienų gija neturi pageidavimo - kitų elementų sutarimas); sienos, viršutiniai paviršiai ir užpildymas gali kiekvienas susijungti iki savo aukščio, kai likusi dalies dalis negali jų sekti, bet likę elementai spausdinami dalies žingsniu." + +msgid "Support for mixed nozzle sizes" +msgstr "Atramos mišriems purkštukų dydžiams" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Atramos gali būti spausdinamos skirtingų purkštukų skersmenų ekstruderiais. Nustatykite atramų purkštuko skersmenį (arba aiškias atramų ir sąsajos gijas), kad atramos liktų vieno purkštuko dydžio." + +msgid "Support nozzle diameter" +msgstr "Atramų purkštuko skersmuo" + +msgid "Support nozzle size" +msgstr "Atramų purkštuko dydis" + +msgid "Support/raft base material" +msgstr "Atramų/platformos pagrindo medžiaga" + +msgid "Support/raft interface material" +msgstr "Atramų/platformos sąsajos medžiaga" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "%1% mm užpildymo linijos plotis per mažas užpildymui, sujungtam į %2% mm aukščio sluoksnius. Padidinkite linijos plotį arba sumažinkite užpildymo gijos pageidaujamą sluoksnio aukštį." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "%1% mm linijos plotis per mažas jo ekstruderio %2% mm sluoksnio aukščiui. Padidinkite linijos plotį arba sumažinkite ekstruderio sluoksnio aukštį." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Ekstruderio %1% sluoksnio aukštis (%2% mm) negali viršyti jo purkštuko skersmens." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Ekstruderio %1% sluoksnio aukštis (%2% mm) kai kurioms dalims ignoruojamas: jis turi būti sveikasis objekto sluoksnio aukščio (%3% mm) kartotinis, ne mažesnis už jį ir neviršyti ekstruderio purkštuko skersmens." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Ekstruderio %1% sluoksnio aukštis (%2% mm) mažesnis už objekto sluoksnio aukštį (%3% mm). Sumažinkite objekto sluoksnio aukštį iki ploniausio ekstruderio sluoksnio aukščio." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Ekstruderio %1% sluoksnio aukštis (%2% mm) turi būti sveikasis objekto sluoksnio aukščio (%3% mm) kartotinis." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Žemiausias ekstruderio spausdinamas sluoksnio aukštis. Riboja mažiausią sluoksnio aukštį, kai įjungtas prisitaikantis sluoksnio aukštis. Dalys, spausdinamos storesniu pageidaujamu ekstruderio sluoksnio aukščiu, taip pat niekada nenusileidžia žemiau šio aukščio (išskyrus pirmą sluoksnį)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Purkštuko %1% pageidaujamas sluoksnio aukštis nebepraeina pro jį ir buvo atstatytas į Numatytą." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Atramų/platformos pagrindo gija nėra atramų/platformos pagrindo medžiagos." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Atramų/platformos pagrindo gija spausdina purkštuku, neatitinkančiu atramų purkštuko skersmens." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Atramų/platformos sąsajos gija nėra atramų/platformos sąsajos medžiagos." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Atramų/platformos sąsajos gija spausdina purkštuku, neatitinkančiu atramų purkštuko skersmens." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Kai kurių dalių sienų gijos pageidauja skirtingų sluoksnių aukščių. Išorinės ir vidinės sienos spausdinamos kartu, todėl šios sienos išlaiko objekto sluoksnio aukštį. Norėdami spausdinti storesnes sienas, priskirkite abu sienų elementus gijoms, pageidaujančioms to paties aukščio." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Kai kurių dalių sienų gijos pageidauja skirtingų sluoksnių aukščių. Išorinės ir vidinės sienos spausdinamos kartu, todėl šios sienos spausdinamos žemesniu aukščiu (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Gijos %1% sienų sluoksnio aukštis pakoreguotas iš pageidaujamų %2% mm į %3% mm, kad išorinės ir vidinės sienos galėtų spausdintis suderinamais sluoksnių aukščiais (\"Koreguoti sienų sluoksnio aukštį\"). Koreguotu aukščiu spausdinamos tik šios gijos sienos; kiti jos elementai išlaiko pageidaujamą." + +msgid "Thick layer regions" +msgstr "Storų sluoksnių sritys" + +msgid "Thick layer tolerance" +msgstr "Storų sluoksnių tolerancija" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Šis spausdintuvas neturi profilio %1% mm purkštukui. Patikrinkite purkštuko %2% sluoksnio aukščio ribas spausdintuvo nustatymuose." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Šis spausdintuvas naudoja skirtingus purkštukų dydžius. Pasirinkite purkštuko dydį atramoms spausdinti ir gijų tipus platformai bei atramų sąsajai." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Ar koreguojamos sienų gijos sienų sluoksnio aukštis mažinamas, ar didinamas, kad pasiektų su kita sienų gija suderinamą aukštį. Aukščiai už koreguojamos gijos sluoksnio aukščio ribų niekada nenaudojami: jei šia kryptimi leidžiamo aukščio nėra, sienos kaip įprasta spausdinamos kartu žemesniu aukščiu." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Kurios iš dviejų sienų gijų sienų sluoksnio aukštis koreguojamas, kai pageidaujami sluoksnių aukščiai nesidalija lygiai." diff --git a/localization/i18n/nl/Snapmaker_Orca_nl.po b/localization/i18n/nl/Snapmaker_Orca_nl.po index 64a4ba2c47a..ebac04e2fde 100644 --- a/localization/i18n/nl/Snapmaker_Orca_nl.po +++ b/localization/i18n/nl/Snapmaker_Orca_nl.po @@ -18696,4 +18696,220 @@ msgstr "" #~ "van de bouwplaat." #~ msgid "Orient the model" -#~ msgstr "Oriënteer het model" \ No newline at end of file +#~ msgstr "Oriënteer het model" +msgid "Adjust wall layer height" +msgstr "Laaghoogte van wanden aanpassen" + +msgid "Adjusted walls" +msgstr "Aangepaste wanden" + +msgid "Adjustment direction" +msgstr "Aanpassingsrichting" + +msgid "Consistent" +msgstr "Consistent" + +msgid "Decrease" +msgstr "Verlagen" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament voor het bodemoppervlak.\n\"Standaard\" gebruikt het actieve filament van het object/onderdeel." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament voor de binnenste wanden.\n\"Standaard\" gebruikt het actieve filament van het object/onderdeel." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament voor de interne massieve vulling.\n\"Standaard\" gebruikt het actieve filament van het object/onderdeel." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament voor de interne dunne vulling.\n\"Standaard\" gebruikt het actieve filament van het object/onderdeel." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament voor de buitenste wanden.\n\"Standaard\" gebruikt het actieve filament van het object/onderdeel." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament voor het printen van ondersteuning (support) en raft. \"Standaard\" betekent geen specifiek filament voor ondersteuning (support) en het huidige filament wordt gebruikt." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament om ondersteuning (support) te printen. \"Standaard\" betekent geen specifiek filament voor ondersteuning (support), en het huidige filament wordt gebruikt." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament voor het bovenvlak.\n\"Standaard\" gebruikt het actieve filament van het object/onderdeel." + +msgid "Fixed" +msgstr "Vast" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Hoe agressief onderdelen die zijn toegewezen aan een extruder met een dikkere voorkeurslaaghoogte worden gecombineerd tot dikke lagen.\nConsistent: onderdelen printen met hoogstens twee laaghoogtes - de extruderlaaghoogte waar hele reeksen lagen passen en de objectlaaghoogte overal elders. Dit geeft de meest gelijkmatige wanden.\nAdaptief: reeksen mogen ook worden gecombineerd op tussenliggende veelvouden van de objectlaaghoogte, zodat meer van het onderdeel met dikkere lagen print, ten koste van banden met wisselende laaghoogtes op gebogen randen.\nVast: onderdelen printen altijd op de extruderlaaghoogte, ook waar de vorm over de gecombineerde lagen verandert of overhangt; gebogen randen worden trappen en details fijner dan de dikke lagen gaan verloren. Alleen geometrie die te kort is voor een hele dikke laag (bovenkanten van onderdelen en de eerste laag) print dunner." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Hoe ver de omtrek van een onderdeel dat is toegewezen aan een extruder met een dikkere voorkeurslaaghoogte zijwaarts mag verschuiven over de lagen van één dikke reeks en toch gecombineerd wordt, als percentage van de mondstukdiameter van die extruder. Hogere waarden combineren meer gebogen randen tot dikke lagen, ten koste van ruwere randwanden: afwijkingen tot dit deel van de mondstukdiameter worden opgeslokt door de dikke extrusies." + +msgid "Increase" +msgstr "Verhogen" + +msgid "Inner walls" +msgstr "Binnenste wanden" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Laaghoogte waarmee deze extruder moet printen, voor printers waarvan de extruders verschillende mondstukmaten hebben. Moet een geheel veelvoud van de objectlaaghoogte zijn. Een onderdeel waarvan alle kenmerken deze extruder volgen, print alleen op elke N-de laag met dienovereenkomstig dikkere extrusies, waar de geometrie het toelaat; elders valt het terug op de objectlaaghoogte. Als de rest van het onderdeel niet kan volgen, combineren wanden die aan deze extruder zijn toegewezen zich toch zelfstandig tot deze hoogte, absorberen volledig dichte bovenvlakken de massieve lagen eronder, en combineert dunne of 100% dichte vulling zich onafhankelijk tot deze hoogte. 0 betekent de objectlaaghoogte gebruiken." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Laaghoogte waarmee deze extruder moet printen: een geheel veelvoud van de objectlaaghoogte binnen de laaghoogtelimieten van deze extruder. Standaard behoudt de objectlaaghoogte." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Geen enkele extruder heeft een mondstuk dat overeenkomt met de mondstukdiameter voor ondersteuning." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Geen geladen filament komt overeen met het materiaal van de ondersteunings-/vlotbasis (en de mondstukdiameter voor ondersteuning)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Geen geladen filament komt overeen met het materiaal van de ondersteunings-/vlotinterface (en de mondstukdiameter voor ondersteuning)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Mondstuk %1%: laaghoogtelimieten ingesteld op %2%-%3% mm, uit \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Op printers waarvan de extruders verschillende mondstukdiameters hebben, worden alleen filamenten van deze mondstukdiameter gebruikt om ondersteuning, vlot en ondersteuningsinterface te printen. Dit houdt filamenten van andere mondstukmaten - met hun andere lijnbreedtes en laaghoogtelimieten - uit de ondersteuning. Ondersteuningsfilamenten met een niet-standaardwaarde moeten met deze diameter overeenkomen. Waarde 0 laat elk filament ondersteuning printen." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Buitenste en binnenste wanden printen automatisch op hun eigen voorkeurslaaghoogtes wanneer de ene hoogte een geheel veelvoud van de andere is. Wanneer de hoogtes niet gelijk deelbaar zijn, past deze optie de wandlaaghoogte van één van de twee wandfilamenten (hieronder gekozen) aan naar het dichtstbijzijnde veelvoud of de dichtstbijzijnde deler van de andere, zodat de wanden zich toch kunnen splitsen. De aangepaste hoogte geldt alleen voor de wanden van dat filament; andere kenmerken behouden de voorkeurslaaghoogte. Aanpassingen verlaten nooit de laaghoogtelimieten van het filament: bestaat er in de gekozen richting geen toegestane hoogte, dan printen de wanden zoals gebruikelijk samen op de lagere hoogte." + +msgid "Outer walls" +msgstr "Buitenste wanden" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Laaghoogtes per extruder worden niet ondersteund in de spiraalvaasmodus." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Laaghoogtes per extruder worden niet ondersteund samen met interfaceschillen." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Laaghoogtes per extruder worden niet ondersteund samen met variabele laaghoogte." + +msgid "Preferred layer height" +msgstr "Voorkeurslaaghoogte" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Print de ondersteuning en vlotbasis alleen met filamenten van dit materiaaltype; extruders met andere types worden hiervoor niet gebruikt. Werkt samen met de mondstukdiameterbeperking voor ondersteuning. Leeg laten voor geen beperking; een expliciet gekozen ondersteunings-/vlotbasisfilament heeft nog steeds voorrang." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Print de ondersteunings- en vlotinterface alleen met filamenten van dit materiaaltype; extruders met andere types worden hiervoor niet gebruikt. Werkt samen met de mondstukdiameterbeperking voor ondersteuning. Leeg laten voor geen beperking; een expliciet gekozen ondersteunings-/vlotinterfacefilament heeft nog steeds voorrang." + +msgid "Raft and support base" +msgstr "Vlot en ondersteuningsbasis" + +msgid "Show legacy filament selection" +msgstr "Oude filamentselectie tonen" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Sommige onderdelen prefereren lagen van %1% mm, maar het mondstuk van filament %2% dat andere kenmerken van het onderdeel print is te klein om die hoogte te extruderen. Deze onderdelen printen in plaats daarvan met de objectlaaghoogte." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Sommige onderdelen prefereren lagen van %1% mm, maar het mondstuk van wandfilament %2% is te klein om die hoogte te extruderen. Buitenste en binnenste wanden printen samen, dus deze wanden behouden de objectlaaghoogte. Wijs beide wandkenmerken toe aan filamenten met voldoende grote mondstukken." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Sommige onderdelen printen lagen van %1% mm met filament %2%, waarvan de maximale laaghoogte %3% mm is. Wijs de kenmerken van het onderdeel toe aan filamenten van het grovere mondstuk, verhoog de maximale laaghoogte van het filament, of accepteer erboven te printen." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Sommige onderdelen printen lagen van %1% mm met filament %2%, waarvan de minimale laaghoogte %3% mm is. Verhoog de objectlaaghoogte, gebruik voor deze kenmerken een filament met een fijner mondstuk, of accepteer onder het minimum van de extruder te printen." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Sommige onderdelen gebruiken filamenten waarvan de voorkeurslaaghoogtes niet allemaal kunnen worden gehonoreerd: een onderdeel print zijn kenmerken met één laagstap (bepaald door zijn wandfilamenten, of door de overeenstemming van de andere kenmerken als geen wandfilament een voorkeur heeft); de wanden, de bovenvlakken en de vulling kunnen zich elk tot hun eigen hoogte combineren wanneer de rest van het onderdeel ze niet kan volgen, maar de overige kenmerken printen met de stap van het onderdeel." + +msgid "Support for mixed nozzle sizes" +msgstr "Ondersteuning bij gemengde mondstukmaten" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Ondersteuning kan printen met extruders van verschillende mondstukdiameters. Stel de mondstukdiameter voor ondersteuning in (of expliciete ondersteunings- en interfacefilamenten) om de ondersteuning op één mondstukmaat te houden." + +msgid "Support nozzle diameter" +msgstr "Mondstukdiameter voor ondersteuning" + +msgid "Support nozzle size" +msgstr "Mondstukmaat voor ondersteuning" + +msgid "Support/raft base material" +msgstr "Materiaal ondersteunings-/vlotbasis" + +msgid "Support/raft interface material" +msgstr "Materiaal ondersteunings-/vlotinterface" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "De vullijnbreedte van %1% mm is te klein voor vulling gecombineerd tot lagen van %2% mm hoog. Vergroot de lijnbreedte of verlaag de voorkeurslaaghoogte van het vulfilament." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "De lijnbreedte van %1% mm is te klein voor de laaghoogte van %2% mm van zijn extruder. Vergroot de lijnbreedte of verlaag de extruderlaaghoogte." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "De laaghoogte van extruder %1% (%2% mm) kan de mondstukdiameter niet overschrijden." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "De laaghoogte van extruder %1% (%2% mm) wordt voor sommige onderdelen genegeerd: ze moet een geheel veelvoud van de objectlaaghoogte (%3% mm) zijn, niet eronder liggen, en mag de mondstukdiameter van de extruder niet overschrijden." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "De laaghoogte van extruder %1% (%2% mm) is kleiner dan de objectlaaghoogte (%3% mm). Verlaag de objectlaaghoogte tot de fijnste extruderlaaghoogte." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "De laaghoogte van extruder %1% (%2% mm) moet een geheel veelvoud van de objectlaaghoogte (%3% mm) zijn." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "De laagste printbare laaghoogte van de extruder. Beperkt de minimale laaghoogte wanneer adaptieve laaghoogte is ingeschakeld. Onderdelen die met een dikkere voorkeursextruderlaaghoogte printen, vallen ook nooit onder deze hoogte terug (behalve de eerste laag)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "De voorkeurslaaghoogte van mondstuk %1% past er niet meer doorheen en is teruggezet op Standaard." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Het ondersteunings-/vlotbasisfilament is niet van het materiaal van de ondersteunings-/vlotbasis." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Het ondersteunings-/vlotbasisfilament print met een mondstuk dat niet overeenkomt met de mondstukdiameter voor ondersteuning." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Het ondersteunings-/vlotinterfacefilament is niet van het materiaal van de ondersteunings-/vlotinterface." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Het ondersteunings-/vlotinterfacefilament print met een mondstuk dat niet overeenkomt met de mondstukdiameter voor ondersteuning." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "De wandfilamenten van sommige onderdelen prefereren verschillende laaghoogtes. Buitenste en binnenste wanden printen samen, dus deze wanden behouden de objectlaaghoogte. Wijs beide wandkenmerken toe aan filamenten met dezelfde voorkeurshoogte om dikkere wanden te printen." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "De wandfilamenten van sommige onderdelen prefereren verschillende laaghoogtes. Buitenste en binnenste wanden printen samen, dus deze wanden printen met de lagere hoogte (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "De wandlaaghoogte van filament %1% is aangepast van de voorkeur van %2% mm naar %3% mm zodat de buitenste en binnenste wanden op compatibele laaghoogtes kunnen printen (\"Laaghoogte van wanden aanpassen\"). Alleen de wanden van dit filament printen de aangepaste hoogte; zijn andere kenmerken behouden de voorkeurshoogte." + +msgid "Thick layer regions" +msgstr "Regio's met dikke lagen" + +msgid "Thick layer tolerance" +msgstr "Tolerantie voor dikke lagen" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Deze printer heeft geen profiel voor een mondstuk van %1% mm. Controleer de laaghoogtelimieten van mondstuk %2% in de printerinstellingen." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Deze printer gebruikt verschillende mondstukmaten. Selecteer de mondstukmaat die de ondersteuning print, en de filamenttypes voor het vlot en de ondersteuningsinterface." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Of de wandlaaghoogte van het aangepaste wandfilament wordt verlaagd of verhoogd om een hoogte te bereiken die compatibel is met het andere wandfilament. Hoogtes buiten de laaghoogtelimieten van het aangepaste filament worden nooit gebruikt: bestaat er in deze richting geen toegestane hoogte, dan printen de wanden zoals gebruikelijk samen op de lagere hoogte." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Welk van de twee wandfilamenten zijn wandlaaghoogte aangepast krijgt wanneer de voorkeurslaaghoogtes niet gelijk deelbaar zijn." diff --git a/localization/i18n/pl/Snapmaker_Orca_pl.po b/localization/i18n/pl/Snapmaker_Orca_pl.po index 5029d4c66e6..3608b349c5b 100644 --- a/localization/i18n/pl/Snapmaker_Orca_pl.po +++ b/localization/i18n/pl/Snapmaker_Orca_pl.po @@ -22091,4 +22091,220 @@ msgstr "" #~ msgstr "" #~ "Proszę podać poprawne wartości:\n" #~ "start > 10 kroków >= 0\n" -#~ "koniec > start + krok)" \ No newline at end of file +#~ "koniec > start + krok)" +msgid "Adjust wall layer height" +msgstr "Dostosuj wysokość warstwy ścian" + +msgid "Adjusted walls" +msgstr "Dostosowywane ściany" + +msgid "Adjustment direction" +msgstr "Kierunek dostosowania" + +msgid "Consistent" +msgstr "Spójnie" + +msgid "Decrease" +msgstr "Zmniejsz" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament do druku dolnej powierzchni.\n\"Domyślny\" używa aktywnego filamentu obiektu/części." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament do druku wewnętrznych ścian.\n\"Domyślny\" używa aktywnego filamentu obiektu/części." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament do druku wewnętrznego wypełnienia pełnego.\n\"Domyślny\" używa aktywnego filamentu obiektu/części." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament do druku wewnętrznego wypełnienia rzadkiego.\n\"Domyślny\" używa aktywnego filamentu obiektu/części." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament do druku zewnętrznych ścian.\n\"Domyślny\" używa aktywnego filamentu obiektu/części." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament do drukowania podstawy podpory i raftu. „Domyślnie” oznacza brak wyboru konkretnego filamentu dla ich podstawy. Zostanie użyty obecny filament" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament do drukowania warstw łączących podpory z modelem. „Domyślnie” oznacza brak konkretnego filamentu dla podpory i używanie obecnego filamentu" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament do druku górnej powierzchni.\n\"Domyślny\" używa aktywnego filamentu obiektu/części." + +msgid "Fixed" +msgstr "Stale" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Jak agresywnie części przypisane do ekstrudera z grubszą preferowaną wysokością warstwy są łączone w grube warstwy.\nSpójnie: części drukują się najwyżej dwiema wysokościami warstwy - wysokością warstwy ekstrudera tam, gdzie mieszczą się całe serie warstw, i wysokością warstwy obiektu wszędzie indziej. Daje to najrówniejsze ściany.\nAdaptacyjnie: serie mogą być łączone także na pośrednich wielokrotnościach wysokości warstwy obiektu, więc większa część elementu drukuje się grubszymi warstwami - kosztem pasm zmiennej wysokości warstwy na zakrzywionych granicach.\nStale: części zawsze drukują się wysokością warstwy ekstrudera, nawet tam, gdzie kształt zmienia się w poprzek łączonych warstw lub zwisa; zakrzywione granice zamieniają się w stopnie, a detale drobniejsze niż grube warstwy giną. Tylko geometria zbyt krótka na całą grubą warstwę (wierzchołki części i pierwsza warstwa) drukuje się cieniej." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Jak daleko obrys części przypisanej do ekstrudera z grubszą preferowaną wysokością warstwy może przesunąć się w bok w poprzek warstw jednej grubej serii i wciąż zostać połączony, jako procent średnicy dyszy tego ekstrudera. Wyższe wartości łączą więcej zakrzywionych granic w grube warstwy, kosztem bardziej chropowatych ścian granicznych: odchylenia do tego ułamka średnicy dyszy są pochłaniane przez grube linie." + +msgid "Increase" +msgstr "Zwiększ" + +msgid "Inner walls" +msgstr "Wewnętrzne ściany" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Wysokość warstwy, którą powinien drukować ten ekstruder; dla drukarek, których ekstrudery mają różne rozmiary dysz. Musi być całkowitą wielokrotnością wysokości warstwy obiektu. Część, której wszystkie elementy podążają za tym ekstruderem, drukuje się tylko co N-tą warstwę odpowiednio grubszymi liniami tam, gdzie pozwala geometria; gdzie indziej wraca do wysokości warstwy obiektu. Gdy reszta części nie może nadążyć, ściany przypisane temu ekstruderowi i tak łączą się samodzielnie do tej wysokości, w pełni gęste górne powierzchnie pochłaniają pełne warstwy pod sobą, a wypełnienie (rzadkie lub o gęstości 100%) łączy się do tej wysokości niezależnie. 0 oznacza użycie wysokości warstwy obiektu." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Wysokość warstwy, którą powinien drukować ten ekstruder: całkowita wielokrotność wysokości warstwy obiektu w granicach ograniczeń wysokości warstwy tego ekstrudera. Domyślnie zachowuje wysokość warstwy obiektu." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Żaden ekstruder nie ma dyszy zgodnej ze średnicą dyszy podpór." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Żaden załadowany filament nie pasuje do materiału podstawy podpór/tratwy (i do średnicy dyszy podpór)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Żaden załadowany filament nie pasuje do materiału warstw stykowych podpór/tratwy (i do średnicy dyszy podpór)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Dysza %1%: ograniczenia wysokości warstwy ustawione na %2%-%3% mm, z \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Na drukarkach, których ekstrudery mają różne średnice dysz, do druku podpór, tratwy i warstw stykowych używane są tylko filamenty o tej średnicy dyszy. Trzyma to filamenty innych rozmiarów dysz - z ich inną szerokością linii i ograniczeniami wysokości warstwy - z dala od podpór. Filamenty podpór ustawione na wartość inną niż domyślna muszą pasować do tej średnicy. Wartość 0 pozwala drukować podpory dowolnym filamentem." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Zewnętrzne i wewnętrzne ściany automatycznie drukują się własnymi preferowanymi wysokościami warstwy, gdy jedna wysokość jest całkowitą wielokrotnością drugiej. Gdy wysokości nie dzielą się równo, ta opcja dostosowuje wysokość warstwy ścian jednego z dwóch filamentów ścian (wybranego poniżej) do najbliższej wielokrotności lub dzielnika drugiej, aby ściany nadal mogły się rozdzielić. Dostosowana wysokość dotyczy tylko ścian tego filamentu; pozostałe elementy zachowują preferowaną wysokość warstwy. Dostosowania nigdy nie wychodzą poza ograniczenia wysokości warstwy filamentu: jeśli w wybranym kierunku nie ma dozwolonej wysokości, ściany drukują się razem na niższej wysokości jak zwykle." + +msgid "Outer walls" +msgstr "Zewnętrzne ściany" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Wysokości warstwy na ekstruder nie są obsługiwane w trybie wazy." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Wysokości warstwy na ekstruder nie są obsługiwane razem z powłokami stykowymi." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Wysokości warstwy na ekstruder nie są obsługiwane razem ze zmienną wysokością warstwy." + +msgid "Preferred layer height" +msgstr "Preferowana wysokość warstwy" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Drukuj podpory i podstawę tratwy tylko filamentami tego typu materiału; ekstrudery z innymi typami nie są do tego używane. Działa razem z ograniczeniem średnicy dyszy podpór. Zostaw puste, aby nie ograniczać; jawnie wybrany filament podstawy podpór/tratwy nadal ma pierwszeństwo." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Drukuj warstwy stykowe podpór i tratwy tylko filamentami tego typu materiału; ekstrudery z innymi typami nie są do tego używane. Działa razem z ograniczeniem średnicy dyszy podpór. Zostaw puste, aby nie ograniczać; jawnie wybrany filament warstw stykowych nadal ma pierwszeństwo." + +msgid "Raft and support base" +msgstr "Tratwa i podstawa podpór" + +msgid "Show legacy filament selection" +msgstr "Pokaż stary wybór filamentu" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Niektóre części preferują warstwy %1% mm, ale dysza filamentu %2% drukującego inne elementy części jest za mała, by wytłoczyć tę wysokość. Te części drukują się zamiast tego wysokością warstwy obiektu." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Niektóre części preferują warstwy %1% mm, ale dysza filamentu ścian %2% jest za mała, by wytłoczyć tę wysokość. Zewnętrzne i wewnętrzne ściany drukują się razem, więc te ściany zachowują wysokość warstwy obiektu. Przypisz oba rodzaje ścian filamentom z wystarczająco dużymi dyszami." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Niektóre części drukują warstwy %1% mm filamentem %2%, którego maksymalna wysokość warstwy to %3% mm. Przypisz elementy części filamentom grubszej dyszy, zwiększ maksymalną wysokość warstwy filamentu lub zaakceptuj druk powyżej niej." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Niektóre części drukują warstwy %1% mm filamentem %2%, którego minimalna wysokość warstwy to %3% mm. Zwiększ wysokość warstwy obiektu, użyj do tych elementów filamentu z drobniejszą dyszą lub zaakceptuj druk poniżej minimum ekstrudera." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Niektóre części używają filamentów, których preferowanych wysokości warstwy nie da się wszystkich spełnić: część drukuje swoje elementy jednym skokiem warstwy (ustalonym przez jej filamenty ścian, a gdy żaden filament ścian nie ma preferencji - przez zgodę pozostałych elementów); ściany, górne powierzchnie i wypełnienie mogą łączyć się każde do własnej wysokości, gdy reszta części nie może za nimi nadążyć, ale pozostałe elementy drukują się skokiem części." + +msgid "Support for mixed nozzle sizes" +msgstr "Podpory przy mieszanych rozmiarach dysz" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Podpory mogą drukować się ekstruderami o różnych średnicach dysz. Ustaw średnicę dyszy podpór (lub jawne filamenty podpór i warstw stykowych), aby utrzymać podpory na jednym rozmiarze dyszy." + +msgid "Support nozzle diameter" +msgstr "Średnica dyszy podpór" + +msgid "Support nozzle size" +msgstr "Rozmiar dyszy podpór" + +msgid "Support/raft base material" +msgstr "Materiał podstawy podpór/tratwy" + +msgid "Support/raft interface material" +msgstr "Materiał warstw stykowych podpór/tratwy" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "Szerokość linii wypełnienia %1% mm jest za mała dla wypełnienia łączonego w warstwy o wysokości %2% mm. Zwiększ szerokość linii lub obniż preferowaną wysokość warstwy filamentu wypełnienia." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "Szerokość linii %1% mm jest za mała dla wysokości warstwy %2% mm jej ekstrudera. Zwiększ szerokość linii lub obniż wysokość warstwy ekstrudera." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Wysokość warstwy ekstrudera %1% (%2% mm) nie może przekraczać średnicy jego dyszy." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Wysokość warstwy ekstrudera %1% (%2% mm) jest ignorowana dla niektórych części: musi być całkowitą wielokrotnością wysokości warstwy obiektu (%3% mm), nie mniejszą od niej, i nie może przekraczać średnicy dyszy ekstrudera." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Wysokość warstwy ekstrudera %1% (%2% mm) jest mniejsza niż wysokość warstwy obiektu (%3% mm). Obniż wysokość warstwy obiektu do najdrobniejszej wysokości warstwy ekstrudera." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Wysokość warstwy ekstrudera %1% (%2% mm) musi być całkowitą wielokrotnością wysokości warstwy obiektu (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Najniższa drukowalna wysokość warstwy ekstrudera. Ogranicza minimalną wysokość warstwy przy włączonej adaptacyjnej wysokości warstwy. Części drukowane grubszą preferowaną wysokością warstwy ekstrudera również nigdy nie schodzą poniżej tej wysokości (poza pierwszą warstwą)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Preferowana wysokość warstwy dyszy %1% już przez nią nie przechodzi i została zresetowana do Domyślnej." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Filament podstawy podpór/tratwy nie jest z materiału podstawy podpór/tratwy." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Filament podstawy podpór/tratwy drukuje dyszą niezgodną ze średnicą dyszy podpór." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Filament warstw stykowych podpór/tratwy nie jest z materiału warstw stykowych podpór/tratwy." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Filament warstw stykowych podpór/tratwy drukuje dyszą niezgodną ze średnicą dyszy podpór." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Filamenty ścian niektórych części preferują różne wysokości warstwy. Zewnętrzne i wewnętrzne ściany drukują się razem, więc te ściany zachowują wysokość warstwy obiektu. Aby drukować grubsze ściany, przypisz oba rodzaje ścian filamentom preferującym tę samą wysokość." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Filamenty ścian niektórych części preferują różne wysokości warstwy. Zewnętrzne i wewnętrzne ściany drukują się razem, więc te ściany drukują się niższą wysokością (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Wysokość warstwy ścian filamentu %1% została dostosowana z preferowanych %2% mm do %3% mm, aby zewnętrzne i wewnętrzne ściany mogły drukować się zgodnymi wysokościami warstwy (\"Dostosuj wysokość warstwy ścian\"). Dostosowaną wysokością drukują się tylko ściany tego filamentu; jego pozostałe elementy zachowują preferowaną." + +msgid "Thick layer regions" +msgstr "Obszary grubych warstw" + +msgid "Thick layer tolerance" +msgstr "Tolerancja grubych warstw" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Ta drukarka nie ma profilu dla dyszy %1% mm. Sprawdź ograniczenia wysokości warstwy dyszy %2% w ustawieniach drukarki." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Ta drukarka używa różnych rozmiarów dysz. Wybierz rozmiar dyszy drukującej podpory oraz typy filamentów używane do tratwy i warstw stykowych podpór." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Czy wysokość warstwy ścian dostosowywanego filamentu jest zmniejszana czy zwiększana, aby osiągnąć wysokość zgodną z drugim filamentem ścian. Wysokości poza ograniczeniami wysokości warstwy dostosowywanego filamentu nigdy nie są używane: jeśli w tym kierunku nie ma dozwolonej wysokości, ściany drukują się razem na niższej wysokości jak zwykle." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Który z dwóch filamentów ścian ma dostosowywaną wysokość warstwy ścian, gdy preferowane wysokości warstwy nie dzielą się równo." diff --git a/localization/i18n/pt_BR/Snapmaker_Orca_pt_BR.po b/localization/i18n/pt_BR/Snapmaker_Orca_pt_BR.po index 1db2c2de942..53a94b40498 100644 --- a/localization/i18n/pt_BR/Snapmaker_Orca_pt_BR.po +++ b/localization/i18n/pt_BR/Snapmaker_Orca_pt_BR.po @@ -19826,4 +19826,220 @@ msgstr "" #~ msgstr "Pausa na Impressão" #~ msgid "Printer local connection failed, please try again." -#~ msgstr "Falha na conexão local da impressora, por favor tente novamente." \ No newline at end of file +#~ msgstr "Falha na conexão local da impressora, por favor tente novamente." +msgid "Adjust wall layer height" +msgstr "Ajustar a altura da camada das paredes" + +msgid "Adjusted walls" +msgstr "Paredes ajustadas" + +msgid "Adjustment direction" +msgstr "Direção do ajuste" + +msgid "Consistent" +msgstr "Consistente" + +msgid "Decrease" +msgstr "Diminuir" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir a superfície inferior.\n\"Padrão\" usa o filamento do objeto/peça." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir paredes internas.\n\"Padrão\" usa o filamento do objeto/peça." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir o preenchimento sólido interno.\n\"Padrão\" usa o filamento do objeto/peça." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir o preenchimento esparso interno.\n\"Padrão\" usa o filamento do objeto/peça." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir paredes externas.\n\"Padrão\" usa o filamento do objeto/peça." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filamento para imprimir a base de suporte e a jangada.\n\"Padrão\" significa nenhum filamento específico para suporte e o filamento atual será usado." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filamento para imprimir a interface de suporte.\n\"Padrão\" significa nenhum filamento específico para a interface de suporte e o filamento atual é usado." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filamento para imprimir a superfície superior.\n\"Padrão\" usa o filamento do objeto/peça." + +msgid "Fixed" +msgstr "Fixo" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Quão agressivamente as peças atribuídas a uma extrusora com altura de camada preferida mais espessa são combinadas em camadas espessas.\nConsistente: as peças imprimem com no máximo duas alturas de camada - a altura da camada da extrusora onde cabem séries inteiras de camadas e a altura da camada do objeto em todo o resto. Isso dá as paredes mais uniformes.\nAdaptativo: as séries também podem ser combinadas em múltiplos intermediários da altura da camada do objeto, então mais da peça imprime com camadas espessas, ao custo de faixas de alturas de camada variáveis nos contornos curvos.\nFixo: as peças sempre imprimem na altura da camada da extrusora, mesmo onde a forma muda através das camadas combinadas ou tem balanços; contornos curvos viram degraus e detalhes mais finos que as camadas espessas são perdidos. Apenas a geometria curta demais para uma camada espessa inteira (os topos das peças e a primeira camada) imprime mais fina." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Quanto o contorno de uma peça atribuída a uma extrusora com altura de camada preferida mais espessa pode deslocar-se lateralmente através das camadas de uma série espessa e ainda ser combinado, como porcentagem do diâmetro do bico dessa extrusora. Valores maiores combinam mais contornos curvos em camadas espessas, ao custo de paredes de contorno mais ásperas: desvios até essa fração do diâmetro do bico são absorvidos pelas extrusões espessas." + +msgid "Increase" +msgstr "Aumentar" + +msgid "Inner walls" +msgstr "Paredes internas" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Altura da camada com que esta extrusora deve imprimir, para impressoras cujas extrusoras têm bicos de tamanhos diferentes. Deve ser um múltiplo inteiro da altura da camada do objeto. Uma peça cujas características seguem todas esta extrusora imprime apenas a cada N camadas com extrusões correspondentemente mais espessas, onde sua geometria permitir; no resto, volta à altura da camada do objeto. Quando o resto da peça não pode acompanhar, as paredes atribuídas a esta extrusora ainda se combinam sozinhas nesta altura, superfícies superiores totalmente densas absorvem as camadas sólidas abaixo delas, e o preenchimento esparso ou 100% denso combina-se independentemente nesta altura. 0 significa usar a altura da camada do objeto." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Altura da camada com que esta extrusora deve imprimir: um múltiplo inteiro da altura da camada do objeto dentro dos limites de altura de camada desta extrusora. Padrão mantém a altura da camada do objeto." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Nenhuma extrusora tem um bico compatível com o diâmetro do bico dos suportes." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Nenhum filamento carregado corresponde ao material da base dos suportes/jangada (e ao diâmetro do bico dos suportes)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Nenhum filamento carregado corresponde ao material da interface dos suportes/jangada (e ao diâmetro do bico dos suportes)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Bico %1%: limites de altura da camada definidos em %2%-%3% mm, de \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Em impressoras cujas extrusoras têm diâmetros de bico diferentes, apenas filamentos desse diâmetro de bico são usados para imprimir suportes, jangada e interface dos suportes. Isso mantém filamentos de outros tamanhos de bico - com suas larguras de linha e limites de altura de camada diferentes - fora dos suportes. Filamentos de suporte definidos com um valor não padrão devem corresponder a este diâmetro. O valor 0 permite que qualquer filamento imprima suportes." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "As paredes externas e internas imprimem automaticamente nas suas próprias alturas de camada preferidas quando uma altura é um múltiplo inteiro da outra. Quando as alturas não se dividem exatamente, esta opção ajusta a altura da camada das paredes de um dos dois filamentos de parede (escolhido abaixo) para o múltiplo ou divisor mais próximo da outra, para que as paredes ainda possam se separar. A altura ajustada aplica-se apenas às paredes desse filamento; as demais características mantêm a altura da camada preferida. Os ajustes nunca saem dos limites de altura de camada do filamento: se não existir altura permitida na direção escolhida, as paredes imprimem juntas na altura menor, como de costume." + +msgid "Outer walls" +msgstr "Paredes externas" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Alturas de camada por extrusora não são suportadas no modo vaso espiral." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Alturas de camada por extrusora não são suportadas junto com cascas de interface." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Alturas de camada por extrusora não são suportadas junto com altura de camada variável." + +msgid "Preferred layer height" +msgstr "Altura da camada preferida" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Imprimir os suportes e a base da jangada apenas com filamentos deste tipo de material; extrusoras carregadas com outros tipos não são usadas para isso. Combina-se com a restrição do diâmetro do bico dos suportes. Deixe vazio para não restringir; um filamento de base dos suportes/jangada escolhido explicitamente ainda tem precedência." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Imprimir a interface dos suportes e da jangada apenas com filamentos deste tipo de material; extrusoras carregadas com outros tipos não são usadas para isso. Combina-se com a restrição do diâmetro do bico dos suportes. Deixe vazio para não restringir; um filamento de interface dos suportes/jangada escolhido explicitamente ainda tem precedência." + +msgid "Raft and support base" +msgstr "Jangada e base dos suportes" + +msgid "Show legacy filament selection" +msgstr "Mostrar a seleção antiga de filamento" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Algumas peças preferem camadas de %1% mm, mas o bico do filamento %2% que imprime outras características da peça é pequeno demais para extrudar essa altura. Essas peças imprimem com a altura da camada do objeto em vez disso." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Algumas peças preferem camadas de %1% mm, mas o bico do filamento de parede %2% é pequeno demais para extrudar essa altura. As paredes externas e internas imprimem juntas, então essas paredes mantêm a altura da camada do objeto. Atribua ambas as características de parede a filamentos com bicos grandes o suficiente." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Algumas peças imprimem camadas de %1% mm com o filamento %2%, cuja altura máxima de camada é %3% mm. Atribua as características da peça aos filamentos do bico mais grosso, aumente a altura máxima de camada do filamento ou aceite imprimir acima dela." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Algumas peças imprimem camadas de %1% mm com o filamento %2%, cuja altura mínima de camada é %3% mm. Aumente a altura da camada do objeto, use um filamento com um bico mais fino para essas características ou aceite imprimir abaixo do mínimo da extrusora." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Algumas peças usam filamentos cujas alturas de camada preferidas não podem ser todas atendidas: uma peça imprime suas características com um só passo de camada (definido pelos seus filamentos de parede, ou pelo acordo das demais características quando nenhum filamento de parede tem preferência); as paredes, as superfícies superiores e o preenchimento podem cada um combinar-se na sua própria altura quando o resto da peça não pode acompanhá-los, mas as características restantes imprimem com o passo da peça." + +msgid "Support for mixed nozzle sizes" +msgstr "Suportes com tamanhos de bico mistos" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Os suportes podem imprimir com extrusoras de diâmetros de bico diferentes. Defina o diâmetro do bico dos suportes (ou filamentos explícitos de suporte e interface) para manter os suportes em um só tamanho de bico." + +msgid "Support nozzle diameter" +msgstr "Diâmetro do bico dos suportes" + +msgid "Support nozzle size" +msgstr "Tamanho do bico dos suportes" + +msgid "Support/raft base material" +msgstr "Material da base dos suportes/jangada" + +msgid "Support/raft interface material" +msgstr "Material da interface dos suportes/jangada" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "A largura de linha do preenchimento de %1% mm é pequena demais para preenchimento combinado em camadas de %2% mm de altura. Aumente a largura da linha ou reduza a altura da camada preferida do filamento de preenchimento." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "A largura de linha de %1% mm é pequena demais para a altura de camada de %2% mm da sua extrusora. Aumente a largura da linha ou reduza a altura da camada da extrusora." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "A altura da camada da extrusora %1% (%2% mm) não pode exceder o diâmetro do seu bico." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "A altura da camada da extrusora %1% (%2% mm) é ignorada para algumas peças: deve ser um múltiplo inteiro da altura da camada do objeto (%3% mm), não abaixo dela, e não deve exceder o diâmetro do bico da extrusora." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "A altura da camada da extrusora %1% (%2% mm) é menor que a altura da camada do objeto (%3% mm). Reduza a altura da camada do objeto para a altura de camada de extrusora mais fina." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "A altura da camada da extrusora %1% (%2% mm) deve ser um múltiplo inteiro da altura da camada do objeto (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "A menor altura de camada imprimível da extrusora. Usada para limitar a altura mínima de camada quando a altura de camada adaptativa está ativada. Peças impressas com uma altura de camada de extrusora preferida mais espessa também nunca caem abaixo desta altura (exceto a primeira camada)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "A altura da camada preferida do bico %1% não passa mais por ele e foi redefinida para Padrão." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "O filamento da base dos suportes/jangada não é do material da base dos suportes/jangada." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "O filamento da base dos suportes/jangada imprime com um bico que não corresponde ao diâmetro do bico dos suportes." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "O filamento da interface dos suportes/jangada não é do material da interface dos suportes/jangada." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "O filamento da interface dos suportes/jangada imprime com um bico que não corresponde ao diâmetro do bico dos suportes." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Os filamentos de parede de algumas peças preferem alturas de camada diferentes. As paredes externas e internas imprimem juntas, então essas paredes mantêm a altura da camada do objeto. Atribua ambas as características de parede a filamentos que prefiram a mesma altura para imprimir paredes mais espessas." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Os filamentos de parede de algumas peças preferem alturas de camada diferentes. As paredes externas e internas imprimem juntas, então essas paredes imprimem com a altura menor (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "A altura da camada das paredes do filamento %1% foi ajustada dos %2% mm preferidos para %3% mm para que as paredes externas e internas possam imprimir em alturas de camada compatíveis (\"Ajustar a altura da camada das paredes\"). Apenas as paredes deste filamento imprimem a altura ajustada; suas demais características mantêm a preferida." + +msgid "Thick layer regions" +msgstr "Regiões de camadas espessas" + +msgid "Thick layer tolerance" +msgstr "Tolerância de camadas espessas" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Esta impressora não tem perfil para um bico de %1% mm. Verifique os limites de altura da camada do bico %2% nas configurações da impressora." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Esta impressora usa tamanhos de bico diferentes. Selecione o tamanho do bico que imprime os suportes e os tipos de filamento usados para a jangada e a interface dos suportes." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Se a altura da camada das paredes do filamento ajustado é diminuída ou aumentada para alcançar uma altura compatível com o outro filamento de parede. Alturas fora dos limites de altura de camada do filamento ajustado nunca são usadas: se não existir altura permitida nesta direção, as paredes imprimem juntas na altura menor, como de costume." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Qual dos dois filamentos de parede tem sua altura da camada das paredes ajustada quando as alturas de camada preferidas não se dividem exatamente." diff --git a/localization/i18n/ru/Snapmaker_Orca_ru.po b/localization/i18n/ru/Snapmaker_Orca_ru.po index 4a062ee0f99..90b005d5893 100644 --- a/localization/i18n/ru/Snapmaker_Orca_ru.po +++ b/localization/i18n/ru/Snapmaker_Orca_ru.po @@ -20099,4 +20099,220 @@ msgstr "" #~ "таких как чрезмерная экструзия." #~ msgid "Right click to reset value to system default." -#~ msgstr "Правая кнопка мыши - сброс значения до системного по умолчанию." \ No newline at end of file +#~ msgstr "Правая кнопка мыши - сброс значения до системного по умолчанию." +msgid "Adjust wall layer height" +msgstr "Подгонять высоту слоя стенок" + +msgid "Adjusted walls" +msgstr "Подгоняемые стенки" + +msgid "Adjustment direction" +msgstr "Направление подгонки" + +msgid "Consistent" +msgstr "Единообразно" + +msgid "Decrease" +msgstr "Уменьшать" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Материал для печати нижней поверхности.\n\"По умолчанию\" использует активный материал объекта/детали." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Материал для печати внутренних периметров.\n\"По умолчанию\" использует активный материал объекта/детали." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Материал для печати внутреннего сплошного заполнения.\n\"По умолчанию\" использует активный материал объекта/детали." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Материал для печати внутреннего заполнения.\n\"По умолчанию\" использует активный материал объекта/детали." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Материал для печати внешних периметров.\n\"По умолчанию\" использует активный материал объекта/детали." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Материал для печати основной части поддержки и подложки. «По умолчанию» – использовать материал модели." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Материал для печати связующего слоя поддержки. «По умолчанию» – использовать материал модели." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Материал для печати верхней поверхности.\n\"По умолчанию\" использует активный материал объекта/детали." + +msgid "Fixed" +msgstr "Фиксированно" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Насколько агрессивно детали, назначенные экструдеру с более толстой предпочитаемой высотой слоя, объединяются в толстые слои.\nЕдинообразно: детали печатаются максимум двумя высотами слоя — высотой слоя экструдера там, где помещаются целые серии слоёв, и высотой слоя объекта в остальных местах. Это даёт самые ровные стенки.\nАдаптивно: серии могут объединяться и на промежуточных кратных высоты слоя объекта, поэтому большая часть детали печатается толстыми слоями — ценой полос переменной высоты слоя на криволинейных границах.\nФиксированно: детали всегда печатаются высотой слоя экструдера, даже там, где форма меняется поперёк объединённых слоёв или нависает; криволинейные границы превращаются в ступени, а детали мельче толстых слоёв теряются. Только геометрия, слишком короткая для целого толстого слоя (верхушки деталей и первый слой), печатается тоньше." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Насколько контур детали, назначенной экструдеру с более толстой предпочитаемой высотой слоя, может смещаться вбок между слоями одной толстой серии и всё же объединяться — в процентах от диаметра сопла этого экструдера. Большие значения объединяют больше криволинейных границ в толстые слои ценой более грубых стенок на границах: отклонения до этой доли диаметра сопла поглощаются толстыми линиями." + +msgid "Increase" +msgstr "Увеличивать" + +msgid "Inner walls" +msgstr "Внутренние периметры" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Высота слоя, которой должен печатать этот экструдер; для принтеров, у которых экструдеры имеют разные размеры сопла. Должна быть целым кратным высоты слоя объекта. Деталь, все элементы которой следуют за этим экструдером, печатается только на каждом N-м слое соответственно более толстыми линиями там, где позволяет её геометрия; в остальных местах используется высота слоя объекта. Когда остальная часть детали не может следовать, стенки, назначенные этому экструдеру, всё равно объединяются до этой высоты самостоятельно, полностью плотные верхние поверхности поглощают сплошные слои под собой, а заполнение (разреженное или 100% плотности) объединяется до этой высоты независимо. 0 — использовать высоту слоя объекта." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Высота слоя, которой должен печатать этот экструдер: целое кратное высоты слоя объекта в пределах ограничений высоты слоя этого экструдера. По умолчанию сохраняется высота слоя объекта." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Ни у одного экструдера нет сопла, соответствующего диаметру сопла поддержек." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Ни один загруженный материал не соответствует материалу основания поддержек/подложки (и диаметру сопла поддержек)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Ни один загруженный материал не соответствует материалу связующего слоя поддержек/подложки (и диаметру сопла поддержек)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Сопло %1%: ограничения высоты слоя установлены на %2%-%3% мм из \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "На принтерах, у которых экструдеры имеют разные диаметры сопла, для печати поддержек, подложки и связующего слоя используются только материалы этого диаметра сопла. Это не пускает в поддержки материалы других размеров сопла — с их другой шириной линии и ограничениями высоты слоя. Материалы поддержек, заданные не по умолчанию, должны соответствовать этому диаметру. Значение 0 позволяет печатать поддержки любым материалом." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Внешние и внутренние периметры автоматически печатаются со своими предпочитаемыми высотами слоя, когда одна высота — целое кратное другой. Когда высоты не делятся нацело, эта опция подгоняет высоту слоя стенок одного из двух материалов стенок (выбранного ниже) к ближайшему кратному или делителю другой, чтобы стенки всё же могли разделиться. Подогнанная высота применяется только к стенкам этого материала; остальные элементы сохраняют предпочитаемую высоту слоя. Подгонка никогда не выходит за ограничения высоты слоя материала: если в выбранном направлении нет допустимой высоты, стенки печатаются вместе на меньшей высоте, как обычно." + +msgid "Outer walls" +msgstr "Внешние периметры" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Высоты слоя для отдельных экструдеров не поддерживаются в режиме вазы." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Высоты слоя для отдельных экструдеров не поддерживаются вместе со связующими оболочками." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Высоты слоя для отдельных экструдеров не поддерживаются вместе с переменной высотой слоя." + +msgid "Preferred layer height" +msgstr "Предпочитаемая высота слоя" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Печатать поддержки и основание подложки только материалами этого типа; экструдеры с другими типами для этого не используются. Действует вместе с ограничением диаметра сопла поддержек. Оставьте пустым, чтобы не ограничивать; явно выбранный материал основания поддержек/подложки по-прежнему в приоритете." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Печатать связующий слой поддержек и подложки только материалами этого типа; экструдеры с другими типами для этого не используются. Действует вместе с ограничением диаметра сопла поддержек. Оставьте пустым, чтобы не ограничивать; явно выбранный материал связующего слоя по-прежнему в приоритете." + +msgid "Raft and support base" +msgstr "Подложка и основание поддержек" + +msgid "Show legacy filament selection" +msgstr "Показать старый выбор материала" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Некоторые детали предпочитают слои %1% мм, но сопло материала %2%, печатающего другие элементы детали, слишком мало для такой высоты. Эти детали печатаются высотой слоя объекта." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Некоторые детали предпочитают слои %1% мм, но сопло материала стенок %2% слишком мало для такой высоты. Внешние и внутренние периметры печатаются вместе, поэтому эти стенки сохраняют высоту слоя объекта. Назначьте оба типа стенок материалам с достаточно большими соплами." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Некоторые детали печатают слои %1% мм материалом %2%, у которого максимальная высота слоя %3% мм. Назначьте элементы детали материалам более крупного сопла, увеличьте максимальную высоту слоя материала или согласитесь печатать выше неё." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Некоторые детали печатают слои %1% мм материалом %2%, у которого минимальная высота слоя %3% мм. Увеличьте высоту слоя объекта, используйте для этих элементов материал с более тонким соплом или согласитесь печатать ниже минимума экструдера." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Некоторые детали используют материалы, чьи предпочитаемые высоты слоя нельзя соблюсти все сразу: деталь печатает свои элементы одним шагом слоя (задаваемым её материалами стенок, а если ни один материал стенок не имеет предпочтения — согласием остальных элементов); стенки, верхние поверхности и заполнение могут объединяться каждый до своей высоты, когда остальная часть детали не может за ними следовать, но остальные элементы печатаются шагом детали." + +msgid "Support for mixed nozzle sizes" +msgstr "Поддержки при разных размерах сопла" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Поддержки могут печататься экструдерами с разными диаметрами сопла. Задайте диаметр сопла поддержек (или явные материалы поддержек и связующего слоя), чтобы держать поддержки на одном размере сопла." + +msgid "Support nozzle diameter" +msgstr "Диаметр сопла поддержек" + +msgid "Support nozzle size" +msgstr "Размер сопла поддержек" + +msgid "Support/raft base material" +msgstr "Материал основания поддержек/подложки" + +msgid "Support/raft interface material" +msgstr "Материал связующего слоя поддержек/подложки" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "Ширина линии заполнения %1% мм слишком мала для заполнения, объединённого в слои высотой %2% мм. Увеличьте ширину линии или уменьшите предпочитаемую высоту слоя материала заполнения." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "Ширина линии %1% мм слишком мала для высоты слоя %2% мм её экструдера. Увеличьте ширину линии или уменьшите высоту слоя экструдера." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Высота слоя экструдера %1% (%2% мм) не может превышать диаметр его сопла." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Высота слоя экструдера %1% (%2% мм) игнорируется для некоторых деталей: она должна быть целым кратным высоты слоя объекта (%3% мм), не меньше её и не должна превышать диаметр сопла экструдера." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Высота слоя экструдера %1% (%2% мм) меньше высоты слоя объекта (%3% мм). Уменьшите высоту слоя объекта до самой тонкой высоты слоя экструдера." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Высота слоя экструдера %1% (%2% мм) должна быть целым кратным высоты слоя объекта (%3% мм)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Наименьшая печатаемая высота слоя экструдера. Ограничивает минимальную высоту слоя при адаптивной высоте слоя. Детали, печатаемые с более толстой предпочитаемой высотой слоя экструдера, тоже никогда не опускаются ниже этой высоты (кроме первого слоя)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Предпочитаемая высота слоя сопла %1% больше не проходит через него и была сброшена на \"По умолчанию\"." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Материал основания поддержек/подложки не относится к материалу основания поддержек/подложки." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Материал основания поддержек/подложки печатается соплом, не соответствующим диаметру сопла поддержек." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Материал связующего слоя поддержек/подложки не относится к материалу связующего слоя поддержек/подложки." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Материал связующего слоя поддержек/подложки печатается соплом, не соответствующим диаметру сопла поддержек." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Материалы стенок некоторых деталей предпочитают разные высоты слоя. Внешние и внутренние периметры печатаются вместе, поэтому эти стенки сохраняют высоту слоя объекта. Чтобы печатать более толстые стенки, назначьте оба типа стенок материалам с одинаковой предпочитаемой высотой." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Материалы стенок некоторых деталей предпочитают разные высоты слоя. Внешние и внутренние периметры печатаются вместе, поэтому эти стенки печатаются меньшей высотой (%1% мм)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Высота слоя стенок материала %1% подогнана с предпочитаемых %2% мм до %3% мм, чтобы внешние и внутренние периметры могли печататься совместимыми высотами слоя (\"Подгонять высоту слоя стенок\"). Подогнанной высотой печатаются только стенки этого материала; остальные его элементы сохраняют предпочитаемую." + +msgid "Thick layer regions" +msgstr "Области толстых слоёв" + +msgid "Thick layer tolerance" +msgstr "Допуск толстых слоёв" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "У этого принтера нет профиля для сопла %1% мм. Проверьте ограничения высоты слоя сопла %2% в настройках принтера." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Этот принтер использует разные размеры сопла. Выберите размер сопла для печати поддержек и типы материалов для подложки и связующего слоя поддержек." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Уменьшать или увеличивать высоту слоя стенок подгоняемого материала, чтобы достичь высоты, совместимой с другим материалом стенок. Высоты вне ограничений высоты слоя подгоняемого материала никогда не используются: если в этом направлении нет допустимой высоты, стенки печатаются вместе на меньшей высоте, как обычно." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Какому из двух материалов стенок подгоняется высота слоя стенок, когда предпочитаемые высоты слоя не делятся нацело." diff --git a/localization/i18n/sv/Snapmaker_Orca_sv.po b/localization/i18n/sv/Snapmaker_Orca_sv.po index 9bfd4cef479..8115cfdda74 100644 --- a/localization/i18n/sv/Snapmaker_Orca_sv.po +++ b/localization/i18n/sv/Snapmaker_Orca_sv.po @@ -18259,4 +18259,220 @@ msgstr "" #~ "andra lager kan göra att modellen lossnar från byggplattan." #~ msgid "Orient the model" -#~ msgstr "Orientera modellen" \ No newline at end of file +#~ msgstr "Orientera modellen" +msgid "Adjust wall layer height" +msgstr "Justera väggarnas lagerhöjd" + +msgid "Adjusted walls" +msgstr "Justerade väggar" + +msgid "Adjustment direction" +msgstr "Justeringsriktning" + +msgid "Consistent" +msgstr "Konsekvent" + +msgid "Decrease" +msgstr "Minska" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament för att skriva ut bottenytan.\n\"Standard\" använder objektets/delens aktiva filament." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament för att skriva ut inre väggar.\n\"Standard\" använder objektets/delens aktiva filament." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament för att skriva ut inre solid ifyllnad.\n\"Standard\" använder objektets/delens aktiva filament." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Filament för att skriva ut inre sparsam ifyllnad.\n\"Standard\" använder objektets/delens aktiva filament." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Filament för att skriva ut yttre väggar.\n\"Standard\" använder objektets/delens aktiva filament." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Filament för att skriva ut support och rafts. ”Standard” betyder ingen specifik filament för support, och nuvarande filament används" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament för att skriva ut supportens anläggningsyta. ” Standard” betyder ingen specifik filament för supportens anläggningsyta, och nuvarande filament används" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Filament för att skriva ut toppytan.\n\"Standard\" använder objektets/delens aktiva filament." + +msgid "Fixed" +msgstr "Fast" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Hur aggressivt delar som tilldelats en extruder med tjockare föredragen lagerhöjd slås ihop till tjocka lager.\nKonsekvent: delar skrivs ut med högst två lagerhöjder - extruderns lagerhöjd där hela lagerserier får plats och objektets lagerhöjd överallt annars. Det ger de jämnaste väggarna.\nAdaptiv: serier kan också slås ihop vid mellanliggande multiplar av objektets lagerhöjd, så mer av delen skrivs ut med tjockare lager, till priset av band med varierande lagerhöjd på böjda kanter.\nFast: delar skrivs alltid ut med extruderns lagerhöjd, även där formen ändras tvärs de ihopslagna lagren eller hänger över; böjda kanter blir trappsteg och detaljer finare än de tjocka lagren går förlorade. Bara geometri som är för kort för ett helt tjockt lager (delarnas toppar och första lagret) skrivs ut tunnare." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Hur långt konturen av en del som tilldelats en extruder med tjockare föredragen lagerhöjd får driva i sidled över lagren i en tjock serie och ändå slås ihop, som procent av den extruderns nozzeldiameter. Högre värden slår ihop mer av böjda kanter till tjocka lager, till priset av grövre kantväggar: avvikelser upp till denna andel av nozzeldiametern sväljs av de tjocka strängarna." + +msgid "Increase" +msgstr "Öka" + +msgid "Inner walls" +msgstr "Inre väggar" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Lagerhöjd som denna extruder ska skriva ut med, för skrivare vars extrudrar har olika nozzelstorlekar. Den måste vara en heltalsmultipel av objektets lagerhöjd. En del vars alla delar följer denna extruder skrivs bara ut på vart N:e lager med motsvarande tjockare strängar, där geometrin tillåter det; annars faller den tillbaka till objektets lagerhöjd. När resten av delen inte kan följa med slås väggar tilldelade denna extruder ändå ihop på egen hand till denna höjd, helt täta toppytor absorberar de solida lagren under sig, och sparsam eller 100% tät ifyllnad slås ihop oberoende till denna höjd. 0 betyder att objektets lagerhöjd används." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Lagerhöjd som denna extruder ska skriva ut med: en heltalsmultipel av objektets lagerhöjd inom extruderns lagerhöjdsbegränsningar. Standard behåller objektets lagerhöjd." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Ingen extruder har en nozzel som matchar stödens nozzeldiameter." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Inget laddat filament matchar materialet för stöd-/raftbasen (och stödens nozzeldiameter)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Inget laddat filament matchar materialet för stöd-/raftgränssnittet (och stödens nozzeldiameter)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Nozzel %1%: lagerhöjdsbegränsningar satta till %2%-%3% mm, från \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "På skrivare vars extrudrar har olika nozzeldiametrar används bara filament med denna nozzeldiameter för att skriva ut stöd, raft och stödgränssnitt. Det håller filament med andra nozzelstorlekar - med sina andra linjebredder och lagerhöjdsbegränsningar - borta från stöden. Stödfilament som satts till ett icke-standardvärde måste matcha denna diameter. Värdet 0 låter vilket filament som helst skriva ut stöd." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Yttre och inre väggar skrivs automatiskt ut med sina egna föredragna lagerhöjder när den ena höjden är en heltalsmultipel av den andra. När höjderna inte delas jämnt justerar detta alternativ vägglagerhöjden för ett av de två väggfilamenten (valt nedan) till närmaste multipel eller delare av den andra, så att väggarna ändå kan delas upp. Den justerade höjden gäller bara det filamentets väggar; övriga delar behåller den föredragna lagerhöjden. Justeringar lämnar aldrig filamentets lagerhöjdsbegränsningar: finns ingen tillåten höjd i den valda riktningen skrivs väggarna ut tillsammans med den lägre höjden som vanligt." + +msgid "Outer walls" +msgstr "Yttre väggar" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Lagerhöjder per extruder stöds inte i spiralvasläge." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Lagerhöjder per extruder stöds inte tillsammans med gränssnittsskal." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Lagerhöjder per extruder stöds inte tillsammans med variabel lagerhöjd." + +msgid "Preferred layer height" +msgstr "Föredragen lagerhöjd" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Skriv bara ut stöd och raftbas med filament av denna materialtyp; extrudrar laddade med andra typer används inte till det. Verkar tillsammans med begränsningen av stödens nozzeldiameter. Lämna tomt för ingen begränsning; ett uttryckligen valt stöd-/raftbasfilament har fortfarande företräde." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Skriv bara ut stöd- och raftgränssnittet med filament av denna materialtyp; extrudrar laddade med andra typer används inte till det. Verkar tillsammans med begränsningen av stödens nozzeldiameter. Lämna tomt för ingen begränsning; ett uttryckligen valt stöd-/raftgränssnittsfilament har fortfarande företräde." + +msgid "Raft and support base" +msgstr "Raft och stödbas" + +msgid "Show legacy filament selection" +msgstr "Visa gammalt filamentval" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Vissa delar föredrar %1% mm lager, men nozzeln på filament %2% som skriver ut andra delar av delen är för liten för att extrudera den höjden. Dessa delar skrivs istället ut med objektets lagerhöjd." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Vissa delar föredrar %1% mm lager, men nozzeln på väggfilament %2% är för liten för att extrudera den höjden. Yttre och inre väggar skrivs ut tillsammans, så dessa väggar behåller objektets lagerhöjd. Tilldela båda väggtyperna filament med tillräckligt stora nozzlar." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Vissa delar skriver ut %1% mm lager med filament %2%, vars maximala lagerhöjd är %3% mm. Tilldela delens delar filament med den grövre nozzeln, höj filamentets maximala lagerhöjd eller acceptera att skriva ut över den." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Vissa delar skriver ut %1% mm lager med filament %2%, vars minsta lagerhöjd är %3% mm. Höj objektets lagerhöjd, använd ett filament med finare nozzel för dessa delar eller acceptera att skriva ut under extruderns minimum." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Vissa delar använder filament vars föredragna lagerhöjder inte alla kan uppfyllas: en del skriver ut sina delar med ett enda lagersteg (satt av dess väggfilament, eller av de andra delarnas överenskommelse när inget väggfilament har någon preferens); väggarna, toppytorna och ifyllnaden kan var för sig slås ihop till sin egen höjd när resten av delen inte kan följa dem, men de återstående delarna skrivs ut med delens steg." + +msgid "Support for mixed nozzle sizes" +msgstr "Stöd för blandade nozzelstorlekar" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Stöd kan skrivas ut med extrudrar med olika nozzeldiametrar. Ställ in stödens nozzeldiameter (eller uttryckliga stöd- och gränssnittsfilament) för att hålla stöden på en nozzelstorlek." + +msgid "Support nozzle diameter" +msgstr "Stödens nozzeldiameter" + +msgid "Support nozzle size" +msgstr "Stödens nozzelstorlek" + +msgid "Support/raft base material" +msgstr "Material för stöd-/raftbas" + +msgid "Support/raft interface material" +msgstr "Material för stöd-/raftgränssnitt" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "Ifyllnadens linjebredd på %1% mm är för liten för ifyllnad ihopslagen till %2% mm höga lager. Öka linjebredden eller sänk ifyllnadsfilamentets föredragna lagerhöjd." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "Linjebredden på %1% mm är för liten för extruderns lagerhöjd på %2% mm. Öka linjebredden eller sänk extruderns lagerhöjd." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Lagerhöjden för extruder %1% (%2% mm) kan inte överstiga dess nozzeldiameter." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Lagerhöjden för extruder %1% (%2% mm) ignoreras för vissa delar: den måste vara en heltalsmultipel av objektets lagerhöjd (%3% mm), inte under den, och får inte överstiga extruderns nozzeldiameter." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Lagerhöjden för extruder %1% (%2% mm) är mindre än objektets lagerhöjd (%3% mm). Sänk objektets lagerhöjd till den finaste extruderlagerhöjden." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Lagerhöjden för extruder %1% (%2% mm) måste vara en heltalsmultipel av objektets lagerhöjd (%3% mm)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Extruderns lägsta utskrivbara lagerhöjd. Begränsar minsta lagerhöjd när adaptiv lagerhöjd är aktiverad. Delar som skrivs ut med en tjockare föredragen extruderlagerhöjd faller heller aldrig under denna höjd (utom första lagret)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Den föredragna lagerhöjden för nozzel %1% går inte längre igenom den och har återställts till Standard." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Stöd-/raftbasfilamentet är inte av stöd-/raftbasmaterialet." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Stöd-/raftbasfilamentet skrivs ut med en nozzel som inte matchar stödens nozzeldiameter." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Stöd-/raftgränssnittsfilamentet är inte av stöd-/raftgränssnittsmaterialet." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Stöd-/raftgränssnittsfilamentet skrivs ut med en nozzel som inte matchar stödens nozzeldiameter." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Väggfilamenten för vissa delar föredrar olika lagerhöjder. Yttre och inre väggar skrivs ut tillsammans, så dessa väggar behåller objektets lagerhöjd. Tilldela båda väggtyperna filament som föredrar samma höjd för att skriva ut tjockare väggar." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Väggfilamenten för vissa delar föredrar olika lagerhöjder. Yttre och inre väggar skrivs ut tillsammans, så dessa väggar skrivs ut med den lägre höjden (%1% mm)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Vägglagerhöjden för filament %1% justerades från föredragna %2% mm till %3% mm så att yttre och inre väggar kan skrivas ut med kompatibla lagerhöjder (\"Justera väggarnas lagerhöjd\"). Bara detta filaments väggar skrivs ut med den justerade höjden; dess övriga delar behåller den föredragna." + +msgid "Thick layer regions" +msgstr "Områden med tjocka lager" + +msgid "Thick layer tolerance" +msgstr "Tolerans för tjocka lager" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Den här skrivaren har ingen profil för en %1% mm nozzel. Kontrollera lagerhöjdsbegränsningarna för nozzel %2% i skrivarinställningarna." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Den här skrivaren använder olika nozzelstorlekar. Välj nozzelstorleken som skriver ut stöden och filamenttyperna som används för raften och stödgränssnittet." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Om det justerade väggfilamentets vägglagerhöjd minskas eller ökas för att nå en höjd som är kompatibel med det andra väggfilamentet. Höjder utanför det justerade filamentets lagerhöjdsbegränsningar används aldrig: finns ingen tillåten höjd i denna riktning skrivs väggarna ut tillsammans med den lägre höjden som vanligt." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Vilket av de två väggfilamenten som får sin vägglagerhöjd justerad när de föredragna lagerhöjderna inte delas jämnt." diff --git a/localization/i18n/tr/Snapmaker_Orca_tr.po b/localization/i18n/tr/Snapmaker_Orca_tr.po index ee26650ea2a..96a50e7329d 100644 --- a/localization/i18n/tr/Snapmaker_Orca_tr.po +++ b/localization/i18n/tr/Snapmaker_Orca_tr.po @@ -21109,4 +21109,220 @@ msgstr "" #~ msgstr "" #~ "Lütfen geçerli değerleri girin:\n" #~ "başlangıç > 10 adım >= 0\n" -#~ "bitiş > başlangıç + adım)" \ No newline at end of file +#~ "bitiş > başlangıç + adım)" +msgid "Adjust wall layer height" +msgstr "Duvar katman yüksekliğini ayarla" + +msgid "Adjusted walls" +msgstr "Ayarlanan duvarlar" + +msgid "Adjustment direction" +msgstr "Ayarlama yönü" + +msgid "Consistent" +msgstr "Tutarlı" + +msgid "Decrease" +msgstr "Azalt" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Alt yüzeyi basmak için filament.\n\"Varsayılan\", nesnenin/parçanın etkin filamentini kullanır." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "İç duvarları basmak için filament.\n\"Varsayılan\", nesnenin/parçanın etkin filamentini kullanır." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "İç dolu dolguyu basmak için filament.\n\"Varsayılan\", nesnenin/parçanın etkin filamentini kullanır." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "İç seyrek dolguyu basmak için filament.\n\"Varsayılan\", nesnenin/parçanın etkin filamentini kullanır." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Dış duvarları basmak için filament.\n\"Varsayılan\", nesnenin/parçanın etkin filamentini kullanır." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Destek tabanını ve salı yazdırmak için filament. \"Varsayılan\", destek için belirli bir filamentin olmadığı ve mevcut filamentin kullanıldığı anlamına gelir." + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Filament baskı desteği arayüzü. \"Varsayılan\", destek arayüzü için özel bir filamentin olmadığı ve mevcut filamentin kullanıldığı anlamına gelir." + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Üst yüzeyi basmak için filament.\n\"Varsayılan\", nesnenin/parçanın etkin filamentini kullanır." + +msgid "Fixed" +msgstr "Sabit" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Daha kalın tercih edilen katman yüksekliğine sahip ekstrudere atanmış parçaların kalın katmanlara ne kadar agresif birleştirileceğini belirler.\nTutarlı: parçalar en fazla iki katman yüksekliğiyle basılır - tam katman dizilerinin sığdığı yerlerde ekstruder katman yüksekliği, diğer her yerde nesne katman yüksekliği. En düzgün duvarları verir.\nUyarlanabilir: diziler nesne katman yüksekliğinin ara katlarında da birleştirilebilir; parçanın daha büyük kısmı kalın katmanlarla basılır ancak kavisli parça sınırlarında değişen katman yüksekliği bantları oluşur.\nSabit: parçalar, şekil birleştirilen katmanlar boyunca değişse ya da sarksa bile her zaman ekstruder katman yüksekliğiyle basılır; kavisli sınırlar basamaklara dönüşür ve kalın katmanlardan ince ayrıntılar kaybolur. Yalnızca tam bir kalın katmana yetmeyen geometri (parça tepeleri ve ilk katman) daha ince basılır." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Daha kalın tercih edilen katman yüksekliğine sahip ekstrudere atanmış bir parçanın dış hattının, bir kalın dizinin katmanları boyunca yana ne kadar kayıp yine de birleştirilebileceğini, o ekstruderin nozul çapının yüzdesi olarak belirler. Daha yüksek değerler kavisli parça sınırlarının daha fazlasını kalın katmanlara birleştirir; bedeli daha pürüzlü sınır duvarlarıdır: nozul çapının bu oranına kadar sapmalar kalın ekstrüzyonlar tarafından yutulur." + +msgid "Increase" +msgstr "Artır" + +msgid "Inner walls" +msgstr "İç duvarlar" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Bu ekstruderin basması gereken katman yüksekliği; ekstruderlerinin nozul boyutları farklı olan yazıcılar içindir. Nesne katman yüksekliğinin tam katı olmalıdır. Tüm özellikleri bu ekstruderi izleyen bir parça, geometrisi izin verdiği yerlerde yalnızca her N. katmanda buna uygun daha kalın ekstrüzyonlarla basılır; diğer yerlerde nesne katman yüksekliğine döner. Parçanın kalanı izleyemediğinde, bu ekstrudere atanmış duvarlar yine de kendi başlarına bu yüksekliğe birleşir, tam yoğunluklu üst yüzeyler altlarındaki dolu katmanları emer ve seyrek ya da %100 yoğun dolgu bağımsız olarak bu yüksekliğe birleşir. 0, nesne katman yüksekliğini kullanmak demektir." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Bu ekstruderin basması gereken katman yüksekliği: bu ekstruderin katman yüksekliği sınırları içinde, nesne katman yüksekliğinin tam katı. Varsayılan, nesne katman yüksekliğini korur." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Destek nozul çapıyla eşleşen nozula sahip ekstruder yok." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Yüklü filamentlerden hiçbiri destek/raft taban malzemesiyle (ve destek nozul çapıyla) eşleşmiyor." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Yüklü filamentlerden hiçbiri destek/raft arayüz malzemesiyle (ve destek nozul çapıyla) eşleşmiyor." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Nozul %1%: katman yüksekliği sınırları \"%4%\" üzerinden %2%-%3% mm olarak ayarlandı." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "Ekstruderlerinin nozul çapları farklı olan yazıcılarda destek, raft ve destek arayüzünü basmak için yalnızca bu nozul çapındaki filamentler kullanılır. Bu, farklı hat genişlikleri ve katman yüksekliği sınırlarına sahip diğer nozul boyutlarındaki filamentleri desteğin dışında tutar. Varsayılan olmayan bir değere ayarlanmış destek filamentleri bu çapla eşleşmelidir. 0 değeri her filamentin destek basmasına izin verir." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Bir yükseklik diğerinin tam katı olduğunda dış ve iç duvarlar otomatik olarak kendi tercih edilen katman yükseklikleriyle basılır. Yükseklikler tam bölünmediğinde bu seçenek, iki duvar filamentinden birinin (aşağıda seçilen) duvar katman yüksekliğini diğerinin en yakın katına veya bölenine ayarlar; böylece duvarlar yine ayrılabilir. Ayarlanan yükseklik yalnızca o filamentin duvarlarına uygulanır; diğer özellikler tercih edilen katman yüksekliğini korur. Ayarlamalar filamentin katman yüksekliği sınırlarının dışına asla çıkmaz: seçilen yönde izin verilen bir yükseklik yoksa duvarlar her zamanki gibi daha düşük yükseklikte birlikte basılır." + +msgid "Outer walls" +msgstr "Dış duvarlar" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Ekstruder başına katman yükseklikleri spiral vazo modunda desteklenmez." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Ekstruder başına katman yükseklikleri arayüz kabuklarıyla birlikte desteklenmez." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Ekstruder başına katman yükseklikleri değişken katman yüksekliğiyle birlikte desteklenmez." + +msgid "Preferred layer height" +msgstr "Tercih edilen katman yüksekliği" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Destek ve raft tabanını yalnızca bu malzeme türündeki filamentlerle basar; başka türler yüklü ekstruderler bunun için kullanılmaz. Destek nozul çapı kısıtlamasıyla birlikte çalışır. Kısıtlama olmaması için boş bırakın; açıkça seçilmiş bir destek/raft taban filamenti yine önceliklidir." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Destek ve raft arayüzünü yalnızca bu malzeme türündeki filamentlerle basar; başka türler yüklü ekstruderler bunun için kullanılmaz. Destek nozul çapı kısıtlamasıyla birlikte çalışır. Kısıtlama olmaması için boş bırakın; açıkça seçilmiş bir destek/raft arayüz filamenti yine önceliklidir." + +msgid "Raft and support base" +msgstr "Raft ve destek tabanı" + +msgid "Show legacy filament selection" +msgstr "Eski filament seçimini göster" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Bazı parçalar %1% mm katmanları tercih ediyor, ancak parçanın diğer özelliklerini basan %2% filamentinin nozulu bu yüksekliği basamayacak kadar küçük. Bu parçalar bunun yerine nesne katman yüksekliğiyle basılır." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Bazı parçalar %1% mm katmanları tercih ediyor, ancak duvar filamenti %2%'nin nozulu bu yüksekliği basamayacak kadar küçük. Dış ve iç duvarlar birlikte basıldığından bu duvarlar nesne katman yüksekliğini korur. Her iki duvar özelliğini de yeterince büyük nozullu filamentlere atayın." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Bazı parçalar, maksimum katman yüksekliği %3% mm olan %2% filamentiyle %1% mm katmanlar basıyor. Parça özelliklerini daha kalın nozulun filamentlerine atayın, filamentin maksimum katman yüksekliğini artırın ya da üzerinde basmayı kabul edin." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Bazı parçalar, minimum katman yüksekliği %3% mm olan %2% filamentiyle %1% mm katmanlar basıyor. Nesne katman yüksekliğini artırın, bu özellikler için daha ince nozullu bir filament kullanın ya da ekstruder minimumunun altında basmayı kabul edin." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Bazı parçalar, tercih edilen katman yükseklikleri tümüyle karşılanamayan filamentler kullanıyor: bir parça özelliklerini tek bir katman adımıyla basar (duvar filamentleri belirler; hiçbir duvar filamentinin tercihi yoksa diğer özelliklerin uzlaşması belirler); parçanın kalanı izleyemediğinde duvarlar, üst yüzeyler ve dolgu her biri kendi yüksekliğine birleşebilir, ancak kalan özellikler parçanın adımıyla basılır." + +msgid "Support for mixed nozzle sizes" +msgstr "Karışık nozul boyutları için destek" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Destek, farklı nozul çaplarına sahip ekstruderlerle basılabilir. Desteği tek nozul boyutunda tutmak için destek nozul çapını (ya da açık destek ve arayüz filamentlerini) ayarlayın." + +msgid "Support nozzle diameter" +msgstr "Destek nozul çapı" + +msgid "Support nozzle size" +msgstr "Destek nozul boyutu" + +msgid "Support/raft base material" +msgstr "Destek/raft taban malzemesi" + +msgid "Support/raft interface material" +msgstr "Destek/raft arayüz malzemesi" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "%1% mm dolgu hat genişliği, %2% mm yüksekliğe birleştirilmiş dolgu için çok küçük. Hat genişliğini artırın ya da dolgu filamentinin tercih edilen katman yüksekliğini düşürün." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "%1% mm hat genişliği, ekstruderinin %2% mm katman yüksekliği için çok küçük. Hat genişliğini artırın ya da ekstruder katman yüksekliğini düşürün." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Ekstruder %1%'in katman yüksekliği (%2% mm) nozul çapını aşamaz." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Ekstruder %1%'in katman yüksekliği (%2% mm) bazı parçalar için yok sayılıyor: nesne katman yüksekliğinin (%3% mm) tam katı olmalı, onun altında olmamalı ve ekstruderin nozul çapını aşmamalıdır." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Ekstruder %1%'in katman yüksekliği (%2% mm) nesne katman yüksekliğinden (%3% mm) küçük. Nesne katman yüksekliğini en ince ekstruder katman yüksekliğine düşürün." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Ekstruder %1%'in katman yüksekliği (%2% mm) nesne katman yüksekliğinin (%3% mm) tam katı olmalıdır." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Ekstruderin basabileceği en düşük katman yüksekliği. Uyarlanabilir katman yüksekliği etkinken minimum katman yüksekliğini sınırlamak için kullanılır. Daha kalın tercih edilen ekstruder katman yüksekliğiyle basılan parçalar da asla bu yüksekliğin altına düşmez (ilk katman hariç)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Nozul %1%'in tercih edilen katman yüksekliği artık içinden geçemiyor ve Varsayılan'a sıfırlandı." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Destek/raft taban filamenti, destek/raft taban malzemesinden değil." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Destek/raft taban filamenti, destek nozul çapıyla eşleşmeyen bir nozulla basılıyor." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Destek/raft arayüz filamenti, destek/raft arayüz malzemesinden değil." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Destek/raft arayüz filamenti, destek nozul çapıyla eşleşmeyen bir nozulla basılıyor." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Bazı parçaların duvar filamentleri farklı katman yükseklikleri tercih ediyor. Dış ve iç duvarlar birlikte basıldığından bu duvarlar nesne katman yüksekliğini korur. Daha kalın duvarlar basmak için her iki duvar özelliğini de aynı yüksekliği tercih eden filamentlere atayın." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Bazı parçaların duvar filamentleri farklı katman yükseklikleri tercih ediyor. Dış ve iç duvarlar birlikte basıldığından bu duvarlar daha düşük yükseklikle (%1% mm) basılır." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Filament %1%'in duvar katman yüksekliği, dış ve iç duvarların uyumlu katman yüksekliklerinde basılabilmesi için tercih edilen %2% mm'den %3% mm'ye ayarlandı (\"Duvar katman yüksekliğini ayarla\"). Ayarlanan yükseklikle yalnızca bu filamentin duvarları basılır; diğer özellikleri tercih edileni korur." + +msgid "Thick layer regions" +msgstr "Kalın katman bölgeleri" + +msgid "Thick layer tolerance" +msgstr "Kalın katman toleransı" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Bu yazıcının %1% mm nozul için profili yok. Lütfen yazıcı ayarlarında nozul %2%'nin katman yüksekliği sınırlarını gözden geçirin." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Bu yazıcı farklı nozul boyutları kullanıyor. Desteği basacak nozul boyutunu ve raft ile destek arayüzü için kullanılacak filament türlerini seçin." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Ayarlanan duvar filamentinin duvar katman yüksekliğinin, diğer duvar filamentiyle uyumlu bir yüksekliğe ulaşmak için azaltılacağını mı yoksa artırılacağını mı belirler. Ayarlanan filamentin katman yüksekliği sınırlarının dışındaki yükseklikler asla kullanılmaz: bu yönde izin verilen bir yükseklik yoksa duvarlar her zamanki gibi daha düşük yükseklikte birlikte basılır." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Tercih edilen katman yükseklikleri tam bölünmediğinde iki duvar filamentinden hangisinin duvar katman yüksekliğinin ayarlanacağını belirler." diff --git a/localization/i18n/uk/Snapmaker_Orca_uk.po b/localization/i18n/uk/Snapmaker_Orca_uk.po index 32596f96fdf..4e067b850af 100644 --- a/localization/i18n/uk/Snapmaker_Orca_uk.po +++ b/localization/i18n/uk/Snapmaker_Orca_uk.po @@ -19343,4 +19343,220 @@ msgstr "" #~ msgstr "Користувача скасовано." #~ msgid "Set the brim type to \"painted\"" -#~ msgstr "Встановіть тип кайми на \"Мальована\"" \ No newline at end of file +#~ msgstr "Встановіть тип кайми на \"Мальована\"" +msgid "Adjust wall layer height" +msgstr "Підганяти висоту шару стінок" + +msgid "Adjusted walls" +msgstr "Підганяні стінки" + +msgid "Adjustment direction" +msgstr "Напрямок підгонки" + +msgid "Consistent" +msgstr "Однорідно" + +msgid "Decrease" +msgstr "Зменшувати" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "Філамент для друку нижньої поверхні.\n\"За замовчуванням\" використовує активний філамент об'єкта/деталі." + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "Філамент для друку внутрішніх стінок.\n\"За замовчуванням\" використовує активний філамент об'єкта/деталі." + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "Філамент для друку внутрішнього суцільного заповнення.\n\"За замовчуванням\" використовує активний філамент об'єкта/деталі." + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "Філамент для друку внутрішнього часткового заповнення.\n\"За замовчуванням\" використовує активний філамент об'єкта/деталі." + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "Філамент для друку зовнішніх стінок.\n\"За замовчуванням\" використовує активний філамент об'єкта/деталі." + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "Філамент для друку підтримок та підкладки. «За замовчуванням» означає відсутність конкретного філаменту для підтримок та використання поточного філаменту" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "Філамент для друку підтримки. «За замовчуванням» означає відсутність конкретного філаменту для друку підтримки, і використовується поточний філамент" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "Філамент для друку верхньої поверхні.\n\"За замовчуванням\" використовує активний філамент об'єкта/деталі." + +msgid "Fixed" +msgstr "Фіксовано" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "Наскільки агресивно деталі, призначені екструдеру з товщою бажаною висотою шару, об'єднуються в товсті шари.\nОднорідно: деталі друкуються щонайбільше двома висотами шару — висотою шару екструдера там, де вміщуються цілі серії шарів, і висотою шару об'єкта скрізь інде. Це дає найрівніші стінки.\nАдаптивно: серії можуть об'єднуватися і на проміжних кратних висоти шару об'єкта, тож більша частина деталі друкується товстими шарами — ціною смуг змінної висоти шару на криволінійних межах.\nФіксовано: деталі завжди друкуються висотою шару екструдера, навіть там, де форма змінюється поперек об'єднаних шарів або нависає; криволінійні межі перетворюються на сходинки, а деталі дрібніші за товсті шари втрачаються. Лише геометрія, закоротка для цілого товстого шару (верхівки деталей і перший шар), друкується тонше." + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "Наскільки контур деталі, призначеної екструдеру з товщою бажаною висотою шару, може зміщуватися вбік поперек шарів однієї товстої серії і все ж об'єднуватися — у відсотках діаметра сопла цього екструдера. Більші значення об'єднують більше криволінійних меж у товсті шари ціною грубіших стінок на межах: відхилення до цієї частки діаметра сопла поглинаються товстими лініями." + +msgid "Increase" +msgstr "Збільшувати" + +msgid "Inner walls" +msgstr "Внутрішні стінки" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "Висота шару, якою має друкувати цей екструдер; для принтерів, у яких екструдери мають різні розміри сопла. Має бути цілим кратним висоти шару об'єкта. Деталь, усі елементи якої слідують за цим екструдером, друкується лише на кожному N-му шарі відповідно товщими лініями там, де дозволяє її геометрія; в інших місцях використовується висота шару об'єкта. Коли решта деталі не може слідувати, стінки, призначені цьому екструдеру, все одно самостійно об'єднуються до цієї висоти, повністю щільні верхні поверхні поглинають суцільні шари під собою, а заповнення (часткове чи 100% щільності) об'єднується до цієї висоти незалежно. 0 — використовувати висоту шару об'єкта." + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "Висота шару, якою має друкувати цей екструдер: ціле кратне висоти шару об'єкта в межах обмежень висоти шару цього екструдера. За замовчуванням зберігається висота шару об'єкта." + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "Жоден екструдер не має сопла, що відповідає діаметру сопла підтримок." + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "Жоден завантажений філамент не відповідає матеріалу основи підтримок/підкладки (і діаметру сопла підтримок)." + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "Жоден завантажений філамент не відповідає матеріалу інтерфейсу підтримок/підкладки (і діаметру сопла підтримок)." + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "Сопло %1%: обмеження висоти шару встановлено на %2%-%3% мм із \"%4%\"." + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "На принтерах, у яких екструдери мають різні діаметри сопла, для друку підтримок, підкладки та інтерфейсу підтримок використовуються лише філаменти цього діаметра сопла. Це не пускає до підтримок філаменти інших розмірів сопла — з їхніми іншими ширинами ліній та обмеженнями висоти шару. Філаменти підтримок, задані не за замовчуванням, мають відповідати цьому діаметру. Значення 0 дозволяє друкувати підтримки будь-яким філаментом." + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "Зовнішні та внутрішні стінки автоматично друкуються своїми бажаними висотами шару, коли одна висота є цілим кратним іншої. Коли висоти не діляться націло, ця опція підганяє висоту шару стінок одного з двох філаментів стінок (обраного нижче) до найближчого кратного або дільника іншої, щоб стінки все ж могли розділитися. Підігнана висота стосується лише стінок цього філаменту; решта елементів зберігають бажану висоту шару. Підгонка ніколи не виходить за обмеження висоти шару філаменту: якщо в обраному напрямку немає дозволеної висоти, стінки друкуються разом на меншій висоті, як звичайно." + +msgid "Outer walls" +msgstr "Зовнішні стінки" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "Висоти шару для окремих екструдерів не підтримуються в режимі спіральної вази." + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "Висоти шару для окремих екструдерів не підтримуються разом з інтерфейсними оболонками." + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "Висоти шару для окремих екструдерів не підтримуються разом зі змінною висотою шару." + +msgid "Preferred layer height" +msgstr "Бажана висота шару" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "Друкувати підтримки та основу підкладки лише філаментами цього типу матеріалу; екструдери з іншими типами для цього не використовуються. Діє разом з обмеженням діаметра сопла підтримок. Залиште порожнім, щоб не обмежувати; явно обраний філамент основи підтримок/підкладки все одно має пріоритет." + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "Друкувати інтерфейс підтримок і підкладки лише філаментами цього типу матеріалу; екструдери з іншими типами для цього не використовуються. Діє разом з обмеженням діаметра сопла підтримок. Залиште порожнім, щоб не обмежувати; явно обраний філамент інтерфейсу все одно має пріоритет." + +msgid "Raft and support base" +msgstr "Підкладка та основа підтримок" + +msgid "Show legacy filament selection" +msgstr "Показати старий вибір філаменту" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "Деякі деталі віддають перевагу шарам %1% мм, але сопло філаменту %2%, що друкує інші елементи деталі, замале для видавлювання такої висоти. Ці деталі друкуються висотою шару об'єкта." + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "Деякі деталі віддають перевагу шарам %1% мм, але сопло філаменту стінок %2% замале для видавлювання такої висоти. Зовнішні та внутрішні стінки друкуються разом, тому ці стінки зберігають висоту шару об'єкта. Призначте обидва типи стінок філаментам із достатньо великими соплами." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "Деякі деталі друкують шари %1% мм філаментом %2%, максимальна висота шару якого %3% мм. Призначте елементи деталі філаментам грубішого сопла, збільште максимальну висоту шару філаменту або погодьтеся друкувати вище за неї." + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "Деякі деталі друкують шари %1% мм філаментом %2%, мінімальна висота шару якого %3% мм. Збільште висоту шару об'єкта, використайте для цих елементів філамент із тоншим соплом або погодьтеся друкувати нижче мінімуму екструдера." + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "Деякі деталі використовують філаменти, чиї бажані висоти шару неможливо задовольнити всі одразу: деталь друкує свої елементи одним кроком шару (який задають її філаменти стінок, а якщо жоден філамент стінок не має переваги — згода решти елементів); стінки, верхні поверхні та заповнення можуть об'єднуватися кожен до своєї висоти, коли решта деталі не може за ними слідувати, але решта елементів друкується кроком деталі." + +msgid "Support for mixed nozzle sizes" +msgstr "Підтримки за різних розмірів сопла" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "Підтримки можуть друкуватися екструдерами з різними діаметрами сопла. Задайте діаметр сопла підтримок (або явні філаменти підтримок та інтерфейсу), щоб тримати підтримки на одному розмірі сопла." + +msgid "Support nozzle diameter" +msgstr "Діаметр сопла підтримок" + +msgid "Support nozzle size" +msgstr "Розмір сопла підтримок" + +msgid "Support/raft base material" +msgstr "Матеріал основи підтримок/підкладки" + +msgid "Support/raft interface material" +msgstr "Матеріал інтерфейсу підтримок/підкладки" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "Ширина лінії заповнення %1% мм замала для заповнення, об'єднаного в шари заввишки %2% мм. Збільште ширину лінії або зменште бажану висоту шару філаменту заповнення." + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "Ширина лінії %1% мм замала для висоти шару %2% мм її екструдера. Збільште ширину лінії або зменште висоту шару екструдера." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "Висота шару екструдера %1% (%2% мм) не може перевищувати діаметр його сопла." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "Висота шару екструдера %1% (%2% мм) ігнорується для деяких деталей: вона має бути цілим кратним висоти шару об'єкта (%3% мм), не меншою за неї і не повинна перевищувати діаметр сопла екструдера." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "Висота шару екструдера %1% (%2% мм) менша за висоту шару об'єкта (%3% мм). Зменште висоту шару об'єкта до найтоншої висоти шару екструдера." + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "Висота шару екструдера %1% (%2% мм) має бути цілим кратним висоти шару об'єкта (%3% мм)." + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "Найменша друкована висота шару екструдера. Обмежує мінімальну висоту шару за адаптивної висоти шару. Деталі, друковані товщою бажаною висотою шару екструдера, також ніколи не опускаються нижче цієї висоти (крім першого шару)." + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "Бажана висота шару сопла %1% більше не проходить крізь нього і була скинута на \"За замовчуванням\"." + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "Філамент основи підтримок/підкладки не належить до матеріалу основи підтримок/підкладки." + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Філамент основи підтримок/підкладки друкується соплом, що не відповідає діаметру сопла підтримок." + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "Філамент інтерфейсу підтримок/підкладки не належить до матеріалу інтерфейсу підтримок/підкладки." + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "Філамент інтерфейсу підтримок/підкладки друкується соплом, що не відповідає діаметру сопла підтримок." + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "Філаменти стінок деяких деталей віддають перевагу різним висотам шару. Зовнішні та внутрішні стінки друкуються разом, тому ці стінки зберігають висоту шару об'єкта. Щоб друкувати товщі стінки, призначте обидва типи стінок філаментам з однаковою бажаною висотою." + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "Філаменти стінок деяких деталей віддають перевагу різним висотам шару. Зовнішні та внутрішні стінки друкуються разом, тому ці стінки друкуються меншою висотою (%1% мм)." + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "Висоту шару стінок філаменту %1% підігнано з бажаних %2% мм до %3% мм, щоб зовнішні та внутрішні стінки могли друкуватися сумісними висотами шару (\"Підганяти висоту шару стінок\"). Підігнаною висотою друкуються лише стінки цього філаменту; решта його елементів зберігають бажану." + +msgid "Thick layer regions" +msgstr "Області товстих шарів" + +msgid "Thick layer tolerance" +msgstr "Допуск товстих шарів" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "Цей принтер не має профілю для сопла %1% мм. Перевірте обмеження висоти шару сопла %2% в налаштуваннях принтера." + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "Цей принтер використовує різні розміри сопла. Виберіть розмір сопла для друку підтримок і типи філаментів для підкладки та інтерфейсу підтримок." + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "Зменшувати чи збільшувати висоту шару стінок підганяного філаменту, щоб досягти висоти, сумісної з іншим філаментом стінок. Висоти поза обмеженнями висоти шару підганяного філаменту ніколи не використовуються: якщо в цьому напрямку немає дозволеної висоти, стінки друкуються разом на меншій висоті, як звичайно." + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "Якому з двох філаментів стінок підганяється висота шару стінок, коли бажані висоти шару не діляться націло." diff --git a/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po b/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po index abef0c29816..89fac4781b3 100644 --- a/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po +++ b/localization/i18n/zh_CN/Snapmaker_Orca_zh_CN.po @@ -15348,4 +15348,220 @@ msgid "Other Colors" msgstr "其他颜色" msgid "Multiple Color" -msgstr "多色" \ No newline at end of file +msgstr "多色" +msgid "Adjust wall layer height" +msgstr "调整墙层高" + +msgid "Adjusted walls" +msgstr "被调整的墙" + +msgid "Adjustment direction" +msgstr "调整方向" + +msgid "Consistent" +msgstr "一致" + +msgid "Decrease" +msgstr "降低" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "用于打印底面的耗材。\n“默认”使用当前对象/零件的耗材。" + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "用于打印内墙的耗材。\n“默认”使用当前对象/零件的耗材。" + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "用于打印内部实心填充的耗材。\n“默认”使用当前对象/零件的耗材。" + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "用于打印内部稀疏填充的耗材。\n“默认”使用当前对象/零件的耗材。" + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "用于打印外墙的耗材。\n“默认”使用当前对象/零件的耗材。" + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "用于打印支撑基底和筏层的耗材。\n“默认”表示不为支撑指定特定耗材,使用当前耗材。" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "用于打印支撑面的耗材。\n“默认”表示不为支撑面指定特定耗材,使用当前耗材。" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "用于打印顶面的耗材。\n“默认”使用当前对象/零件的耗材。" + +msgid "Fixed" +msgstr "固定" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "决定分配给较厚首选层高挤出机的零件被合并为厚层的激进程度。\n一致:零件最多以两种层高打印——在能容纳完整厚层组的位置使用挤出机层高,其余位置使用对象层高。墙面最均匀。\n自适应:层组还可以按对象层高的中间倍数合并,零件更多部分以厚层打印,代价是曲面边界上出现层高交替的条带。\n固定:零件始终以挤出机层高打印,即使形状在合并的层之间变化或悬空;曲面边界变成台阶,比厚层更精细的细节会丢失。只有高度不足一个完整厚层的几何(零件顶部和首层)以较薄的层打印。" + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "分配给较厚首选层高挤出机的零件轮廓在一个厚层组内允许横向偏移多少仍可合并,以该挤出机喷嘴直径的百分比表示。数值越大,曲面边界被合并为厚层的比例越高,代价是边界墙面更粗糙:不超过喷嘴直径该比例的偏差会被厚挤出线吞掉。" + +msgid "Increase" +msgstr "提高" + +msgid "Inner walls" +msgstr "内墙" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "该挤出机应使用的层高,用于挤出机喷嘴规格不同的打印机。必须是对象层高的整数倍。若零件的所有特征都由该挤出机打印,则在几何允许处每N层打印一次并相应加厚挤出;其余位置回退到对象层高。当零件其余部分无法跟随时,分配给该挤出机的墙仍会单独合并到该层高,全密度顶面会吸收其下方的实心层,稀疏或100%密度的填充也会独立合并到该层高。0表示使用对象层高。" + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "该挤出机应使用的层高:对象层高的整数倍,且在该挤出机的层高限制内。默认保持对象层高。" + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "没有挤出机的喷嘴与支撑喷嘴直径匹配。" + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "没有已装载的耗材丝与支撑/筏层底部材料匹配(且符合支撑喷嘴直径)。" + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "没有已装载的耗材丝与支撑/筏层接触面材料匹配(且符合支撑喷嘴直径)。" + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "喷嘴%1%:层高限制已设为%2%-%3% mm,来自\"%4%\"。" + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "在挤出机喷嘴直径不同的打印机上,仅使用该喷嘴直径的耗材丝打印支撑、筏层和支撑面。这样可将其他喷嘴规格的耗材丝——线宽和层高限制都不同——挡在支撑之外。设置为非默认值的支撑耗材丝必须与该直径匹配。0表示允许任何耗材丝打印支撑。" + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "当一方层高是另一方的整数倍时,外墙和内墙会自动以各自的首选层高打印。当两者不能整除时,此选项将其中一个墙耗材丝(在下方选择)的墙层高调整为另一方最接近的倍数或约数,使墙仍能拆分。调整后的层高仅作用于该耗材丝的墙;其他特征保持首选层高。调整绝不超出该耗材丝的层高限制:若所选方向上没有允许的层高,墙照常以较低层高一起打印。" + +msgid "Outer walls" +msgstr "外墙" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "旋转花瓶模式不支持按挤出机设置层高。" + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "按挤出机设置层高与接触面外壳不兼容。" + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "按挤出机设置层高与可变层高不兼容。" + +msgid "Preferred layer height" +msgstr "首选层高" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "仅使用此材料类型的耗材丝打印支撑和筏层底部;装载其他类型的挤出机不用于此。与支撑喷嘴直径限制叠加生效。留空表示不限制;显式选择的支撑/筏层底部耗材丝仍然优先。" + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "仅使用此材料类型的耗材丝打印支撑面和筏层接触面;装载其他类型的挤出机不用于此。与支撑喷嘴直径限制叠加生效。留空表示不限制;显式选择的支撑/筏层接触面耗材丝仍然优先。" + +msgid "Raft and support base" +msgstr "筏层与支撑底部" + +msgid "Show legacy filament selection" +msgstr "显示旧版耗材丝选择" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "部分零件首选%1% mm层,但打印该零件其他特征的耗材丝%2%的喷嘴太小,无法挤出该层高。这些零件将改用对象层高打印。" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "部分零件首选%1% mm层,但墙耗材丝%2%的喷嘴太小,无法挤出该层高。外墙和内墙一起打印,因此这些墙保持对象层高。请将两种墙特征都分配给喷嘴足够大的耗材丝。" + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "部分零件使用耗材丝%2%打印%1% mm层,但其最大层高为%3% mm。请将零件特征分配给较粗喷嘴的耗材丝,提高该耗材丝的最大层高,或接受超限打印。" + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "部分零件使用耗材丝%2%打印%1% mm层,但其最小层高为%3% mm。请提高对象层高,为这些特征改用更细喷嘴的耗材丝,或接受低于挤出机最小值打印。" + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "部分零件所用耗材丝的首选层高无法全部满足:零件的各特征以同一层距打印(由其墙耗材丝决定;若墙耗材丝无偏好,则由其余特征协商决定);当零件其余部分无法跟随时,墙、顶面和填充可各自合并到自己的层高,但其余特征按零件的层距打印。" + +msgid "Support for mixed nozzle sizes" +msgstr "混合喷嘴规格的支撑" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "支撑可能会由不同喷嘴直径的挤出机打印。请设置支撑喷嘴直径(或显式指定支撑及支撑面耗材丝),使支撑保持单一喷嘴规格。" + +msgid "Support nozzle diameter" +msgstr "支撑喷嘴直径" + +msgid "Support nozzle size" +msgstr "支撑喷嘴规格" + +msgid "Support/raft base material" +msgstr "支撑/筏层底部材料" + +msgid "Support/raft interface material" +msgstr "支撑/筏层接触面材料" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "%1% mm的填充线宽对于合并到%2% mm高的填充层太小。请增大线宽,或降低填充耗材丝的首选层高。" + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "%1% mm的线宽对于其挤出机%2% mm的层高太小。请增大线宽或降低挤出机层高。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "挤出机%1%的层高(%2% mm)不能超过其喷嘴直径。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "挤出机%1%的层高(%2% mm)对部分零件被忽略:它必须是对象层高(%3% mm)的整数倍、不低于对象层高,且不得超过该挤出机的喷嘴直径。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "挤出机%1%的层高(%2% mm)小于对象层高(%3% mm)。请将对象层高降低到最细的挤出机层高。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "挤出机%1%的层高(%2% mm)必须是对象层高(%3% mm)的整数倍。" + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "该挤出机可打印的最低层高。启用自适应层高时用于限制最小层高。以较厚首选挤出机层高打印的零件也绝不会回落到该层高以下(首层除外)。" + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "喷嘴%1%的首选层高已无法通过该喷嘴,已重置为默认。" + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "支撑/筏层底部耗材丝不属于支撑/筏层底部材料。" + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "支撑/筏层底部耗材丝使用的喷嘴与支撑喷嘴直径不匹配。" + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "支撑/筏层接触面耗材丝不属于支撑/筏层接触面材料。" + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "支撑/筏层接触面耗材丝使用的喷嘴与支撑喷嘴直径不匹配。" + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "部分零件的墙耗材丝首选层高不同。外墙和内墙一起打印,因此这些墙保持对象层高。要打印更厚的墙,请将两种墙特征分配给首选层高相同的耗材丝。" + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "部分零件的墙耗材丝首选层高不同。外墙和内墙一起打印,因此这些墙以较低层高(%1% mm)打印。" + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "耗材丝%1%的墙层高已从首选的%2% mm调整为%3% mm,使外墙和内墙能以兼容的层高打印(\"调整墙层高\")。仅该耗材丝的墙使用调整后的层高;其其他特征保持首选层高。" + +msgid "Thick layer regions" +msgstr "厚层区域" + +msgid "Thick layer tolerance" +msgstr "厚层容差" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "此打印机没有%1% mm喷嘴的配置文件。请在打印机设置中检查喷嘴%2%的层高限制。" + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "此打印机使用不同的喷嘴规格。请选择打印支撑的喷嘴规格,以及筏层和支撑面使用的耗材丝类型。" + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "被调整的墙耗材丝的墙层高是降低还是提高,以达到与另一墙耗材丝兼容的层高。绝不使用超出被调整耗材丝层高限制的层高:若该方向上没有允许的层高,墙照常以较低层高一起打印。" + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "当首选层高不能整除时,两个墙耗材丝中哪一个的墙层高被调整。" diff --git a/localization/i18n/zh_TW/Snapmaker_Orca_zh_TW.po b/localization/i18n/zh_TW/Snapmaker_Orca_zh_TW.po index 5bf5cc3f5a8..27e75d24e72 100644 --- a/localization/i18n/zh_TW/Snapmaker_Orca_zh_TW.po +++ b/localization/i18n/zh_TW/Snapmaker_Orca_zh_TW.po @@ -18217,4 +18217,220 @@ msgstr "" #~ "設定為 -1 以停用。" #~ msgid ", ver: " -#~ msgstr ",版本" \ No newline at end of file +#~ msgstr ",版本" +msgid "Adjust wall layer height" +msgstr "調整牆層高" + +msgid "Adjusted walls" +msgstr "被調整的牆" + +msgid "Adjustment direction" +msgstr "調整方向" + +msgid "Consistent" +msgstr "一致" + +msgid "Decrease" +msgstr "降低" + +msgid "Filament to print bottom surface.\n\"Default\" uses the active object/part filament." +msgstr "用於列印底面的線材。\n「預設」會使用目前作用中的物件/零件線材。" + +msgid "Filament to print inner walls.\n\"Default\" uses the active object/part filament." +msgstr "用於列印內牆的線材。\n「預設」會使用目前作用中的物件/零件線材。" + +msgid "Filament to print internal solid infill.\n\"Default\" uses the active object/part filament." +msgstr "用於列印內部實心填充的線材。\n「預設」會使用目前作用中的物件/零件線材。" + +msgid "Filament to print internal sparse infill.\n\"Default\" uses the active object/part filament." +msgstr "用於列印內部稀疏填充的線材。\n「預設」會使用目前作用中的物件/零件線材。" + +msgid "Filament to print outer walls.\n\"Default\" uses the active object/part filament." +msgstr "用於列印外牆的線材。\n「預設」會使用目前作用中的物件/零件的線材。" + +msgid "Filament to print support base and raft.\n\"Default\" means no specific filament for support and current filament is used." +msgstr "用於列印支撐基座與筏層的線材。\n「預設」表示不為支撐指定特定線材,並使用目前的線材。" + +msgid "Filament to print support interface.\n\"Default\" means no specific filament for support interface and current filament is used." +msgstr "用於列印支撐面的線材。\n「預設」表示不為支撐面指定特定線材,並使用目前的線材。" + +msgid "Filament to print top surface.\n\"Default\" uses the active object/part filament." +msgstr "用於列印頂面的線材。\n「預設」使用目前作用中物件/零件的線材。" + +msgid "Fixed" +msgstr "固定" + +msgid "How aggressively object parts assigned to an extruder with a thicker preferred layer height are combined into thick layers.\nConsistent: parts print with at most two layer heights, the extruder layer height wherever whole runs of layers fit and the object layer height everywhere else. This gives the most uniform walls.\nAdaptive: runs may also be combined at intermediate multiples of the object layer height, so more of the part prints with thicker layers, at the price of bands of varying layer heights on curved part boundaries.\nFixed: parts always print at the extruder layer height, even where the shape changes across the combined layers or overhangs; curved boundaries turn into steps and detail finer than the thick layers is lost. Only geometry too short for a whole thick layer (part tops and the first layer) prints thinner." +msgstr "決定分配給較厚偏好層高擠出機的零件被合併為厚層的積極程度。\n一致:零件最多以兩種層高列印——在能容納完整厚層組的位置使用擠出機層高,其餘位置使用物件層高。牆面最均勻。\n自適應:層組也可以按物件層高的中間倍數合併,零件更多部分以厚層列印,代價是曲面邊界出現層高交替的帶狀痕跡。\n固定:零件始終以擠出機層高列印,即使形狀在合併的層之間變化或懸空;曲面邊界變成階梯,比厚層更精細的細節會遺失。只有高度不足一個完整厚層的幾何(零件頂部與首層)以較薄的層列印。" + +msgid "How far the outline of an object part assigned to an extruder with a thicker preferred layer height may drift sideways across the layers of one thick run and still be combined, as a percentage of that extruder's nozzle diameter. Higher values combine more of curved part boundaries into thick layers, at the price of rougher boundary walls: deviations up to this fraction of the nozzle diameter are swallowed by the thick extrusions." +msgstr "分配給較厚偏好層高擠出機的零件輪廓在一個厚層組內允許橫向偏移多少仍可合併,以該擠出機噴嘴直徑的百分比表示。數值越大,曲面邊界被合併為厚層的比例越高,代價是邊界牆面更粗糙:不超過噴嘴直徑該比例的偏差會被厚擠出線吞掉。" + +msgid "Increase" +msgstr "提高" + +msgid "Inner walls" +msgstr "內牆" + +#, no-c-format, no-boost-format +msgid "Layer height this extruder should print with, used for printers whose extruders have different nozzle sizes. It must be an integer multiple of the object layer height. A part whose features all follow this extruder prints only on every Nth layer with correspondingly thicker extrusions, wherever its geometry allows it; elsewhere it falls back to the object layer height. When the rest of the part cannot follow, walls assigned to this extruder still combine to this height on their own, full-density top surfaces absorb the solid layers below them, and sparse or 100% dense infill combines to this height independently. 0 means to use the object layer height." +msgstr "該擠出機應使用的層高,用於擠出機噴嘴規格不同的印表機。必須是物件層高的整數倍。若零件的所有特徵都由該擠出機列印,則在幾何允許處每N層列印一次並相應加厚擠出;其餘位置回退到物件層高。當零件其餘部分無法跟隨時,分配給該擠出機的牆仍會單獨合併到該層高,全密度頂面會吸收其下方的實心層,稀疏或100%密度的填充也會獨立合併到該層高。0表示使用物件層高。" + +msgid "Layer height this extruder should print with: an integer multiple of the object layer height within this extruder's layer height limits. Default keeps the object layer height." +msgstr "該擠出機應使用的層高:物件層高的整數倍,且在該擠出機的層高限制內。預設保持物件層高。" + +msgid "No extruder has a nozzle matching the support nozzle diameter." +msgstr "沒有擠出機的噴嘴與支撐噴嘴直徑相符。" + +msgid "No loaded filament matches the support/raft base material (and the support nozzle diameter)." +msgstr "沒有已裝載的線材與支撐/筏層底部材料相符(且符合支撐噴嘴直徑)。" + +msgid "No loaded filament matches the support/raft interface material (and the support nozzle diameter)." +msgstr "沒有已裝載的線材與支撐/筏層接觸面材料相符(且符合支撐噴嘴直徑)。" + +#, no-c-format, boost-format +msgid "Nozzle %1%: layer height limits set to %2%-%3% mm, from \"%4%\"." +msgstr "噴嘴%1%:層高限制已設為%2%-%3% mm,來自\"%4%\"。" + +msgid "On printers whose extruders have different nozzle diameters, only filaments of this nozzle diameter are used to print support, raft and support interface. This keeps filaments of other nozzle sizes - with their different line widths and layer height limits - out of the support. Support filaments set to a non-default value must match this diameter. Value 0 allows any filament to print support." +msgstr "在擠出機噴嘴直徑不同的印表機上,僅使用該噴嘴直徑的線材列印支撐、筏層與支撐面。這樣可將其他噴嘴規格的線材——線寬與層高限制都不同——擋在支撐之外。設定為非預設值的支撐線材必須與該直徑相符。0表示允許任何線材列印支撐。" + +msgid "Outer and inner walls automatically print at their own preferred layer heights when one height is an integer multiple of the other. When the heights do not divide evenly, this option adjusts the wall layer height of one of the two wall filaments (chosen below) to the nearest multiple or divisor of the other, so the walls can still split. The adjusted height only applies to that filament's walls; other features keep the preferred layer height. Adjustments never leave the filament's layer height limits: if no allowed height exists in the chosen direction, the walls print together at the lower height as usual." +msgstr "當一方層高是另一方的整數倍時,外牆與內牆會自動以各自的偏好層高列印。當兩者不能整除時,此選項將其中一個牆線材(在下方選擇)的牆層高調整為另一方最接近的倍數或約數,使牆仍能拆分。調整後的層高僅作用於該線材的牆;其他特徵保持偏好層高。調整絕不超出該線材的層高限制:若所選方向上沒有允許的層高,牆照常以較低層高一起列印。" + +msgid "Outer walls" +msgstr "外牆" + +msgid "Per-extruder layer heights are not supported in spiral vase mode." +msgstr "螺旋花瓶模式不支援按擠出機設定層高。" + +msgid "Per-extruder layer heights are not supported together with interface shells." +msgstr "按擠出機設定層高與接觸面外殼不相容。" + +msgid "Per-extruder layer heights are not supported together with variable layer height." +msgstr "按擠出機設定層高與可變層高不相容。" + +msgid "Preferred layer height" +msgstr "偏好層高" + +msgid "Print the support and raft base only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft base filament still takes precedence." +msgstr "僅使用此材料類型的線材列印支撐與筏層底部;裝載其他類型的擠出機不用於此。與支撐噴嘴直徑限制疊加生效。留空表示不限制;明確選擇的支撐/筏層底部線材仍然優先。" + +msgid "Print the support and raft interface only with filaments of this material type; extruders loaded with other types are not used for it. Combines with the support nozzle diameter restriction. Leave empty for no restriction; an explicitly selected support/raft interface filament still takes precedence." +msgstr "僅使用此材料類型的線材列印支撐面與筏層接觸面;裝載其他類型的擠出機不用於此。與支撐噴嘴直徑限制疊加生效。留空表示不限制;明確選擇的支撐/筏層接觸面線材仍然優先。" + +msgid "Raft and support base" +msgstr "筏層與支撐底部" + +msgid "Show legacy filament selection" +msgstr "顯示舊版線材選擇" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of filament %2% printing other features of the part is too small to extrude that height. These parts print with the object layer height instead." +msgstr "部分零件偏好%1% mm層,但列印該零件其他特徵的線材%2%的噴嘴太小,無法擠出該層高。這些零件將改用物件層高列印。" + +#, no-c-format, boost-format +msgid "Some object parts prefer %1% mm layers, but the nozzle of wall filament %2% is too small to extrude that height. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments with large enough nozzles." +msgstr "部分零件偏好%1% mm層,但牆線材%2%的噴嘴太小,無法擠出該層高。外牆與內牆一起列印,因此這些牆保持物件層高。請將兩種牆特徵都分配給噴嘴夠大的線材。" + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose maximum layer height is %3% mm. Assign the part's features to filaments of the coarser nozzle, raise the filament's maximum layer height, or accept printing above it." +msgstr "部分零件使用線材%2%列印%1% mm層,但其最大層高為%3% mm。請將零件特徵分配給較粗噴嘴的線材,提高該線材的最大層高,或接受超限列印。" + +#, no-c-format, boost-format +msgid "Some object parts print %1% mm layers with filament %2% whose minimum layer height is %3% mm. Raise the object layer height, use a filament with a finer nozzle for these features, or accept printing below the extruder's minimum." +msgstr "部分零件使用線材%2%列印%1% mm層,但其最小層高為%3% mm。請提高物件層高,為這些特徵改用更細噴嘴的線材,或接受低於擠出機最小值列印。" + +msgid "Some object parts use filaments whose preferred layer heights cannot all be honored: a part prints its features with one layer pitch (set by its wall filaments, or by the other features' agreement when no wall filament has a preference); the walls, the top surfaces and the infill can each combine to their own height when the rest of the part cannot follow them, but the remaining features print with the part's pitch." +msgstr "部分零件所用線材的偏好層高無法全部滿足:零件的各特徵以同一層距列印(由其牆線材決定;若牆線材無偏好,則由其餘特徵協商決定);當零件其餘部分無法跟隨時,牆、頂面與填充可各自合併到自己的層高,但其餘特徵按零件的層距列印。" + +msgid "Support for mixed nozzle sizes" +msgstr "混合噴嘴規格的支撐" + +msgid "Support may print with extruders of differing nozzle diameters. Set the support nozzle diameter (or explicit support and interface filaments) to keep the support on one nozzle size." +msgstr "支撐可能會由不同噴嘴直徑的擠出機列印。請設定支撐噴嘴直徑(或明確指定支撐及支撐面線材),使支撐保持單一噴嘴規格。" + +msgid "Support nozzle diameter" +msgstr "支撐噴嘴直徑" + +msgid "Support nozzle size" +msgstr "支撐噴嘴規格" + +msgid "Support/raft base material" +msgstr "支撐/筏層底部材料" + +msgid "Support/raft interface material" +msgstr "支撐/筏層接觸面材料" + +#, no-c-format, boost-format +msgid "The %1% mm infill line width is too small for infill combined to %2% mm high layers. Increase the line width or lower the preferred layer height of the infill filament." +msgstr "%1% mm的填充線寬對於合併到%2% mm高的填充層太小。請增大線寬,或降低填充線材的偏好層高。" + +#, no-c-format, boost-format +msgid "The %1% mm line width is too small for the %2% mm layer height of its extruder. Increase the line width or lower the extruder layer height." +msgstr "%1% mm的線寬對於其擠出機%2% mm的層高太小。請增大線寬或降低擠出機層高。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) cannot exceed its nozzle diameter." +msgstr "擠出機%1%的層高(%2% mm)不能超過其噴嘴直徑。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is ignored for some object parts: it must be an integer multiple of the object layer height (%3% mm), not below it, and must not exceed the extruder's nozzle diameter." +msgstr "擠出機%1%的層高(%2% mm)對部分零件被忽略:它必須是物件層高(%3% mm)的整數倍、不低於物件層高,且不得超過該擠出機的噴嘴直徑。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) is smaller than the object layer height (%3% mm). Lower the object layer height to the finest extruder layer height." +msgstr "擠出機%1%的層高(%2% mm)小於物件層高(%3% mm)。請將物件層高降低到最細的擠出機層高。" + +#, no-c-format, boost-format +msgid "The layer height of extruder %1% (%2% mm) must be an integer multiple of the object layer height (%3% mm)." +msgstr "擠出機%1%的層高(%2% mm)必須是物件層高(%3% mm)的整數倍。" + +msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height. Parts printed with a thicker preferred extruder layer height never fall back below this height either (the first layer excepted)." +msgstr "該擠出機可列印的最低層高。啟用自適應層高時用於限制最小層高。以較厚偏好擠出機層高列印的零件也絕不會回落到該層高以下(首層除外)。" + +#, no-c-format, boost-format +msgid "The preferred layer height of nozzle %1% no longer fits through it and was reset to Default." +msgstr "噴嘴%1%的偏好層高已無法通過該噴嘴,已重設為預設。" + +msgid "The support/raft base filament is not of the support/raft base material." +msgstr "支撐/筏層底部線材不屬於支撐/筏層底部材料。" + +msgid "The support/raft base filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "支撐/筏層底部線材使用的噴嘴與支撐噴嘴直徑不相符。" + +msgid "The support/raft interface filament is not of the support/raft interface material." +msgstr "支撐/筏層接觸面線材不屬於支撐/筏層接觸面材料。" + +msgid "The support/raft interface filament prints with a nozzle that does not match the support nozzle diameter." +msgstr "支撐/筏層接觸面線材使用的噴嘴與支撐噴嘴直徑不相符。" + +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls keep the object layer height. Assign both wall features to filaments preferring the same height to print thicker walls." +msgstr "部分零件的牆線材偏好層高不同。外牆與內牆一起列印,因此這些牆保持物件層高。要列印更厚的牆,請將兩種牆特徵分配給偏好層高相同的線材。" + +#, no-c-format, boost-format +msgid "The wall filaments of some object parts prefer different layer heights. Outer and inner walls print together, so these walls print with the lower height (%1% mm)." +msgstr "部分零件的牆線材偏好層高不同。外牆與內牆一起列印,因此這些牆以較低層高(%1% mm)列印。" + +#, no-c-format, boost-format +msgid "The wall layer height of filament %1% was adjusted from the preferred %2% mm to %3% mm so the outer and inner walls can print at compatible layer heights (\"Adjust wall layer height\"). Only this filament's walls print the adjusted height; its other features keep the preferred one." +msgstr "線材%1%的牆層高已從偏好的%2% mm調整為%3% mm,使外牆與內牆能以相容的層高列印(\"調整牆層高\")。僅該線材的牆使用調整後的層高;其其他特徵保持偏好層高。" + +msgid "Thick layer regions" +msgstr "厚層區域" + +msgid "Thick layer tolerance" +msgstr "厚層容差" + +#, no-c-format, boost-format +msgid "This printer has no profile for a %1% mm nozzle. Please review the layer height limits of nozzle %2% in the printer settings." +msgstr "此印表機沒有%1% mm噴嘴的設定檔。請在印表機設定中檢查噴嘴%2%的層高限制。" + +msgid "This printer uses different nozzle sizes. Select the nozzle size that prints the support, and the filament types used for the raft and the support interface." +msgstr "此印表機使用不同的噴嘴規格。請選擇列印支撐的噴嘴規格,以及筏層與支撐面使用的線材類型。" + +msgid "Whether the adjusted wall filament's wall layer height is decreased or increased to reach a height compatible with the other wall filament. Heights outside the adjusted filament's layer height limits are never used: if no allowed height exists in this direction, the walls print together at the lower height as usual." +msgstr "被調整的牆線材的牆層高是降低還是提高,以達到與另一牆線材相容的層高。絕不使用超出被調整線材層高限制的層高:若該方向上沒有允許的層高,牆照常以較低層高一起列印。" + +msgid "Which of the two wall filaments gets its wall layer height adjusted when the preferred layer heights do not divide evenly." +msgstr "當偏好層高不能整除時,兩個牆線材中哪一個的牆層高被調整。" From 9ba183b65ce510e61c0160739f7c3568f184507a Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:45:17 +0000 Subject: [PATCH 19/43] Translate the support dialog's OK / Cancel buttons --- src/slic3r/GUI/ConfigManipulation.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index cf7003aafea..03d85ad080e 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -1071,6 +1071,10 @@ int ConfigManipulation::show_support_filament_dialog(DynamicPrintConfig* config, auto *base_choice = add_choice(_(L("Raft and support base")), type_items, type_selection("support_base_material")); auto *interface_choice = add_choice(_(L("Support interface")), type_items, type_selection("support_interface_material")); sizer->Add(dlg.CreateSeparatedButtonSizer(wxOK | wxCANCEL), 0, wxEXPAND | wxALL, 10); + if (wxWindow *btn = dlg.FindWindow(wxID_OK); btn != nullptr) + btn->SetLabel(_(L("OK"))); + if (wxWindow *btn = dlg.FindWindow(wxID_CANCEL); btn != nullptr) + btn->SetLabel(_(L("Cancel"))); dlg.SetSizerAndFit(sizer); dlg.CentreOnScreen(); const int answer = dlg.ShowModal(); From a0ec6a591da2ccf598a9128a311a0e734999a34f Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Wed, 5 Aug 2026 07:49:53 +0000 Subject: [PATCH 20/43] Support materials for every printer, legacy selectors behind a toggle Show the support/raft base and interface material options for every printer, not only mixed-nozzle ones, so multi-filament setups with a single nozzle size can use them too; they stay visible like the other support rows and merely disable while neither support nor a skirt needs them. The legacy base/interface filament selectors are hidden unless the "Show legacy filament selection" toggle is on. Ticking the toggle takes effect immediately, and opening a 3mf project that assigns either selector - globally, per object, per part or per height range - switches the toggle on, so explicit selections from older projects stay visible and editable. Nothing else changes the toggle. Also fix a crash to desktop after visiting the Support page: the toggle checkbox is cached to keep its state in sync, but the page's controls are destroyed when another page activates, so the cached pointer dangled; it now clears on wxEVT_DESTROY. --- src/slic3r/GUI/ConfigManipulation.cpp | 16 ++++++++-------- src/slic3r/GUI/Plater.cpp | 20 ++++++++++++++++++++ src/slic3r/GUI/Tab.cpp | 18 +++++++++++++++--- src/slic3r/GUI/Tab.hpp | 1 + 4 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 03d85ad080e..ed3aece171d 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -761,14 +761,14 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("support_filament", have_support_material || have_skirt); // ORCA: support_nozzle_diameter only applies to printers whose extruders have differing - // nozzle diameters. There the support filament dialog owns the base/interface choice and the - // legacy selectors hide behind the "Show legacy filament selection" toggle, staying editable - // for older projects. - const bool mixed_nozzle_sizes = printer_has_mixed_nozzle_sizes(); - toggle_line("support_nozzle_diameter", have_support_material && mixed_nozzle_sizes); - toggle_line("support_base_material", have_support_material && mixed_nozzle_sizes); - toggle_line("support_interface_material", have_support_material && mixed_nozzle_sizes); - const bool legacy_support_selectors = ! mixed_nozzle_sizes || wxGetApp().app_config->get_bool("show_legacy_support_filament"); + // nozzle diameters; the material options serve any multi-filament setup and stay visible + // like the other support rows. The legacy base/interface selectors show only while the + // "Show legacy filament selection" toggle is on; opening a 3mf project with an assigned + // selector switches the toggle on (see Plater's project loading). + toggle_line("support_nozzle_diameter", have_support_material && printer_has_mixed_nozzle_sizes()); + toggle_field("support_base_material", have_support_material || have_skirt); + toggle_field("support_interface_material", have_support_material); + const bool legacy_support_selectors = wxGetApp().app_config->get_bool("show_legacy_support_filament"); toggle_line("support_filament", legacy_support_selectors); toggle_line("support_interface_filament", legacy_support_selectors); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 7bd637455dc..2e3b4b738b4 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10977,6 +10977,26 @@ std::vector Plater::priv::load_files(const std::vector& input_ if (wipe_tower_y_opt) file_wipe_tower_y = *wipe_tower_y_opt; + // ORCA: a project carrying an explicit legacy support filament + // selection reveals the legacy selectors in the Support page. + { + auto assigned = [](const DynamicPrintConfig &c) { + const auto *base = c.option("support_filament"); + const auto *intf = c.option("support_interface_filament"); + return (base != nullptr && base->value > 0) || (intf != nullptr && intf->value > 0); + }; + bool legacy_assigned = assigned(config); + for (const ModelObject *object : model.objects) { + legacy_assigned |= assigned(object->config.get()); + for (const ModelVolume *volume : object->volumes) + legacy_assigned |= assigned(volume->config.get()); + for (const auto &range : object->layer_config_ranges) + legacy_assigned |= assigned(range.second.get()); + } + if (legacy_assigned) + wxGetApp().app_config->set_bool("show_legacy_support_filament", true); + } + preset_bundle->load_config_model(filename.string(), std::move(config), file_version); ConfigOption* bed_type_opt = preset_bundle->project_config.option("curr_bed_type"); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 8b5c1f7e887..47a3cd58466 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2517,15 +2517,24 @@ void TabPrint::build() optgroup->append_single_option_line("support_filament", "support_settings_filament#base"); optgroup->append_single_option_line("support_interface_filament", "support_settings_filament#interface"); optgroup->append_single_option_line("support_interface_not_for_body", "support_settings_filament#avoid-interface-filament-for-base"); - // ORCA: on mixed-nozzle printers the support filament dialog owns the base/interface - // choice; this toggle reveals the legacy selectors above for older projects. + // ORCA: the support material options own the base/interface choice; this toggle + // reveals the legacy selectors above for older projects. auto legacy_support_toggle = [this](wxWindow* parent) { auto *sizer = new wxBoxSizer(wxHORIZONTAL); - auto *check = new ::CheckBox(parent); + auto *check = m_legacy_support_check = new ::CheckBox(parent); check->SetValue(wxGetApp().app_config->get_bool("show_legacy_support_filament")); + // Page controls are destroyed when another page activates; drop the cached pointer. + check->Bind(wxEVT_DESTROY, [this, check](wxWindowDestroyEvent &evt) { + if (m_legacy_support_check == check) + m_legacy_support_check = nullptr; + evt.Skip(); + }); check->Bind(wxEVT_TOGGLEBUTTON, [this](wxCommandEvent &evt) { wxGetApp().app_config->set_bool("show_legacy_support_filament", evt.IsChecked()); update(); + if (m_active_page != nullptr) + m_active_page->update_visibility(m_mode, true); + m_page_view->GetParent()->Layout(); evt.Skip(); }); sizer->Add(check, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 4); @@ -2761,6 +2770,9 @@ void TabPrint::toggle_options() } m_config_manipulation.toggle_print_fff_options(m_config, m_type < Preset::TYPE_COUNT); + // The visibility pass may have switched the legacy toggle on for a loaded selection. + if (m_legacy_support_check != nullptr) + m_legacy_support_check->SetValue(wxGetApp().app_config->get_bool("show_legacy_support_filament")); Field *field = m_active_page->get_field("support_style"); auto support_type = m_config->opt_enum("support_type"); diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index aa33cc93048..6a68f440b72 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -459,6 +459,7 @@ class TabPrint : public Tab private: ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr; + ::CheckBox* m_legacy_support_check = nullptr; }; class TabPrintModel : public TabPrint From 10e508c01e658c96fa0dc006c2ac3838fe0ed31b Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Wed, 19 Aug 2026 09:45:16 +0200 Subject: [PATCH 21/43] Fix crash when right-clicking the build plate check_volumes_outside_state() dereferenced its ObjectFilamentResults argument unconditionally, but several callers use the default null argument (e.g. plate validation triggered from the canvas context-menu path). Route null callers to a local results object. Also guard the per-extruder printable-area lookup in the unprintable-error text builder: not every printer profile defines extruder_printable_area. --- src/slic3r/GUI/GLCanvas3D.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp index ee4ca170521..344d2858470 100644 --- a/src/slic3r/GUI/GLCanvas3D.cpp +++ b/src/slic3r/GUI/GLCanvas3D.cpp @@ -1539,7 +1539,9 @@ static std::pair construct_extruder_unprintable_error(ObjectFilament left_z_max = (float) printable_height_option->values[0]; right_z_max = (float) printable_height_option->values[1]; } - std::vector printable_areas = preset.config.option("extruder_printable_area")->values; + // Not every printer profile defines per-extruder printable areas. + auto printable_area_option = preset.config.option("extruder_printable_area"); + std::vector printable_areas = printable_area_option ? printable_area_option->values : std::vector(); if (printable_areas.size() == 2 && printable_areas[0].size() == 4) { left_x_min = printable_areas[0][0][0]; left_y_min = printable_areas[0][0][1]; @@ -1591,6 +1593,12 @@ ModelInstanceEPrintVolumeState GLCanvas3D::check_volumes_outside_state(ObjectFil //assert(m_initialized); ModelInstanceEPrintVolumeState state; + // Several callers (e.g. Plater::validate_current_plate) use the default null + // argument; route them to a local so the error-text builders below always + // dereference a valid object. + ObjectFilamentResults local_results; + if (object_results == nullptr) + object_results = &local_results; m_volumes.check_outside_state(m_bed.build_volume(), &state, object_results); construct_error_string(*object_results, get_object_clashed_text()); From 9849cb856b37ba65f988ac4b914ca8abc3d1b610 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:22:57 +0200 Subject: [PATCH 22/43] Harden GUI paths against crashes found by code audit - Flush-volume auto-calculation: iterate physical filaments only (the flush matrix and minimum flush volumes are sized by the physical count) and clamp all matrix accesses. - Plate filament-to-extruder mapping: bounds-check the filament map and physical extruder map lookups; return -1 for out-of-range ids. - Plate list import: stop when the plate limit is reached instead of indexing with a failed create result. - Printer preset switch: tolerate an unknown target preset or missing extruder-variant option; default to not transferring variants. - Object list: clear the real UI-to-3D volume map entry (the loop-by- value clear was a no-op), and bounds-check remapped volume indices before dereferencing. --- src/slic3r/GUI/GUI_ObjectList.cpp | 25 ++++++++++++++----------- src/slic3r/GUI/PartPlate.cpp | 11 ++++++++++- src/slic3r/GUI/Plater.cpp | 11 ++++++++--- src/slic3r/GUI/Tab.cpp | 5 +++-- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index 03c3c0d5eab..3faff33ca82 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -1186,9 +1186,11 @@ void ObjectList::update_filament_in_config(const wxDataViewItem& item) const int obj_idx = m_objects_model->GetIdByItem(m_objects_model->GetObject(item)); if (item_type & itVolume){ const int ui_volume_idx = m_objects_model->GetVolumeIdByItem(item); - if (obj_idx < 0 || ui_volume_idx < 0) + if (obj_idx < 0 || size_t(obj_idx) >= m_objects->size() || ui_volume_idx < 0) return; int volume_in3d_idx = m_objects_model->get_real_volume_index_in_3d(obj_idx,ui_volume_idx); + if (volume_in3d_idx < 0 || size_t(volume_in3d_idx) >= (*m_objects)[obj_idx]->volumes.size()) + return; config = &(*m_objects)[obj_idx]->volumes[volume_in3d_idx]->config; } else if (item_type & itLayer) @@ -4225,11 +4227,11 @@ wxDataViewItemArray ObjectList::add_volumes_to_object_in_list(size_t obj_idx, st int volume_idx{-1}; auto& ui_and_3d_volume_map = m_objects_model->get_ui_and_3d_volume_map(); - for (auto item : ui_and_3d_volume_map) { - if (item.first == obj_idx) { - item.second.clear(); - } - } + // Clear the actual map entry; iterating by value only cleared a copy, + // leaving stale volume indices that later cause out-of-range accesses. + auto map_it = ui_and_3d_volume_map.find(obj_idx); + if (map_it != ui_and_3d_volume_map.end()) + map_it->second.clear(); int ui_volume_idx = 0; for (const ModelVolume *volume : object->volumes) { ++volume_idx; @@ -6653,11 +6655,12 @@ void ObjectList::set_extruder_for_selected_items(const int extruder) const int obj_idx = m_objects_model->GetObjectIdByItem(item); int vol_idx = m_objects_model->GetVolumeIdByItem(item); vol_idx = m_objects_model->get_real_volume_index_in_3d(obj_idx, vol_idx); - if ((obj_idx < m_objects->size()) && (obj_idx < (*m_objects)[obj_idx]->volumes.size())) { - auto volume_type = (*m_objects)[obj_idx]->volumes[vol_idx]->type(); - if (volume_type != ModelVolumeType::MODEL_PART && volume_type != ModelVolumeType::PARAMETER_MODIFIER) - continue; - } + if (obj_idx < 0 || size_t(obj_idx) >= m_objects->size() || + vol_idx < 0 || size_t(vol_idx) >= (*m_objects)[obj_idx]->volumes.size()) + continue; + auto volume_type = (*m_objects)[obj_idx]->volumes[vol_idx]->type(); + if (volume_type != ModelVolumeType::MODEL_PART && volume_type != ModelVolumeType::PARAMETER_MODIFIER) + continue; } if (type & itLayerRoot) diff --git a/src/slic3r/GUI/PartPlate.cpp b/src/slic3r/GUI/PartPlate.cpp index cbb86e62ca5..5ff2824bab1 100644 --- a/src/slic3r/GUI/PartPlate.cpp +++ b/src/slic3r/GUI/PartPlate.cpp @@ -1948,7 +1948,7 @@ std::vector PartPlate::get_extruders_without_support(bool conside_custom_gc int PartPlate::get_physical_extruder_by_filament_id(const DynamicConfig& g_config, int idx) const { const std::vector& filament_map = get_real_filament_maps(g_config); - if (filament_map.size() < idx) + if (idx <= 0 || idx > (int)filament_map.size()) { return -1; } @@ -1960,6 +1960,10 @@ int PartPlate::get_physical_extruder_by_filament_id(const DynamicConfig& g_confi } int zero_base_logical_idx = filament_map[idx - 1] - 1; + if (zero_base_logical_idx < 0 || zero_base_logical_idx >= (int)the_map->values.size()) + { + return -1; + } return the_map->values[zero_base_logical_idx]; } @@ -6502,6 +6506,11 @@ int PartPlateList::load_from_3mf_structure(PlateDataPtrs& plate_data_list, int f for (unsigned int i = 0; i < (unsigned int)plate_data_list.size(); ++i) { int index = create_plate(false); + if (index < 0) + { + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << boost::format(": plate limit reached, skipping %1% remaining plate(s)") % (plate_data_list.size() - i); + break; + } m_plate_list[index]->m_locked = plate_data_list[i]->locked; m_plate_list[index]->config()->apply(plate_data_list[i]->config); m_plate_list[index]->set_plate_name(plate_data_list[i]->plate_name); diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 6e8aa632d07..a1975043d43 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -12371,7 +12371,9 @@ void Sidebar::auto_calc_flushing_volumes_internal(const int modify_id, const int int m_max_flush_volume = Slic3r::g_max_flush_volume; unsigned int m_number_of_extruders = (int)(sqrt(init_matrix.size()) + 0.001); - const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(); + // Physical filaments only: the flush matrix and min_flush_volumes are sized by the + // physical filament count, so mixed (virtual) filaments must not extend this loop. + const std::vector extruder_colours = wxGetApp().plater()->get_extruder_colors_from_plater_config(nullptr, /*include_mixed=*/false); std::vector> multi_colours; // Support for multi-color filament @@ -12392,8 +12394,11 @@ void Sidebar::auto_calc_flushing_volumes_internal(const int modify_id, const int multi_colours.push_back(single_filament); } - if (modify_id >= 0 && modify_id < multi_colours.size()) { - for (int i = 0; i < multi_colours.size(); ++i) { + // Defence in depth: never index past the matrix / flush-volume buffers. + const size_t flush_count = std::min(multi_colours.size(), + std::min(m_number_of_extruders, min_flush_volumes.size())); + if (modify_id >= 0 && modify_id < (int)flush_count) { + for (int i = 0; i < (int)flush_count; ++i) { // from to modify int from_idx = i; if (from_idx != modify_id) { diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index a0e6d8836a8..abe6e075b7a 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -7244,8 +7244,9 @@ bool Tab::select_preset( }; Preset *to_be_selected = m_presets->find_preset(preset_name, false, true); ConfigOptionStrings* cur_opt2 = dynamic_cast (m_presets->get_edited_preset().config.option("printer_extruder_variant")); - ConfigOptionStrings* to_select_opt2 = dynamic_cast (to_be_selected->config.option("printer_extruder_variant")); - bool no_transfer_variant = cur_opt2->values != to_select_opt2->values; + ConfigOptionStrings* to_select_opt2 = to_be_selected ? dynamic_cast (to_be_selected->config.option("printer_extruder_variant")) : nullptr; + // Unknown target preset or missing variant info: do not transfer variant settings. + bool no_transfer_variant = !cur_opt2 || !to_select_opt2 || cur_opt2->values != to_select_opt2->values; for (PresetUpdate &pu : updates) { pu.old_preset_dirty = (old_printer_technology == pu.technology) && pu.presets->current_is_dirty(); pu.new_preset_compatible = (new_printer_technology == pu.technology) && is_compatible_with_printer(pu.presets->get_edited_preset_with_vendor_profile(), new_printer_preset_with_vendor_profile); From 139259ca011a7b3d250de76a16f38aaf573eaf95 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:06:31 +0200 Subject: [PATCH 23/43] profiles: express remaining Snapmaker U1 line widths as % of nozzle diameter --- resources/profiles/Snapmaker.json | 2 +- .../0.25 Benchy @Snapmaker U1 (0.4 nozzle).json | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json index ece77949c5b..eee6bd1b64a 100644 --- a/resources/profiles/Snapmaker.json +++ b/resources/profiles/Snapmaker.json @@ -1,6 +1,6 @@ { "name": "Snapmaker", - "version": "02.02.56.02", + "version": "02.02.56.03", "force_update": "0", "description": "Snapmaker configurations", "machine_model_list": [ diff --git a/resources/profiles/Snapmaker/process/0.25 Benchy @Snapmaker U1 (0.4 nozzle).json b/resources/profiles/Snapmaker/process/0.25 Benchy @Snapmaker U1 (0.4 nozzle).json index 9d0aec1feb0..33856cf0962 100644 --- a/resources/profiles/Snapmaker/process/0.25 Benchy @Snapmaker U1 (0.4 nozzle).json +++ b/resources/profiles/Snapmaker/process/0.25 Benchy @Snapmaker U1 (0.4 nozzle).json @@ -39,12 +39,12 @@ "infill_combination": "1", "infill_direction": "90", "gap_fill_target": "nowhere", - "line_width": "0.4", - "inner_wall_line_width": "0.5", - "internal_solid_infill_line_width": "0.5", - "outer_wall_line_width": "0.5", - "sparse_infill_line_width": "0.5", - "top_surface_line_width": "0.5", + "line_width": "100%", + "inner_wall_line_width": "125%", + "internal_solid_infill_line_width": "125%", + "outer_wall_line_width": "125%", + "sparse_infill_line_width": "125%", + "top_surface_line_width": "125%", "bottom_shell_layers": "2", "initial_layer_travel_speed": "100%", "bridge_acceleration": "3000", From e631e322f03d859b37a1c8e58113502a282d7393 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:42:28 +0200 Subject: [PATCH 24/43] Fix CI preset cache packaging paths for the fork's install layout --- .github/workflows/build_orca.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index ed67cf8046a..460716fd8b3 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -193,7 +193,7 @@ jobs: shell: bash # The bundle was already packed from resources/, so the caches have to be # installed into it here; the source tree keeps its JSONs for later jobs. - run: ./scripts/build_preset_cache.sh -b build/${{ inputs.arch }} build/${{ inputs.arch }}/OrcaSlicer/OrcaSlicer.app/Contents/Resources/profiles + run: ./scripts/build_preset_cache.sh -b build/${{ inputs.arch }} "build/${{ inputs.arch }}/Snapmaker_Orca/Snapmaker Orca.app/Contents/Resources/profiles" - name: Pack macOS app bundle ${{ inputs.arch }} if: runner.os == 'macOS' && !inputs.macos-combine-only @@ -461,7 +461,7 @@ jobs: shell: cmd # Shipped into both the already-installed tree (portable zip, MSIX) and # the checkout cpack re-installs from when it builds the NSIS installer. - run: scripts\build_preset_cache.bat --prune-source "%BUILD_DIR%" "resources\profiles" "%BUILD_DIR%\OrcaSlicer\resources\profiles" + run: scripts\build_preset_cache.bat --prune-source "%BUILD_DIR%" "resources\profiles" "%BUILD_DIR%\Snapmaker_Orca\resources\profiles" - name: Pack unit tests Win if: runner.os == 'Windows' @@ -602,7 +602,7 @@ jobs: # Both were packed from resources/ before the caches existed, so the # AppImage is unpacked first and the caches shipped into it and into # the package tree; the source tree keeps its JSONs for later steps. - appimage=$(find build -maxdepth 1 -name "OrcaSlicer_Linux_AppImage*.AppImage" | head -1) + appimage=$(find build -maxdepth 1 -name "Snapmaker_Orca_Linux_AppImage*.AppImage" | head -1) chmod +x "$appimage" "$appimage" --appimage-extract ./scripts/build_preset_cache.sh -b build build/package/resources/profiles squashfs-root/resources/profiles From 846ae1d72f6f835febab152608e32016ee9f5369 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:42:28 +0200 Subject: [PATCH 25/43] Log the actual preset name for vendor duplicates --- src/libslic3r/Preset.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 6e594cdb6d6..9b9943f8dee 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -4009,17 +4009,17 @@ std::vector PresetCollection::merge_presets(PresetCollection &&othe } m_presets.emplace(it, std::move(preset)); } else { + // Take the name first: the Snapmaker-wins branch moves the preset away. + std::string preset_name = preset.name; std::string default_vendor = std::string(PresetBundle::SM_BUNDLE); - if (preset.vendor->name == default_vendor) { - if (preset.vendor != nullptr) { - // Re-assign a pointer to the vendor structure in the new PresetBundle. - auto it = new_vendors.find(preset.vendor->id); - assert(it != new_vendors.end()); - preset.vendor = &it->second; - } + if (preset.vendor != nullptr && preset.vendor->name == default_vendor) { + // Re-assign a pointer to the vendor structure in the new PresetBundle. + auto it_vendor = new_vendors.find(preset.vendor->id); + assert(it_vendor != new_vendors.end()); + preset.vendor = &it_vendor->second; m_presets.emplace(it, std::move(preset)); } - duplicates.emplace_back(std::move(preset.name)); + duplicates.emplace_back(std::move(preset_name)); } } From a6b77d74ea380a22e7518c6cabcac3171b6b7110 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:16:32 +0200 Subject: [PATCH 26/43] Use ASCII dashes in test names so Windows ctest filters match --- tests/libslic3r/test_mixed_filament.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/libslic3r/test_mixed_filament.cpp b/tests/libslic3r/test_mixed_filament.cpp index bcb8763d4b8..dd8a2662822 100644 --- a/tests/libslic3r/test_mixed_filament.cpp +++ b/tests/libslic3r/test_mixed_filament.cpp @@ -2649,7 +2649,7 @@ TEST_CASE("MERGE-REGRESS-04: merge_mixed_filament marks source deleted and seria // [MixedFilament][Delete] — DELETE-PRIORITY resolution order tests // ============================================================================ -TEST_CASE("DELETE-PRIORITY-01: resolve-order — manual_pattern blocks pair/gradient check", "[MixedFilament][Delete]") +TEST_CASE("DELETE-PRIORITY-01: resolve-order - manual_pattern blocks pair/gradient check", "[MixedFilament][Delete]") { // Verify that when a mixed filament has a non-empty manual_pattern, the // component_a/component_b pair check and gradient check are skipped (both @@ -2819,7 +2819,7 @@ TEST_CASE("DELETE-GROUP-01: comma-separated group adjustment after deletion", "[ // [MixedFilament][Gradient] — GRAD-DEL regression tests // ============================================================================ -TEST_CASE("GRAD-DEL-01: gradient with partial component survival — matching ID removes entry", "[MixedFilament][Gradient]") +TEST_CASE("GRAD-DEL-01: gradient with partial component survival - matching ID removes entry", "[MixedFilament][Gradient]") { // Setup: 6 physical. Create a custom entry with gradient_component_ids="123" // (IDs 1, 2, 3). Delete physical #2. The gradient check (step 2 in @@ -2853,7 +2853,7 @@ TEST_CASE("GRAD-DEL-01: gradient with partial component survival — matching ID CHECK(gradient_entries == 0); } -TEST_CASE("GRAD-DEL-02: gradient partial survival — no matching component, IDs adjusted", "[MixedFilament][Gradient]") +TEST_CASE("GRAD-DEL-02: gradient partial survival - no matching component, IDs adjusted", "[MixedFilament][Gradient]") { // Setup: 6 physical. Create a custom entry with gradient_component_ids="345" // (IDs 3, 4, 5). Delete physical #2. None of the gradient IDs match #2, From 13ac4eb5ccffc9d910c54b1799c192cca4f6e3d2 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:16:32 +0200 Subject: [PATCH 27/43] Ship libsentry with the macOS test bundle and gate the Sentry symbol upload --- .github/workflows/build_all.yml | 3 ++- .github/workflows/build_orca.yml | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 10162aa1ff6..f09f204c588 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -90,7 +90,8 @@ jobs: upload_symbols_macos: name: Upload macOS Debug Symbols to Sentry needs: build_macos_universal - if: ${{ !cancelled() && needs.build_macos_universal.result == 'success' && !vars.SELF_HOSTED }} + # Needs the Sentry org secrets; forks without them skip instead of failing. + if: ${{ !cancelled() && needs.build_macos_universal.result == 'success' && !vars.SELF_HOSTED && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} uses: ./.github/workflows/sentry_cli.yml with: os: macos-14 diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index 460716fd8b3..c84f52553e7 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -175,7 +175,9 @@ jobs: - name: Pack unit tests mac if: runner.os == 'macOS' && !inputs.macos-combine-only && inputs.arch == 'arm64' working-directory: ${{ github.workspace }} - run: tar -cvf build_tests.tar build/arm64/tests + # The test binaries load libsentry.dylib through an rpath into the deps + # prefix; ship it at that path so the unit-test runner can resolve it. + run: tar -cvf build_tests.tar build/arm64/tests deps/build/arm64/OrcaSlicer_dep/usr/local/lib/libsentry.dylib - name: Upload Test Artifact mac if: runner.os == 'macOS' && !inputs.macos-combine-only && inputs.arch == 'arm64' From 7a90cd874192fdae803f9c0b2bdf09112161665c Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 22:16:32 +0200 Subject: [PATCH 28/43] Update Flatpak manifest dependency archives to match current deps --- .../io.github.Snapmaker.Snapmaker_Orca.yml | 77 +++++++++++++++++-- 1 file changed, 70 insertions(+), 7 deletions(-) diff --git a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml index 5782c3838ae..379b5c11937 100644 --- a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml +++ b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml @@ -173,9 +173,9 @@ modules: # CGAL - type: file - url: https://github.com/CGAL/cgal/archive/refs/tags/v5.4.zip + url: https://github.com/CGAL/cgal/releases/download/v5.6.3/CGAL-5.6.3.zip dest: external-packages/CGAL - sha256: d7605e0a5a5ca17da7547592f6f6e4a59430a0bc861948974254d0de43eab4c0 + sha256: 5d577acb4a9918ccb960491482da7a3838f8d363aff47e14d703f19fd84733d4 # GMP - type: file @@ -191,7 +191,7 @@ modules: # MPFR - type: file - url: https://www.mpfr.org/mpfr-4.2.2/mpfr-4.2.2.tar.bz2 + url: https://ftp.gnu.org/gnu/mpfr/mpfr-4.2.2.tar.bz2 dest: external-packages/MPFR sha256: 9ad62c7dc910303cd384ff8f1f4767a655124980bb6d8650fe62c815a231bb7b @@ -239,9 +239,9 @@ modules: # Qhull - type: file - url: https://github.com/qhull/qhull/archive/v8.0.1.zip + url: https://github.com/qhull/qhull/archive/v8.0.2.zip dest: external-packages/Qhull - sha256: 5287f5edd6a0372588f5d6640799086a4033d89d19711023ef8229dd9301d69b + sha256: a378e9a39e718e289102c20d45632f873bfdc58a7a5f924246ea4b176e185f1e # TBB - type: file @@ -257,15 +257,78 @@ modules: # GLFW - type: file - url: https://github.com/glfw/glfw/archive/refs/tags/3.3.7.zip + url: https://github.com/glfw/glfw/archive/refs/tags/3.4.zip dest: external-packages/GLFW - sha256: e02d956935e5b9fb4abf90e2c2e07c9a0526d7eacae8ee5353484c69a2a76cd0 + sha256: a133ddc3d3c66143eba9035621db8e0bcf34dba1ee9514a9e23e96afd39fd57a # libnoise - type: file url: https://github.com/SoftFever/Orca-deps-libnoise/archive/refs/tags/1.0.zip dest: external-packages/libnoise sha256: 96ffd6cc47898dd8147aab53d7d1b1911b507d9dbaecd5613ca2649468afd8b6 + # Eigen 5.0.1 + - type: file + url: https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.zip + dest: external-packages/Eigen + sha256: 0dbb1f9e3aaad66f352c03227d8c983f6f0b49e0b07e71a7300f4abcc01aee12 + + # Draco 1.5.7 + - type: file + url: https://github.com/google/draco/archive/refs/tags/1.5.7.zip + dest: external-packages/Draco + sha256: 27b72ba2d5ff3d0a9814ad40d4cb88f8dc89a35491c0866d952473f8f9416b77 + + # Assimp 5.4.3 + - type: file + url: https://github.com/assimp/assimp/archive/refs/tags/v5.4.3.tar.gz + dest: external-packages/Assimp + sha256: 66dfbaee288f2bc43172440a55d0235dfc7bf885dda6435c038e8000e79582cb + + # FFmpeg n7.0.3 + - type: file + url: https://github.com/FFmpeg/FFmpeg/archive/refs/tags/n7.0.3.tar.gz + dest: external-packages/FFMPEG + sha256: deedcabe339165214a3637df4c86a507aef0d793cf8774ff68735f4737e8ddbc + + # wxInspector 1.0.0 + - type: file + url: https://github.com/Noisyfox/wxInspector/archive/refs/tags/v1.0.0.zip + dest: external-packages/wxInspector + sha256: 0ba163956f2d468b19a91b96c5aba66ee9610843ea41dda628ea44cdafde7db7 + + # CPython 3.12.13 + - type: file + url: https://www.python.org/ftp/python/3.12.13/Python-3.12.13.tar.xz + dest: external-packages/python3 + sha256: c08bc65a81971c1dd5783182826503369466c7e67374d1646519adf05207b684 + + # Fallback archives for deps normally provided by the GNOME SDK; + # only used if find_package() fails to locate them. + + # ZLIB 1.2.13 + - type: file + url: https://github.com/madler/zlib/archive/refs/tags/v1.2.13.zip + dest: external-packages/ZLIB + sha256: c2856951bbf30e30861ace3765595d86ba13f2cf01279d901f6c62258c57f4ff + + # libpng 1.6.35 + - type: file + url: https://github.com/glennrp/libpng/archive/refs/tags/v1.6.35.zip + dest: external-packages/PNG + sha256: 3d22d46c566b1761a0e15ea397589b3a5f36ac09b7c785382e6470156c04247f + + # libjpeg-turbo 3.0.1 + - type: file + url: https://github.com/libjpeg-turbo/libjpeg-turbo/archive/refs/tags/3.0.1.zip + dest: external-packages/JPEG + sha256: d6d99e693366bc03897677650e8b2dfa76b5d6c54e2c9e70c03f0af821b0a52f + + # Freetype 2.12.1 + - type: file + url: https://github.com/SoftFever/orca_deps/releases/download/freetype-2.12.1.tar.gz/freetype-2.12.1.tar.gz + dest: external-packages/FREETYPE + sha256: efe71fd4b8246f1b0b1b9bfca13cfff1c9ad85930340c27df469733bbb620938 + - name: Snapmaker_Orca buildsystem: simple build-commands: From 92b0ee80244587752cc74a15687bf5533393379c Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:23:42 +0200 Subject: [PATCH 29/43] Pin Flatpak wxWidgets to orca-3.3.2 with the deps build options --- .../io.github.Snapmaker.Snapmaker_Orca.yml | 93 ++++++++++--------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml index 379b5c11937..c01a9354471 100644 --- a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml +++ b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml @@ -70,55 +70,56 @@ modules: url: https://github.com/FreeSpacenav/libspnav/releases/download/v1.2/libspnav-1.2.tar.gz sha256: 093747e7e03b232e08ff77f1ad7f48552c06ac5236316a5012db4269951c39db + # wxWidgets built as a separate module (no network at build time). + # Config-opts mirror deps/wxWidgets/wxWidgets.cmake with FLATPAK=ON, DEP_WX_GTK3=ON - name: orca_wxwidgets - buildsystem: simple - build-commands: - - | - set -euo pipefail - export CMAKE_POLICY_VERSION_MINIMUM=3.5 - mkdir builddir && cd builddir - cmake ../ -GNinja \ - -DwxBUILD_PRECOMP=ON \ - -DwxBUILD_TOOLKIT=gtk3 \ - -DwxBUILD_DEBUG_LEVEL=0 \ - -DwxBUILD_SAMPLES=OFF \ - -DwxBUILD_SHARED=ON \ - -DwxUSE_MEDIACTRL=ON \ - -DwxUSE_DETECT_SM=OFF \ - -DwxUSE_UNICODE=ON \ - -DwxUSE_PRIVATE_FONTS=ON \ - -DwxUSE_OPENGL=ON \ - -DwxUSE_WEBREQUEST=ON \ - -DwxUSE_WEBVIEW=ON \ - -DwxUSE_WEBVIEW_EDGE=OFF \ - -DwxUSE_WEBVIEW_IE=OFF \ - -DwxUSE_REGEX=builtin \ - -DwxUSE_LIBSDL=OFF \ - -DwxUSE_XTEST=OFF \ - -DwxUSE_STC=OFF \ - -DwxUSE_AUI=ON \ - -DwxUSE_LIBPNG=sys \ - -DwxUSE_ZLIB=sys \ - -DwxUSE_LIBJPEG=sys \ - -DwxUSE_LIBTIFF=OFF \ - -DwxUSE_EXPAT=sys \ - -DBUILD_SHARED_LIBS:BOOL=ON \ - -DCMAKE_INSTALL_PREFIX:STRING=/app \ - -DCMAKE_PREFIX_PATH=/app \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_POLICY_VERSION_MINIMUM=3.5 - cmake --build . --target install -j$FLATPAK_BUILDER_N_JOBS + buildsystem: cmake-ninja + build-options: + env: + CMAKE_POLICY_VERSION_MINIMUM: "3.5" + config-opts: + - -DCMAKE_BUILD_TYPE=Release + - -DwxBUILD_PRECOMP=ON + - -DwxBUILD_TOOLKIT=gtk3 + - -DCMAKE_DEBUG_POSTFIX:STRING=d + - -DwxBUILD_DEBUG_LEVEL=0 + - -DwxBUILD_SAMPLES=OFF + - -DwxBUILD_SHARED=ON + - -DBUILD_SHARED_LIBS=ON + - -DwxUSE_MEDIACTRL=ON + - -DwxUSE_DETECT_SM=OFF + - -DwxUSE_PRIVATE_FONTS=ON + - -DwxUSE_OPENGL=ON + - -DwxUSE_GLCANVAS_EGL=ON + - -DwxUSE_WEBREQUEST=ON + - -DwxUSE_WEBVIEW=ON + - -DwxUSE_WEBVIEW_EDGE=OFF + - -DwxUSE_WEBVIEW_IE=OFF + - -DwxUSE_REGEX=builtin + - -DwxUSE_LIBSDL=OFF + - -DwxUSE_XTEST=OFF + - -DwxUSE_STC=OFF + - -DwxUSE_AUI=ON + - -DwxUSE_LIBPNG=sys + - -DwxUSE_ZLIB=sys + - -DwxUSE_LIBJPEG=sys + - -DwxUSE_LIBTIFF=OFF + # sys, not builtin (unlike the static deps build): wxWidgets installs the + # builtin libwxwebp*.so only for static builds, so a shared build leaves + # them missing at runtime. The GNOME runtime provides libwebp. + - -DwxUSE_LIBWEBP=sys + - -DwxUSE_EXPAT=sys + - -DwxUSE_NANOSVG=OFF + - -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld + - -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld + - -DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld sources: + # Use git instead of archive: wxWidgets 3.3 relies on multiple git + # submodules (PCRE2, etc.) that are not included in GitHub tarballs. - type: git - url: https://github.com/SoftFever/Orca-deps-wxWidgets - branch: master - path: ../ - cleanup: - - "*.la" - - "*.a" - - "*.cmake" - - /include - - /app/bin/* + url: https://github.com/SoftFever/Orca-deps-wxWidgets.git + tag: orca-3.3.2 + commit: db1005db3dea2c37a46fb455a9a02e37aa360751 - name: orca_deps buildsystem: simple From e9266fc198e2ace1a5d6ea2d8c090ae24da8fe60 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:23:42 +0200 Subject: [PATCH 30/43] Gate the Windows Sentry symbol upload like the macOS one --- .github/workflows/build_deps.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_deps.yml b/.github/workflows/build_deps.yml index 84d86c1da49..a61b5da07c7 100644 --- a/.github/workflows/build_deps.yml +++ b/.github/workflows/build_deps.yml @@ -217,7 +217,8 @@ jobs: upload_symbols: name: Upload Debug Symbols to Sentry needs: [build_orca] - if: ${{ !cancelled() && needs.build_orca.result == 'success' && (startsWith(inputs.os, 'windows') || inputs.os == 'orca-win-server') && inputs.arch != 'arm64' }} + # Needs the Sentry org secrets; forks without them skip instead of failing (same gate as build_all.yml's upload_symbols_macos). + if: ${{ !cancelled() && needs.build_orca.result == 'success' && (startsWith(inputs.os, 'windows') || inputs.os == 'orca-win-server') && inputs.arch != 'arm64' && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} uses: ./.github/workflows/sentry_cli.yml with: os: ${{ inputs.os }} From a95a8c765ee9a0625664e73d661b7ab09416a302 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:53:16 +0200 Subject: [PATCH 31/43] Drop lld linker flags from the Flatpak wxWidgets module --- scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml index c01a9354471..fbe7ce9cb46 100644 --- a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml +++ b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml @@ -110,9 +110,6 @@ modules: - -DwxUSE_LIBWEBP=sys - -DwxUSE_EXPAT=sys - -DwxUSE_NANOSVG=OFF - - -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld - - -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld - - -DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld sources: # Use git instead of archive: wxWidgets 3.3 relies on multiple git # submodules (PCRE2, etc.) that are not included in GitHub tarballs. From 87efad22a49aa7739e6a184f2320ad9aea4fcb7c Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:26:59 +0200 Subject: [PATCH 32/43] Unify the Sentry symbol upload into one job for all platforms --- .github/workflows/build_all.yml | 18 +-- .github/workflows/build_deps.yml | 16 --- .github/workflows/build_orca.yml | 2 +- .github/workflows/sentry_cli.yml | 188 +++++++++++-------------------- 4 files changed, 78 insertions(+), 146 deletions(-) diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index f09f204c588..97cba7065e7 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -85,17 +85,19 @@ jobs: arch: universal macos-combine-only: true secrets: inherit - # The macOS dSYM is only produced by the universal combine leg above, so it is - # uploaded here rather than from build_deps.yml (which handles the Windows PDB). - upload_symbols_macos: - name: Upload macOS Debug Symbols to Sentry - needs: build_macos_universal + # One Sentry upload for every platform: the macOS dSYM from the universal + # combine leg and the Windows PDB from the x64 build leg (Linux produces no + # symbol artifact). upload-dif handles both formats from a single runner. + upload_symbols: + name: Upload Debug Symbols to Sentry + needs: [build_macos_universal, build_windows] # Needs the Sentry org secrets; forks without them skip instead of failing. - if: ${{ !cancelled() && needs.build_macos_universal.result == 'success' && !vars.SELF_HOSTED && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} + if: ${{ !cancelled() && !vars.SELF_HOSTED && (needs.build_macos_universal.result == 'success' || needs.build_windows.result == 'success') && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} uses: ./.github/workflows/sentry_cli.yml with: - os: macos-14 - dsym-artifact-name: dSYM_Mac_${{ needs.build_macos_universal.outputs.release }} + os: ubuntu-latest + pdb-artifact-name: PDB + dsym-artifact-name: ${{ needs.build_macos_universal.result == 'success' && format('dSYM_Mac_{0}', needs.build_macos_universal.outputs.release) || '' }} release: ${{ needs.build_macos_universal.outputs.release || github.sha }} secrets: inherit # One test job per built arch, on the runner that built it. diff --git a/.github/workflows/build_deps.yml b/.github/workflows/build_deps.yml index a61b5da07c7..a92a7fb025b 100644 --- a/.github/workflows/build_deps.yml +++ b/.github/workflows/build_deps.yml @@ -209,19 +209,3 @@ jobs: os: ${{ inputs.os }} arch: ${{ inputs.arch }} secrets: inherit - - # Only the Windows x64 leg produces a PDB artifact (see build_orca.yml: "Pack - # PDB"/"Upload artifacts Win PDB" skip arm64). The macOS dSYM is produced by the - # universal *combine* leg, which build_all.yml calls directly rather than through - # this workflow -- build_all.yml's upload_symbols_macos job handles that. - upload_symbols: - name: Upload Debug Symbols to Sentry - needs: [build_orca] - # Needs the Sentry org secrets; forks without them skip instead of failing (same gate as build_all.yml's upload_symbols_macos). - if: ${{ !cancelled() && needs.build_orca.result == 'success' && (startsWith(inputs.os, 'windows') || inputs.os == 'orca-win-server') && inputs.arch != 'arm64' && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} - uses: ./.github/workflows/sentry_cli.yml - with: - os: ${{ inputs.os }} - pdb-artifact-name: PDB - release: ${{ needs.build_orca.outputs.release || github.sha }} - secrets: inherit diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index c84f52553e7..c278ef5c3ce 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -515,7 +515,7 @@ jobs: name: Snapmaker_Orca_Windows_${{ env.ver }}${{ env.ARCH_SUFFIX }} path: ${{ github.workspace }}/${{ env.BUILD_DIR }}/Snapmaker_Orca*.exe - # sentry_cli.yml consumes this artifact name (via build_deps.yml). + # sentry_cli.yml consumes this artifact name (via build_all.yml). - name: Upload artifacts Win PDB if: runner.os == 'Windows' && inputs.arch != 'arm64' uses: actions/upload-artifact@v7 diff --git a/.github/workflows/sentry_cli.yml b/.github/workflows/sentry_cli.yml index b8d2935f4e4..c2c3ac04267 100644 --- a/.github/workflows/sentry_cli.yml +++ b/.github/workflows/sentry_cli.yml @@ -1,169 +1,115 @@ name: Upload Debug Symbols to Sentry +# One upload for every platform: sentry-cli's upload-dif is format-agnostic, so +# a single Linux runner ships the Windows PDBs and the macOS dSYMs together +# (Linux builds produce no symbol artifact). Artifact downloads are tolerant - +# whatever symbol artifacts this run produced get uploaded, the rest are +# reported as warnings instead of failing the run. on: workflow_call: inputs: os: required: true type: string - # Steps below gate on `runner.os`, not on this string, so any Windows / - # macOS / Linux runner label (incl. self-hosted orca-*-server) works. - description: "Runner label to run the upload on (e.g. windows-latest, macos-14, ubuntu-24.04)" + description: "Runner label to run the upload on (e.g. ubuntu-24.04)" pdb-artifact-name: required: false type: string - description: "Artifact name for Windows PDB archive (e.g., 'PDB')" + description: "Artifact name for the Windows PDB archive (e.g. 'PDB'); empty skips it" dsym-artifact-name: required: false type: string - description: "Artifact name for macOS dSYM archive (e.g., 'dSYM_Mac_V1.0.0')" + description: "Artifact name for the macOS dSYM archive (e.g. 'dSYM_Mac_V1.0.0'); empty skips it" release: - required: true + required: false type: string - description: "Release version/tag" + description: "Release version/tag (informational; upload-dif auto-associates releases)" jobs: upload_symbols: name: Upload Debug Symbols to Sentry runs-on: ${{ inputs.os }} steps: - # ==================== Windows ==================== - - name: "[Windows] Install sentry-cli via choco" - if: runner.os == 'Windows' && inputs.pdb-artifact-name != '' - shell: pwsh - run: | - choco install sentry-cli -y -y 2>&1 | Out-Null - - - name: "[Windows] Download PDB artifact" - if: runner.os == 'Windows' && inputs.pdb-artifact-name != '' - uses: actions/download-artifact@v8 - with: - name: ${{ inputs.pdb-artifact-name }} - path: ./symbols - - - name: "[Windows] Extract PDB archive" - if: runner.os == 'Windows' && inputs.pdb-artifact-name != '' - shell: pwsh - run: | - $archive = Get-ChildItem -Path ./symbols -Filter "*.7z" -File | Select-Object -First 1 - if ($archive) { - Write-Host "Found archive: $($archive.FullName)" - Write-Host "Extracting to ./symbols/extracted ..." - 7z x "$($archive.FullName)" -o"./symbols/extracted" -y - if ($LASTEXITCODE -ne 0) { - Write-Host "::error::Failed to extract archive with exit code $LASTEXITCODE" - exit $LASTEXITCODE - } - Write-Host "Extraction complete. Contents:" - Get-ChildItem -Path ./symbols/extracted -Recurse | ForEach-Object { Write-Host " $($_.FullName)" } - } else { - Write-Host "No .7z archive found, assuming PDB files are already extracted" - Get-ChildItem -Path ./symbols -Recurse | ForEach-Object { Write-Host " Found: $($_.FullName)" } - } - - - name: "[Windows] Upload PDB to Sentry (sentry-cli)" - if: runner.os == 'Windows' && inputs.pdb-artifact-name != '' - shell: pwsh - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_LOG_LEVEL: debug - run: | - # Determine upload path - prefer extracted folder if it exists - $uploadPath = "./symbols" - if (Test-Path "./symbols/extracted") { - $uploadPath = "./symbols/extracted" - } - - $pdbFiles = Get-ChildItem -Path $uploadPath -Filter "*.pdb" -File -Recurse - if ($pdbFiles.Count -gt 0) { - Write-Host "Found $($pdbFiles.Count) PDB file(s) to upload from $uploadPath :" - $pdbFiles | ForEach-Object { Write-Host " - $($_.FullName)" } - Write-Host "" - Write-Host "Starting Sentry upload with debug logging..." - sentry-cli.exe --log-level=debug --auth-token $env:SENTRY_AUTH_TOKEN upload-dif --org "${{ secrets.SENTRY_ORG }}" --project "${{ secrets.SENTRY_PROJECT }}" $uploadPath 2>&1 | Out-Host - if ($LASTEXITCODE -ne 0) { - Write-Host "::error::Sentry upload failed with exit code $LASTEXITCODE" - exit $LASTEXITCODE - } - } else { - Write-Host "::error::No PDB files found in $uploadPath" - Get-ChildItem -Path ./symbols -Recurse | ForEach-Object { Write-Host " Found: $($_.FullName)" } - exit 1 - } - - # ==================== macOS ==================== - - name: "[macOS] Install sentry-cli" - if: runner.os == 'macOS' && inputs.dsym-artifact-name != '' + - name: Install sentry-cli + shell: bash run: | - # Try multiple installation methods for reliability - # Method 1: Official install script (most reliable) if ! command -v sentry-cli &> /dev/null; then echo "Installing sentry-cli via official script..." curl -sL https://sentry.io/get-cli/ | bash || true fi - - # Method 2: npm fallback if ! command -v sentry-cli &> /dev/null; then echo "Official script failed, trying npm..." npm install -g @sentry/cli || true fi - - # Method 3: Direct binary download fallback - if ! command -v sentry-cli &> /dev/null; then - echo "npm failed, downloading binary directly..." - SENTRY_CLI_VERSION=$(curl -s https://api.github.com/repos/getsentry/sentry-cli/releases/latest | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') - curl -sL "https://github.com/getsentry/sentry-cli/releases/download/${SENTRY_CLI_VERSION}/sentry-cli-Darwin-universal" -o /usr/local/bin/sentry-cli - chmod +x /usr/local/bin/sentry-cli - fi - - # Verify installation if command -v sentry-cli &> /dev/null; then - echo "sentry-cli installed successfully:" sentry-cli --version else - echo "::error::Failed to install sentry-cli via all methods" + echo "::error::Failed to install sentry-cli" exit 1 fi - - name: "[macOS] Download dSYM artifact" - if: runner.os == 'macOS' && inputs.dsym-artifact-name != '' + - name: Download Windows PDB artifact + id: dl_pdb + if: inputs.pdb-artifact-name != '' + continue-on-error: true + uses: actions/download-artifact@v8 + with: + name: ${{ inputs.pdb-artifact-name }} + path: ./symbols/windows + + - name: Download macOS dSYM artifact + id: dl_dsym + if: inputs.dsym-artifact-name != '' + continue-on-error: true uses: actions/download-artifact@v8 with: name: ${{ inputs.dsym-artifact-name }} - path: ./symbols + path: ./symbols/macos + + - name: Extract archives + if: steps.dl_pdb.outcome == 'success' || steps.dl_dsym.outcome == 'success' + shell: bash + run: | + # The Windows PDBs ship as a .7z inside the artifact. + shopt -s nullglob globstar + for archive in ./symbols/**/*.7z; do + echo "Extracting $archive ..." + dir="$(dirname "$archive")/extracted" + if command -v 7z &> /dev/null; then + 7z x "$archive" -o"$dir" -y + else + 7zz x "$archive" -o"$dir" -y + fi + rm -f "$archive" + done - - name: "[macOS] Upload dSYM to Sentry (sentry-cli)" - if: runner.os == 'macOS' && inputs.dsym-artifact-name != '' + - name: Upload debug symbols to Sentry + if: steps.dl_pdb.outcome == 'success' || steps.dl_dsym.outcome == 'success' + shell: bash env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_LOG_LEVEL: debug run: | - echo "Checking for dSYM files in ./symbols..." - find ./symbols -name "*.dSYM" -type d | while read dsym; do - echo "Found dSYM: $dsym" - done - - dsymCount=$(find ./symbols -name "*.dSYM" -type d | wc -l | tr -d ' ') - if [ "$dsymCount" -gt 0 ]; then - echo "Found $dsymCount dSYM file(s) to upload from ./symbols:" - find ./symbols -name "*.dSYM" -type d | while read dsym; do - echo " - $dsym" - done - echo "" - echo "Starting Sentry upload with debug logging..." - # upload-dif does not support --release flag, it auto-associates with releases - sentry-cli --log-level=debug --auth-token "$SENTRY_AUTH_TOKEN" upload-dif --org "${{ secrets.SENTRY_ORG }}" --project "${{ secrets.SENTRY_PROJECT }}" ./symbols - UPLOAD_EXIT_CODE=$? - if [ $UPLOAD_EXIT_CODE -ne 0 ]; then - echo "::error::Sentry upload failed with exit code $UPLOAD_EXIT_CODE" - exit 1 - fi - else - echo "::error::No dSYM files found in ./symbols" - echo "Contents of ./symbols:" - find ./symbols -type f -o -type d | head -20 + if [ "${{ steps.dl_pdb.outcome }}" = "failure" ]; then + echo "::warning::PDB artifact '${{ inputs.pdb-artifact-name }}' could not be downloaded; uploading without Windows symbols" + fi + if [ "${{ steps.dl_dsym.outcome }}" = "failure" ]; then + echo "::warning::dSYM artifact '${{ inputs.dsym-artifact-name }}' could not be downloaded; uploading without macOS symbols" + fi + pdb_count=$(find ./symbols -name "*.pdb" -type f | wc -l | tr -d ' ') + dsym_count=$(find ./symbols -name "*.dSYM" -type d | wc -l | tr -d ' ') + echo "Found $pdb_count PDB file(s) and $dsym_count dSYM bundle(s):" + find ./symbols -name "*.pdb" -type f -o -name "*.dSYM" -type d + if [ "$pdb_count" -eq 0 ] && [ "$dsym_count" -eq 0 ]; then + echo "::error::Symbol artifacts downloaded but contained no PDB or dSYM files" + find ./symbols | head -40 exit 1 fi + # upload-dif does not support --release; it auto-associates with releases + sentry-cli --log-level=debug --auth-token "$SENTRY_AUTH_TOKEN" upload-dif \ + --org "${{ secrets.SENTRY_ORG }}" --project "${{ secrets.SENTRY_PROJECT }}" ./symbols - # ==================== Linux ==================== - + - name: Nothing to upload + if: steps.dl_pdb.outcome != 'success' && steps.dl_dsym.outcome != 'success' + shell: bash + run: echo "::warning::No symbol artifacts were available in this run; nothing uploaded to Sentry" From 181bfebcfb78341710c76bacd5e591124201f9c1 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:31:29 +0200 Subject: [PATCH 33/43] Build the Flatpak with the clang/lld toolchain like the Linux CI --- scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml index fbe7ce9cb46..eab258992f4 100644 --- a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml +++ b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml @@ -2,6 +2,8 @@ app-id: io.github.Snapmaker.Snapmaker_Orca runtime: org.gnome.Platform runtime-version: "49" sdk: org.gnome.Sdk +sdk-extensions: + - org.freedesktop.Sdk.Extension.llvm21 command: entrypoint separate-locales: true rename-icon: Snapmaker_Orca @@ -21,7 +23,12 @@ finish-args: - --env=SPNAV_SOCKET=/run/spnav.sock build-options: + append-path: /usr/lib/sdk/llvm21/bin + prepend-ld-library-path: /usr/lib/sdk/llvm21/lib env: + CC: clang + CXX: clang++ + LDFLAGS: "-fuse-ld=lld" CMAKE_POLICY_VERSION_MINIMUM: "3.5" modules: @@ -110,6 +117,9 @@ modules: - -DwxUSE_LIBWEBP=sys - -DwxUSE_EXPAT=sys - -DwxUSE_NANOSVG=OFF + - -DCMAKE_EXE_LINKER_FLAGS=-fuse-ld=lld + - -DCMAKE_SHARED_LINKER_FLAGS=-fuse-ld=lld + - -DCMAKE_MODULE_LINKER_FLAGS=-fuse-ld=lld sources: # Use git instead of archive: wxWidgets 3.3 relies on multiple git # submodules (PCRE2, etc.) that are not included in GitHub tarballs. From b739163fac228ff75b94536e19e7d297da032f40 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:15:36 +0200 Subject: [PATCH 34/43] Build Flatpak glu module with -Wno-register under clang --- scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml index eab258992f4..943551f0555 100644 --- a/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml +++ b/scripts/flatpak/io.github.Snapmaker.Snapmaker_Orca.yml @@ -48,6 +48,9 @@ modules: sha256: e305b9f07f52743ca481da0a4e0c76c35efd60adaf1b0694eb3bb021e2137e39 - name: glu + build-options: + # clang treats C++17 'register' as an error where GCC only warns + cxxflags: -Wno-register config-opts: - --disable-static sources: From ba1fce28c64ddb5cbf98212d79be6d8a9305d679 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 12:50:30 +0200 Subject: [PATCH 35/43] Enable Sentry crash reporting on Linux and ship its symbols --- .github/workflows/build_all.yml | 9 +++---- .github/workflows/build_orca.yml | 21 ++++++++++++++++ .github/workflows/sentry_cli.yml | 36 +++++++++++++++++++++------- CMakeLists.txt | 5 ++-- deps/CMakeLists.txt | 5 ++-- deps/Sentry/Sentry.cmake | 2 ++ src/sentry_wrapper/SentryWrapper.cpp | 9 +++++++ 7 files changed, 70 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build_all.yml b/.github/workflows/build_all.yml index 97cba7065e7..636790b6e49 100644 --- a/.github/workflows/build_all.yml +++ b/.github/workflows/build_all.yml @@ -86,18 +86,19 @@ jobs: macos-combine-only: true secrets: inherit # One Sentry upload for every platform: the macOS dSYM from the universal - # combine leg and the Windows PDB from the x64 build leg (Linux produces no - # symbol artifact). upload-dif handles both formats from a single runner. + # combine leg, the Windows PDB from the x64 build leg and the unstripped + # Linux binaries. upload-dif handles all formats from a single runner. upload_symbols: name: Upload Debug Symbols to Sentry - needs: [build_macos_universal, build_windows] + needs: [build_macos_universal, build_windows, build_linux] # Needs the Sentry org secrets; forks without them skip instead of failing. - if: ${{ !cancelled() && !vars.SELF_HOSTED && (needs.build_macos_universal.result == 'success' || needs.build_windows.result == 'success') && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} + if: ${{ !cancelled() && !vars.SELF_HOSTED && (needs.build_macos_universal.result == 'success' || needs.build_windows.result == 'success' || needs.build_linux.result == 'success') && (github.repository_owner == 'Snapmaker' || vars.SENTRY_UPLOAD == '1') }} uses: ./.github/workflows/sentry_cli.yml with: os: ubuntu-latest pdb-artifact-name: PDB dsym-artifact-name: ${{ needs.build_macos_universal.result == 'success' && format('dSYM_Mac_{0}', needs.build_macos_universal.outputs.release) || '' }} + linux-symbols-pattern: Linux_symbols_* release: ${{ needs.build_macos_universal.outputs.release || github.sha }} secrets: inherit # One test job per built arch, on the runner that built it. diff --git a/.github/workflows/build_orca.yml b/.github/workflows/build_orca.yml index c278ef5c3ce..26aac19898f 100644 --- a/.github/workflows/build_orca.yml +++ b/.github/workflows/build_orca.yml @@ -585,6 +585,27 @@ jobs: chmod +x "$appimage" tar -cvf build_tests.tar build/tests + # The unstripped build-tree binary carries the symtab Sentry needs; the + # AppImage ships the same build (same ELF build-id). Arch subdir keeps the + # two Linux legs from colliding in the unified symbol upload. + - name: Pack Linux symbols + if: runner.os == 'Linux' + shell: bash + run: | + arch=${{ inputs.arch == 'aarch64' && 'aarch64' || 'x86_64' }} + mkdir -p symbols_linux/$arch + cp build/src/Release/snapmaker-orca symbols_linux/$arch/ + + - name: Upload Linux symbols artifact + if: runner.os == 'Linux' + uses: actions/upload-artifact@v7 + with: + name: Linux_symbols_${{ inputs.arch == 'aarch64' && 'aarch64' || 'x86_64' }} + overwrite: true + path: symbols_linux + retention-days: 5 + if-no-files-found: error + # Use tar because upload-artifacts won't always preserve directory structure # and doesn't preserve file permissions - name: Upload Test Artifact diff --git a/.github/workflows/sentry_cli.yml b/.github/workflows/sentry_cli.yml index c2c3ac04267..6f2339330e8 100644 --- a/.github/workflows/sentry_cli.yml +++ b/.github/workflows/sentry_cli.yml @@ -1,8 +1,8 @@ name: Upload Debug Symbols to Sentry # One upload for every platform: sentry-cli's upload-dif is format-agnostic, so -# a single Linux runner ships the Windows PDBs and the macOS dSYMs together -# (Linux builds produce no symbol artifact). Artifact downloads are tolerant - +# a single runner ships the Windows PDBs, the macOS dSYMs and the unstripped +# Linux ELF binaries together. Artifact downloads are tolerant - # whatever symbol artifacts this run produced get uploaded, the rest are # reported as warnings instead of failing the run. on: @@ -20,6 +20,10 @@ on: required: false type: string description: "Artifact name for the macOS dSYM archive (e.g. 'dSYM_Mac_V1.0.0'); empty skips it" + linux-symbols-pattern: + required: false + type: string + description: "Artifact name pattern for Linux ELF symbols (e.g. 'Linux_symbols_*'); empty skips it" release: required: false type: string @@ -66,8 +70,18 @@ jobs: name: ${{ inputs.dsym-artifact-name }} path: ./symbols/macos + - name: Download Linux symbols artifacts + id: dl_linux + if: inputs.linux-symbols-pattern != '' + continue-on-error: true + uses: actions/download-artifact@v8 + with: + pattern: ${{ inputs.linux-symbols-pattern }} + merge-multiple: true + path: ./symbols/linux + - name: Extract archives - if: steps.dl_pdb.outcome == 'success' || steps.dl_dsym.outcome == 'success' + if: steps.dl_pdb.outcome == 'success' || steps.dl_dsym.outcome == 'success' || steps.dl_linux.outcome == 'success' shell: bash run: | # The Windows PDBs ship as a .7z inside the artifact. @@ -84,7 +98,7 @@ jobs: done - name: Upload debug symbols to Sentry - if: steps.dl_pdb.outcome == 'success' || steps.dl_dsym.outcome == 'success' + if: steps.dl_pdb.outcome == 'success' || steps.dl_dsym.outcome == 'success' || steps.dl_linux.outcome == 'success' shell: bash env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} @@ -96,12 +110,16 @@ jobs: if [ "${{ steps.dl_dsym.outcome }}" = "failure" ]; then echo "::warning::dSYM artifact '${{ inputs.dsym-artifact-name }}' could not be downloaded; uploading without macOS symbols" fi + if [ "${{ steps.dl_linux.outcome }}" = "failure" ]; then + echo "::warning::Linux symbol artifacts '${{ inputs.linux-symbols-pattern }}' could not be downloaded; uploading without Linux symbols" + fi pdb_count=$(find ./symbols -name "*.pdb" -type f | wc -l | tr -d ' ') dsym_count=$(find ./symbols -name "*.dSYM" -type d | wc -l | tr -d ' ') - echo "Found $pdb_count PDB file(s) and $dsym_count dSYM bundle(s):" - find ./symbols -name "*.pdb" -type f -o -name "*.dSYM" -type d - if [ "$pdb_count" -eq 0 ] && [ "$dsym_count" -eq 0 ]; then - echo "::error::Symbol artifacts downloaded but contained no PDB or dSYM files" + elf_count=$(find ./symbols/linux -type f 2>/dev/null | wc -l | tr -d ' ') + echo "Found $pdb_count PDB file(s), $dsym_count dSYM bundle(s), $elf_count Linux binary/binaries:" + find ./symbols -name "*.pdb" -type f -o -name "*.dSYM" -type d -o -path "./symbols/linux/*" -type f + if [ "$pdb_count" -eq 0 ] && [ "$dsym_count" -eq 0 ] && [ "$elf_count" -eq 0 ]; then + echo "::error::Symbol artifacts downloaded but contained no PDB, dSYM or ELF files" find ./symbols | head -40 exit 1 fi @@ -110,6 +128,6 @@ jobs: --org "${{ secrets.SENTRY_ORG }}" --project "${{ secrets.SENTRY_PROJECT }}" ./symbols - name: Nothing to upload - if: steps.dl_pdb.outcome != 'success' && steps.dl_dsym.outcome != 'success' + if: steps.dl_pdb.outcome != 'success' && steps.dl_dsym.outcome != 'success' && steps.dl_linux.outcome != 'success' shell: bash run: echo "::warning::No symbol artifacts were available in this run; nothing uploaded to Sentry" diff --git a/CMakeLists.txt b/CMakeLists.txt index ada6333ff6c..81765547e87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -148,8 +148,9 @@ option(SLIC3R_MSVC_COMPILE_PARALLEL "Compile on Visual Studio in parallel" 1) option(SLIC3R_MSVC_PDB "Generate PDB files on MSVC in Release mode" 1) option(SLIC3R_ASAN "Enable ASan on Clang and GCC" 0) -# Sentry crash reporting - enabled only on Windows/macOS by default -if (WIN32 OR APPLE) +# Sentry crash reporting - on by default everywhere except the Flatpak build +# (Flathub distribution stays telemetry-free). +if (WIN32 OR APPLE OR (UNIX AND NOT FLATPAK)) set(SLIC3R_SENTRY_DEFAULT ON) else() set(SLIC3R_SENTRY_DEFAULT OFF) diff --git a/deps/CMakeLists.txt b/deps/CMakeLists.txt index 4da8a525a94..6f46a0ebfb1 100644 --- a/deps/CMakeLists.txt +++ b/deps/CMakeLists.txt @@ -112,8 +112,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Linux") option(DEP_WX_GTK3 "Build wxWidgets against GTK3" ON) endif() -# Sentry crash reporting - enabled only on Windows by default -if (WIN32 OR APPLE) +# Sentry crash reporting - on by default everywhere except the Flatpak build +# (Flathub distribution stays telemetry-free). +if (WIN32 OR APPLE OR (UNIX AND NOT FLATPAK)) set(SLIC3R_SENTRY_DEFAULT ON) else() set(SLIC3R_SENTRY_DEFAULT OFF) diff --git a/deps/Sentry/Sentry.cmake b/deps/Sentry/Sentry.cmake index 7efd4de50c1..aa3e68e37ca 100644 --- a/deps/Sentry/Sentry.cmake +++ b/deps/Sentry/Sentry.cmake @@ -60,6 +60,8 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux") ${_sentry_platform_flags} -DSENTRY_TRANSPORT_CURL=ON -DSENTRY_BUILD_SHARED_LIBS=OFF + # breakpad backend: in-process, needs no crashpad_handler shipped in the AppImage + -DSENTRY_BACKEND=breakpad -DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo ) set(_sentry_cmake_generator -G "Unix Makefiles") diff --git a/src/sentry_wrapper/SentryWrapper.cpp b/src/sentry_wrapper/SentryWrapper.cpp index 097c7d34111..69e8bbba6df 100644 --- a/src/sentry_wrapper/SentryWrapper.cpp +++ b/src/sentry_wrapper/SentryWrapper.cpp @@ -240,6 +240,15 @@ void initSentryEx() std::cout<< "Failed to get temp path, Sentry data directory will be empty"; } } +#else + // Linux: the breakpad backend runs in-process, so no handler path is + // needed; keep the minidump database under the XDG data directory. + const char* xdg_data_env = std::getenv("XDG_DATA_HOME"); + const char* home_dir_env = std::getenv("HOME"); + if (xdg_data_env != nullptr && xdg_data_env[0] != '\0') + dataBaseDir = std::string(xdg_data_env) + "/Snapmaker_Orca/SentryData"; + else if (home_dir_env != nullptr && home_dir_env[0] != '\0') + dataBaseDir = std::string(home_dir_env) + "/.local/share/Snapmaker_Orca/SentryData"; #endif if (!handlerDir.empty()) From b7e5754e1f44df3492749158bbfa16edcef7fcf4 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 18:52:28 +0200 Subject: [PATCH 36/43] Allow independent support layer heights with the prime tower via grid-aligned layers The prime tower is built on the object layer grid, so free-form support layer heights were force-disabled whenever the tower was enabled and supports fell back to the object grid. Keep the setting active instead: tree supports (all styles) now plan thick layers as whole multiples of object layers, so every toolchange still lands on a tower layer, and the classic support generator synchronizes with the object layers as before. Support gaps are rounded to layer multiples while the tower is enabled. --- src/libslic3r/PrintConfig.cpp | 21 ++++++--------- src/libslic3r/Slicing.cpp | 6 ++--- src/libslic3r/Support/SupportMaterial.cpp | 10 +++---- src/libslic3r/Support/SupportParameters.hpp | 12 +++++++++ src/libslic3r/Support/TreeSupport.cpp | 30 +++++++++++++++++++++ 5 files changed, 58 insertions(+), 21 deletions(-) diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 868babf5f36..ca64111181a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -10103,12 +10103,11 @@ void DynamicPrintConfig::normalize_fdm(int used_filaments) ept_opt->value = false; } - if (ept_opt->value) { - if (islh_opt) - islh_opt->value = false; - //if (alh_opt) - // alh_opt->value = false; - } + // With the prime tower enabled, independent support layer heights are no longer + // forced off: tree supports plan grid-aligned thick layers (whole multiples of + // object layers) so every toolchange still lands on a tower layer, and the + // classic support generator falls back to synchronized layers on its own. + (void) islh_opt; /* BBS: MusangKing - not sure if this is still valid, just comment it out cause "Independent support layer height" is re-opened. else { if (islh_opt) @@ -10205,13 +10204,9 @@ t_config_option_keys DynamicPrintConfig::normalize_fdm_2(int num_objects, int us } if (ept_opt->value) { - if (islh_opt) { - if (islh_opt->value) { - islh_opt->value = false; - changed_keys.push_back("independent_support_layer_height"); - } - //islh_opt->value = false; - } + // Independent support layer heights stay enabled with the prime tower (see + // normalize_fdm()); supports print grid-aligned thick layers instead. + (void) islh_opt; //if (alh_opt) { // if (alh_opt->value) { // alh_opt->value = false; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index cd293beb467..9da5615625e 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -181,7 +181,7 @@ SlicingParameters SlicingParameters::create_from_config( params.gap_raft_object = 0.0; } else { params.gap_raft_object = raft_z_gap; - if (!print_config.independent_support_layer_height) { + if (!print_config.independent_support_layer_height || print_config.enable_prime_tower) { params.gap_raft_object = std::round(params.gap_raft_object / object_config.layer_height + EPSILON) * object_config.layer_height; @@ -194,7 +194,7 @@ SlicingParameters SlicingParameters::create_from_config( } else { params.gap_object_support = support_bottom_z_gap; - if (!print_config.independent_support_layer_height) { + if (!print_config.independent_support_layer_height || print_config.enable_prime_tower) { params.gap_object_support = std::round(params.gap_object_support / object_config.layer_height + EPSILON) * object_config.layer_height; @@ -207,7 +207,7 @@ SlicingParameters SlicingParameters::create_from_config( } else { params.gap_support_object = support_top_z_gap; - if (!print_config.independent_support_layer_height) { + if (!print_config.independent_support_layer_height || print_config.enable_prime_tower) { params.gap_support_object = std::round(params.gap_support_object / object_config.layer_height + EPSILON) * object_config.layer_height; diff --git a/src/libslic3r/Support/SupportMaterial.cpp b/src/libslic3r/Support/SupportMaterial.cpp index 6a3e3ed4f49..900dacd4549 100644 --- a/src/libslic3r/Support/SupportMaterial.cpp +++ b/src/libslic3r/Support/SupportMaterial.cpp @@ -1748,7 +1748,7 @@ static inline std::pair new_cont } else { // BBS: need to consider adaptive layer heights - if (print_config.independent_support_layer_height) { + if (support_layer_heights_free(print_config)) { print_z = layer.bottom_z() - slicing_params.gap_support_object; height = 0; } @@ -1781,13 +1781,13 @@ static inline std::pair new_cont // Contact layer will be printed with a normal flow, but // it will support layers printed with a bridging flow. - if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer) && print_config.independent_support_layer_height) { + if (object_config.thick_bridges && SupportMaterialInternal::has_bridging_extrusions(layer) && support_layer_heights_free(print_config)) { coordf_t bridging_height = 0.; for (const LayerRegion* region : layer.regions()) bridging_height += region->region().bridging_height_avg(print_config); bridging_height /= coordf_t(layer.regions().size()); // BBS: align bridging height - if (!print_config.independent_support_layer_height) + if (!support_layer_heights_free(print_config)) bridging_height = std::ceil(bridging_height / object_config.layer_height - EPSILON) * object_config.layer_height; coordf_t bridging_print_z = layer.print_z - bridging_height - slicing_params.gap_support_object; if (bridging_print_z >= min_print_z) { @@ -1807,7 +1807,7 @@ static inline std::pair new_cont } else { // BBS: if independent_support_layer_height is not enabled, the support layer_height should be the same as layer height. // Note that for this case, adaptive layer height must be disabled. - bridging_layer->height = print_config.independent_support_layer_height ? 0. : object_config.layer_height; + bridging_layer->height = support_layer_heights_free(print_config) ? 0. : object_config.layer_height; // Don't know the height yet. bridging_layer->bottom_z = bridging_print_z - bridging_layer->height; } @@ -2419,7 +2419,7 @@ static inline SupportGeneratorLayer* detect_bottom_contacts( // with some spacing from object - it looks we don't need the actual // top shapes so this can be done here Layer* upper_layer = layer.upper_layer; - if (object.print()->config().independent_support_layer_height) { + if (support_layer_heights_free(object.print()->config())) { // If the layer is extruded with no bridging flow, support just the normal extrusions. layer_new.height = slicing_params.zero_gap_interface_bottom ? // Align the interface layer with the object's layer height. diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 3d9ac6e93a9..37a37f0c617 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -14,6 +14,13 @@ inline int number_of_support_interface_bottom_layers(const PrintObjectConfig& ob object_config.support_interface_bottom_layers.value; } +// Free-form (off-grid) support layer heights are only allowed without the prime tower; +// with the tower enabled the classic generator synchronizes with the object layers and +// tree supports plan grid-aligned thick layers instead. +inline bool support_layer_heights_free(const PrintConfig &print_config) { + return print_config.independent_support_layer_height && !print_config.enable_prime_tower; +} + struct SupportParameters { SupportParameters() = delete; SupportParameters(const PrintObject& object) @@ -186,6 +193,10 @@ struct SupportParameters { support_extrusion_width = support_extrusion_width > 0 ? support_extrusion_width : extrusion_width; independent_layer_height = print_config.independent_support_layer_height; + // The prime tower is built on the object layer grid, so toolchanges must land on + // grid Zs: independent support heights stay enabled but snap to whole multiples + // of object layers (tree supports; the classic generator synchronizes instead). + grid_aligned_layer_height = independent_layer_height && print_config.enable_prime_tower; // force double walls everywhere if wall count is larger than 1 tree_branch_diameter_double_wall_area_scaled = object_config.tree_support_wall_count.value > 1 ? 0.1 : @@ -316,6 +327,7 @@ struct SupportParameters { } bool independent_layer_height = false; + bool grid_aligned_layer_height = false; const double thresh_big_overhang = Slic3r::sqr(scale_(10)); bool ironing; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index d4a0ad8905b..561ce3e2ee3 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -3259,6 +3259,36 @@ std::vector TreeSupport::plan_layer_heights() z_heights[m_object->get_layer(layer_nr)->print_z] = m_object->get_layer(layer_nr)->height; layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, size_t(layer_nr)}; } + } else if (m_support_params.grid_aligned_layer_height) { + // Grid-aligned independent heights: group whole object layers into runs whose summed + // height still fits through the support nozzle. Every print_z lands on an object + // layer, so with the prime tower enabled each toolchange still has a tower layer to + // wipe on - the reason free-form heights are not allowed together with the tower. + const coordf_t max_layer_height = std::max(m_slicing_params.max_suport_layer_height, m_object->config().layer_height.value); + const size_t n = m_object->layer_count(); + // A run must end exactly at a top-contact layer so the support tops keep their + // contact Z (mirrors the boundaries the free-form planner inserts), and the contact + // layer itself prints at the object layer height for interface quality. + std::vector boundary(n + 1, 0); + for (size_t layer_nr = 1; layer_nr < contact_nodes.size() && layer_nr < n; ++layer_nr) + if (!contact_nodes[layer_nr].empty()) { + boundary[layer_nr] = 1; + boundary[layer_nr + 1] = 1; + } + layer_heights.reserve(n); + layer_heights.push_back({m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}); + size_t i = 1; + while (i < n) { + coordf_t height = m_object->get_layer(i)->height; + size_t j = i + 1; + while (j < n && !boundary[j] && + height + m_object->get_layer(j)->height <= max_layer_height + EPSILON) { + height += m_object->get_layer(j)->height; + ++j; + } + layer_heights.push_back({m_object->get_layer(j - 1)->print_z, height, j - 1}); + i = j; + } } else { const coordf_t max_layer_height = m_slicing_params.max_suport_layer_height; const coordf_t min_layer_height = m_slicing_params.min_layer_height; From 27cfd9eb4a350ffd3e00c3540f7698888946cc68 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Mon, 31 Aug 2026 19:31:48 +0200 Subject: [PATCH 37/43] Fix layer height limit mapping for default support filament on mixed-nozzle setups --- src/libslic3r/Slicing.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 9da5615625e..913d12f689f 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -131,7 +131,7 @@ SlicingParameters SlicingParameters::create_from_config( params.max_layer_height = std::numeric_limits::max(); if (object_config.enable_support.value || params.base_raft_layers > 0 || object_config.enforce_support_layers > 0) { // Has some form of support. Add the support layers to the minimum / maximum layer height limits. - auto support_layer_height_limit = [&print_config, &object_config, &restricted_default](int configured, bool min_limit) -> coordf_t { + auto support_layer_height_limit = [&print_config, &object_config, &object_extruders, &restricted_default](int configured, bool min_limit) -> coordf_t { auto from_nozzle = [&print_config, min_limit](int filament) { return min_limit ? min_layer_height_from_nozzle(print_config, filament) : max_layer_height_from_nozzle(print_config, filament); }; @@ -148,6 +148,18 @@ SlicingParameters SlicingParameters::create_from_config( if (found) return limit; } + if (configured == 0 && !object_extruders.empty()) { + // Unrestricted "default" support filament: supports print with the object's own + // extruder(s), so combine those limits. from_nozzle(0) would read the FIRST + // extruder's limits instead, capping support heights by an unrelated fine nozzle + // on mixed-diameter machines. + coordf_t limit = min_limit ? 0. : std::numeric_limits::max(); + for (unsigned int extruder_id : object_extruders) { + coordf_t l = from_nozzle(int(extruder_id + 1)); + limit = min_limit ? std::max(limit, l) : std::min(limit, l); + } + return limit; + } return from_nozzle(configured); }; params.min_layer_height = std::max( @@ -164,8 +176,9 @@ SlicingParameters SlicingParameters::create_from_config( params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, 0)); } else { for (unsigned int extruder_id : object_extruders) { - params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, extruder_id)); - params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, extruder_id)); + // object_extruders holds zero based extruder indices, from_nozzle indices are one based. + params.min_layer_height = std::max(params.min_layer_height, min_layer_height_from_nozzle(print_config, int(extruder_id + 1))); + params.max_layer_height = std::min(params.max_layer_height, max_layer_height_from_nozzle(print_config, int(extruder_id + 1))); } } From 5f68db2fe0769effc72b55215c31b68f35f0c6c4 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 08:23:42 +0200 Subject: [PATCH 38/43] Avoid the Windows near macro as an identifier --- src/libslic3r/PrintObjectSlice.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/libslic3r/PrintObjectSlice.cpp b/src/libslic3r/PrintObjectSlice.cpp index 09fc7c70d7e..79e5b5c4aaa 100644 --- a/src/libslic3r/PrintObjectSlice.cpp +++ b/src/libslic3r/PrintObjectSlice.cpp @@ -1265,11 +1265,11 @@ void PrintObject::apply_extruder_layer_heights() // object volume exists through the pass height, so the run fills it instead. LayerRegion *fill_target = nullptr; ExPolygons filled; - auto fill_by_covering_run = [&](const ExPolygon &piece, const Polygons &near) { + auto fill_by_covering_run = [&](const ExPolygon &piece, const Polygons &nearby) { for (size_t other = 0; other < num_regions; ++ other) { LayerRegion *neighbor = m_layers[idx]->regions()[other]; if (other == region_id || neighbor->combined_layer_count() < 2 || - intersection(near, to_polygons(neighbor->slices.surfaces)).empty()) + intersection(nearby, to_polygons(neighbor->slices.surfaces)).empty()) continue; const size_t run_bottom = idx + 1 - size_t(neighbor->combined_layer_count()); if (run_bottom > 0 && ! intersection(to_polygons(piece), printed_at(run_bottom - 1)).empty()) @@ -1286,10 +1286,10 @@ void PrintObject::apply_extruder_layer_heights() }; ExPolygons dropped; for (ExPolygon &piece : floating) { - const Polygons near = offset(piece, anchor_dist); - if (! intersection(near, anchors).empty()) - fill_by_covering_run(piece, near); - else if (! intersection(near, unprinted_now).empty()) + const Polygons nearby = offset(piece, anchor_dist); + if (! intersection(nearby, anchors).empty()) + fill_by_covering_run(piece, nearby); + else if (! intersection(nearby, unprinted_now).empty()) // Beside or over deferred geometry, with no anchor: would extrude into thin air. dropped.emplace_back(std::move(piece)); } From 57bb765caee7cc8ddbdc4d0a7fed938a35622ca2 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 10:31:29 +0200 Subject: [PATCH 39/43] Include boost/nowide/fstream.hpp where its ifstream is used --- src/libslic3r/Format/OBJ.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libslic3r/Format/OBJ.cpp b/src/libslic3r/Format/OBJ.cpp index e066925a981..10abe8e4dee 100644 --- a/src/libslic3r/Format/OBJ.cpp +++ b/src/libslic3r/Format/OBJ.cpp @@ -10,6 +10,7 @@ #include #include +#include #ifdef _WIN32 #define DIR_SEPARATOR '\\' From 152cee47cb57807ec59ea160583ea0d6900f88c1 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 19:40:12 +0200 Subject: [PATCH 40/43] Add half and quarter layer height steps for supports under the prime tower --- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/Print.cpp | 3 +- src/libslic3r/PrintConfig.cpp | 26 ++++++ src/libslic3r/PrintConfig.hpp | 11 +++ src/libslic3r/Support/SupportParameters.hpp | 11 +++ src/libslic3r/Support/TreeSupport.cpp | 88 ++++++++++++++++++--- src/slic3r/GUI/Tab.cpp | 1 + 7 files changed, 128 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index d3d312390f8..9ddde1698ee 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1195,7 +1195,7 @@ static std::vector s_Preset_print_options{ // BBS "print_extruder_id", "print_extruder_variant", - "independent_support_layer_height", + "independent_support_layer_height", "support_layer_height_step", "support_angle", "support_interface_top_layers", "support_interface_bottom_layers", diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 561d6ad3926..dcf8035e391 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -875,7 +875,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n || opt_key == "filament_is_support" || opt_key == "filament_printable" || opt_key == "filament_change_length" - || opt_key == "independent_support_layer_height") { + || opt_key == "independent_support_layer_height" + || opt_key == "support_layer_height_step") { steps.emplace_back(psWipeTower); // Soluble support interface / non-soluble base interface produces non-soluble interface layers below soluble interface layers. // Thus switching between soluble / non-soluble interface layer material may require recalculation of supports. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ca64111181a..152926a26db 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -492,6 +492,13 @@ static const t_config_enum_values s_keys_map_TimelapseType = { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(TimelapseType) +static const t_config_enum_values s_keys_map_SupportLayerHeightStep = { + {"whole", slhsWholeLayer}, + {"half", slhsHalfLayer}, + {"quarter", slhsQuarterLayer} +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(SupportLayerHeightStep) + static const t_config_enum_values s_keys_map_SkirtType = { { "combined", stCombined }, { "perobject", stPerObject } @@ -7518,6 +7525,25 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionEnum(smsDefault)); + def = this->add("support_layer_height_step", coEnum); + def->label = L("Support layer height step"); + def->category = L("Support"); + def->tooltip = L("Step granularity for independent support layer heights while the prime tower is enabled. " + "With whole layers, support layer heights are multiples of the object layer height. " + "Half or quarter steps also allow multiples like 1.5x or 1.25x, which helps when the support " + "nozzle's maximum layer height sits between two whole multiples. The finer steps place " + "support boundaries between object layers, which adds thin prime tower layers there and " + "increases the purged volume on those toolchanges."); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.emplace_back("whole"); + def->enum_values.emplace_back("half"); + def->enum_values.emplace_back("quarter"); + def->enum_labels.emplace_back(L("100% (whole layers)")); + def->enum_labels.emplace_back(L("50%")); + def->enum_labels.emplace_back(L("25%")); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionEnum(slhsWholeLayer)); + def = this->add("independent_support_layer_height", coBool); def->label = L("Independent support layer height"); def->category = L("Support"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 45da39579fe..e852d3abe96 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -387,6 +387,15 @@ enum TimelapseType : int { tlSmooth }; +// Step granularity for grid-aligned independent support layer heights (prime +// tower enabled): support boundaries may land on whole object layers or on +// half / quarter subdivisions of them. +enum SupportLayerHeightStep : int { + slhsWholeLayer = 0, + slhsHalfLayer, + slhsQuarterLayer, +}; + enum SkirtType { stCombined, stPerObject }; @@ -712,6 +721,7 @@ CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLADisplayOrientation) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SLAPillarConnectionMode) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BrimType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(TimelapseType) +CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SupportLayerHeightStep) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(BedType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(SkirtType) CONFIG_OPTION_ENUM_DECLARE_STATIC_MAPS(InputShaperType) @@ -1981,6 +1991,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE( ((ConfigOptionString, thumbnails)) // BBS: move from PrintObjectConfig ((ConfigOptionBool, independent_support_layer_height)) + ((ConfigOptionEnum, support_layer_height_step)) ((ConfigOptionBool, combine_brims)) // SoftFever ((ConfigOptionPercents, filament_shrink)) diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 37a37f0c617..2982551910b 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -197,6 +197,15 @@ struct SupportParameters { // grid Zs: independent support heights stay enabled but snap to whole multiples // of object layers (tree supports; the classic generator synchronizes instead). grid_aligned_layer_height = independent_layer_height && print_config.enable_prime_tower; + // Sub-layer step for grid-aligned heights: 1 = whole object layers, 2/4 allow + // boundaries on half/quarter subdivisions (thin tower layers appear there). + grid_height_step = 1; + if (grid_aligned_layer_height) { + if (print_config.support_layer_height_step.value == slhsHalfLayer) + grid_height_step = 2; + else if (print_config.support_layer_height_step.value == slhsQuarterLayer) + grid_height_step = 4; + } // force double walls everywhere if wall count is larger than 1 tree_branch_diameter_double_wall_area_scaled = object_config.tree_support_wall_count.value > 1 ? 0.1 : @@ -328,6 +337,8 @@ struct SupportParameters { bool independent_layer_height = false; bool grid_aligned_layer_height = false; + // 1 = whole object layers; 2/4 = half/quarter sub-layer boundaries allowed. + int grid_height_step = 1; const double thresh_big_overhang = Slic3r::sqr(scale_(10)); bool ironing; diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index 561ce3e2ee3..e8063f601e0 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -3260,11 +3260,15 @@ std::vector TreeSupport::plan_layer_heights() layer_heights[layer_nr] = {m_object->get_layer(layer_nr)->print_z, m_object->get_layer(layer_nr)->height, size_t(layer_nr)}; } } else if (m_support_params.grid_aligned_layer_height) { - // Grid-aligned independent heights: group whole object layers into runs whose summed - // height still fits through the support nozzle. Every print_z lands on an object - // layer, so with the prime tower enabled each toolchange still has a tower layer to - // wipe on - the reason free-form heights are not allowed together with the tower. + // Grid-aligned independent heights: support boundaries land on object layers or, + // with a finer configured step, on half/quarter subdivisions of them. Boundaries + // on the object grid keep every toolchange on an existing prime tower layer; + // sub-layer boundaries get their own (thinner) tower layers, which costs extra + // purge there but lets support heights exceed whole multiples (e.g. 1.5x) when + // the support nozzle's maximum lies between two whole multiples. const coordf_t max_layer_height = std::max(m_slicing_params.max_suport_layer_height, m_object->config().layer_height.value); + const coordf_t min_layer_height = m_slicing_params.min_layer_height; + const int height_step = std::max(1, m_support_params.grid_height_step); const size_t n = m_object->layer_count(); // A run must end exactly at a top-contact layer so the support tops keep their // contact Z (mirrors the boundaries the free-form planner inserts), and the contact @@ -3277,17 +3281,77 @@ std::vector TreeSupport::plan_layer_heights() } layer_heights.reserve(n); layer_heights.push_back({m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}); + // The prime tower prints a slab between every pair of adjacent print Zs, so a + // fractional boundary splits an object layer into two tower slabs. Both slabs + // must stay printable by every filament in the print: gate sub-positions on the + // strictest configured minimum layer height (0 = the 0.07 mm slicing default). + coordf_t tower_min_slab = 0.; + for (unsigned int extruder_id : m_object->print()->extruders()) { + coordf_t min_h = m_object->print()->config().min_layer_height.get_at(extruder_id); + tower_min_slab = std::max(tower_min_slab, min_h == 0. ? 0.07 : min_h); + } + // A candidate boundary: a sub-position of an object layer. + struct SubPos { coordf_t z; size_t obj_layer_nr; }; + std::vector subs; size_t i = 1; while (i < n) { - coordf_t height = m_object->get_layer(i)->height; - size_t j = i + 1; - while (j < n && !boundary[j] && - height + m_object->get_layer(j)->height <= max_layer_height + EPSILON) { - height += m_object->get_layer(j)->height; - ++j; + size_t span_end = i + 1; + while (span_end < n && !boundary[span_end]) ++span_end; + // Sub-position ladder over object layers i..span_end-1. Grid positions use the + // layer's print_z verbatim so they stay bit-exact; fractional positions keep a + // quantum comfortably above the tower plan's 1e-3 mm Z-merge epsilon, or the + // layer is left unsplittable. + subs.clear(); + for (size_t l = i; l < span_end; ++l) { + const Layer *layer = m_object->get_layer(l); + const coordf_t h = layer->height; + const int steps = h / height_step > 0.002 ? height_step : 1; + for (int k = 1; k < steps; ++k) { + const coordf_t below = h * k / steps; + if (below < tower_min_slab - EPSILON || h - below < tower_min_slab - EPSILON) + continue; // a tower slab on either side of this boundary would be too thin + subs.push_back({layer->print_z - h + below, l}); + } + subs.push_back({layer->print_z, l}); + } + const coordf_t span_start_z = m_object->get_layer(i)->print_z - m_object->get_layer(i)->height; + const size_t pieces_begin = layer_heights.size(); + coordf_t piece_start = span_start_z; + for (size_t s = 0; s < subs.size(); ++s) { + // Close the piece at the span end, or right before the sub-position that + // would push it past the maximum support layer height. + if (s + 1 == subs.size() || subs[s + 1].z - piece_start > max_layer_height + EPSILON) { + layer_heights.push_back({subs[s].z, subs[s].z - piece_start, subs[s].obj_layer_nr}); + piece_start = subs[s].z; + } + } + // Rebalance a trailing piece thinner than the support minimum. + if (layer_heights.size() - pieces_begin >= 2 && layer_heights.back().height < min_layer_height - EPSILON) { + LayerHeightData tail = layer_heights.back(); layer_heights.pop_back(); + LayerHeightData prev = layer_heights.back(); layer_heights.pop_back(); + const coordf_t combined_start = prev.print_z - prev.height; + const coordf_t combined = tail.print_z - combined_start; + if (combined <= max_layer_height + EPSILON) { + layer_heights.push_back({tail.print_z, combined, tail.obj_layer_nr}); + } else { + // Cannot merge: split the two pieces as evenly as the sub-grid allows. + const coordf_t ideal = combined_start + combined / 2.; + size_t best = subs.size(); + for (size_t s = 0; s < subs.size(); ++s) + if (subs[s].z > combined_start + EPSILON && subs[s].z < tail.print_z - EPSILON && + (best == subs.size() || std::abs(subs[s].z - ideal) < std::abs(subs[best].z - ideal))) + best = s; + if (best < subs.size()) { + layer_heights.push_back({subs[best].z, subs[best].z - combined_start, subs[best].obj_layer_nr}); + layer_heights.push_back({tail.print_z, tail.print_z - subs[best].z, tail.obj_layer_nr}); + } else { + // No usable split point: keep the original pieces, thin tail and all. + layer_heights.push_back(prev); + layer_heights.push_back(tail); + } + } } - layer_heights.push_back({m_object->get_layer(j - 1)->print_z, height, j - 1}); - i = j; + i = span_end; } } else { const coordf_t max_layer_height = m_slicing_params.max_suport_layer_height; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 09112607b0c..0bdb85bdf2b 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -3191,6 +3191,7 @@ void TabPrint::build() optgroup->append_single_option_line("bridge_no_support", "support_settings_advanced#dont-support-bridges"); optgroup->append_single_option_line("max_bridge_length", "support_settings_advanced"); optgroup->append_single_option_line("independent_support_layer_height", "support_settings_advanced#independent-support-layer-height"); + optgroup->append_single_option_line("support_layer_height_step"); optgroup = page->new_optgroup(L("Tree supports"), L"param_support_tree"); optgroup->append_single_option_line("tree_support_tip_diameter", "support_settings_tree#tip-diameter"); From c7de231e9131c3901aafabf3dade2d2cabb1b01d Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 20:08:52 +0200 Subject: [PATCH 41/43] Keep the prime tower on whole slabs with fractional support heights and offer sub-base preferred layer heights --- src/libslic3r/GCode/ToolOrdering.cpp | 21 ++++++++++++-- src/libslic3r/Support/TreeSupport.cpp | 19 +++++++------ src/slic3r/GUI/Plater.cpp | 40 ++++++++++++++++++++++++--- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 06b579c3c31..113cc0e1d86 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -1382,9 +1382,24 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ lt.has_wipe_tower = config.enable_wrapping_detection; } + // Fractional independent support heights place support-only layers between the object + // grid Zs. Those layers get no tower layer of their own: the tower would have to print + // a sub-minimum slab there, and the toolchange they carry switches TO the support + // filament, which tolerates an unpurged nozzle (the residue lands in the support). + // The switch back to an object filament happens on an object-grid layer with a full + // tower slab. Smooth timelapse needs a tower layer on every print layer, so it keeps + // the old behavior. + auto support_only_off_grid = [&config](const LayerTools <) { + return lt.has_support && !lt.has_object && + config.independent_support_layer_height && + config.support_layer_height_step != slhsWholeLayer && + config.timelapse_type != TimelapseType::tlSmooth; + }; + //FIXME this is a hack to get the ball rolling. for (LayerTools < : m_layer_tools) - lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0)) + lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && !support_only_off_grid(lt) && + (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0)) || lt.print_z < object_bottom_z + EPSILON; // Test for a raft, insert additional wipe tower layer to fill in the raft separation gap. @@ -1453,6 +1468,8 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ } for (int i = first_wt_idx + 1; i < last_wt_idx; ++i) { LayerTools < = m_layer_tools[i]; + if (support_only_off_grid(lt)) + continue; // no tower slab on fractional support-only layers lt.has_wipe_tower = true; // GCode::process_layer emits wipe-tower G-code inside `for (extruder_id : layer_tools.extruders)`. // An empty extruders vector here would silently skip wipe tower output, leaving the tower @@ -1531,7 +1548,7 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ double last_wipe_tower_print_z = lt_next.print_z; while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { - if (!config.enable_wrapping_detection) + if (!config.enable_wrapping_detection && !support_only_off_grid(m_layer_tools[j])) m_layer_tools[j].has_wipe_tower = true; last_wipe_tower_print_z = m_layer_tools[j].print_z; } diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index e8063f601e0..d9c3ed65cf7 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -3281,15 +3281,18 @@ std::vector TreeSupport::plan_layer_heights() } layer_heights.reserve(n); layer_heights.push_back({m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}); - // The prime tower prints a slab between every pair of adjacent print Zs, so a - // fractional boundary splits an object layer into two tower slabs. Both slabs - // must stay printable by every filament in the print: gate sub-positions on the - // strictest configured minimum layer height (0 = the 0.07 mm slicing default). + // Fractional support-only layers normally get no prime tower layer (their + // toolchange goes straight to the support filament), so they impose nothing on + // the tower. Smooth timelapse is the exception: it needs a tower layer on every + // print layer, so there a fractional boundary splits an object layer into two + // tower slabs and both must stay printable by every filament - gate sub-positions + // on the strictest configured minimum layer height (0 = the 0.07 mm default). coordf_t tower_min_slab = 0.; - for (unsigned int extruder_id : m_object->print()->extruders()) { - coordf_t min_h = m_object->print()->config().min_layer_height.get_at(extruder_id); - tower_min_slab = std::max(tower_min_slab, min_h == 0. ? 0.07 : min_h); - } + if (m_object->print()->config().timelapse_type.value == TimelapseType::tlSmooth) + for (unsigned int extruder_id : m_object->print()->extruders()) { + coordf_t min_h = m_object->print()->config().min_layer_height.get_at(extruder_id); + tower_min_slab = std::max(tower_min_slab, min_h == 0. ? 0.07 : min_h); + } // A candidate boundary: a sub-position of an object layer. struct SubPos { coordf_t z; size_t obj_layer_nr; }; std::vector subs; diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 4daf99735a6..bad4ed84422 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -11039,7 +11039,21 @@ static void fill_nozzle_layer_height_combo(ComboBox *combo, size_t extruder_idx) std::max(0., preferred->get_at(extruder_idx)) : 0.; wxString current_label; - if (base_height > EPSILON) + if (base_height > EPSILON) { + // Fractions of the object layer height for finer nozzles: selecting one lowers the + // object layer height to it and pins the other extruders to their current effective + // height, so the printed result is unchanged and the configuration stays valid. + for (int d = 4; d >= 2; d /= 2) { + const double height = base_height / d; + if (height + EPSILON < floor_lh || height > cap + EPSILON) + continue; + if (std::abs(height * 1000. - std::round(height * 1000.)) > 1e-6) + continue; // only cleanly representable heights + const wxString label = nozzle_combo_label(height); + combo->AppendString(label); + if (current > 0. && std::abs(height - current) < EPSILON) + current_label = label; + } for (int n = 1; n * base_height <= cap + EPSILON; ++n) { const double height = n * base_height; if (height + EPSILON < floor_lh) @@ -11049,6 +11063,7 @@ static void fill_nozzle_layer_height_combo(ComboBox *combo, size_t extruder_idx) if (current > 0. && std::abs(height - current) < EPSILON) current_label = label; } + } if (current_label.empty() && current > 0.) { // An explicit preference no longer among the valid values (the object layer height or // the limits changed after it was set) is still displayed truthfully; slicing warns. @@ -11233,9 +11248,10 @@ void Sidebar::update_nozzle_settings(bool switch_machine) ComboBox* lh_combo = new ComboBox(nozzle_panel, wxID_ANY, wxEmptyString, wxDefaultPosition, {-1, FromDIP(32)}, 0, nullptr, wxCB_READONLY); - lh_combo->SetToolTip(_L("Layer height this extruder should print with: an integer multiple of the object " - "layer height within this extruder's layer height limits. Default keeps the object " - "layer height.")); + lh_combo->SetToolTip(_L("Layer height this extruder should print with: a multiple of the object layer " + "height within this extruder's layer height limits. Default keeps the object layer " + "height. Selecting a fraction of the object layer height lowers the object layer " + "height to it and keeps the other extruders at their current effective height.")); fill_nozzle_layer_height_combo(lh_combo, i); lh_combo->Bind(wxEVT_COMBOBOX, [lh_combo, i](wxCommandEvent& event) { @@ -11257,6 +11273,22 @@ void Sidebar::update_nozzle_settings(bool switch_machine) if (i >= heights.size() || std::abs(heights[i] - new_height) < EPSILON) return; heights[i] = new_height; + const auto* base_opt = wxGetApp().preset_bundle->prints.get_edited_preset().config.option("layer_height"); + const double base_height = base_opt != nullptr ? base_opt->value : 0.; + if (new_height > EPSILON && base_height > EPSILON && new_height < base_height - EPSILON) { + // A preference below the object layer height: the engine slices the object on + // the finest preferred height, so lower the object layer height to it and pin + // every extruder that followed the old value - the printed result is identical + // and the configuration stays valid. + for (size_t j = 0; j < heights.size(); ++j) + if (j != i && heights[j] <= EPSILON) + heights[j] = base_height; + if (Tab* print_tab = wxGetApp().get_tab(Preset::TYPE_PRINT); print_tab != nullptr) { + DynamicPrintConfig print_conf = wxGetApp().preset_bundle->prints.get_edited_preset().config; + print_conf.set_key_value("layer_height", new ConfigOptionFloat(new_height)); + print_tab->load_config(print_conf); + } + } new_conf.set_key_value("extruder_layer_height", new ConfigOptionFloats(heights)); // As with the diameter combo: marks the printer preset modified and propagates the // change without switching presets. Do not event.Skip() (plain ComboBox, see above). From 31ae9b1abf50e006a7ca05f52a8b82b7dcfed7eb Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Tue, 1 Sep 2026 21:31:50 +0200 Subject: [PATCH 42/43] Bridge fractional support layer toolchanges onto full prime tower slabs --- src/libslic3r/GCode/ToolOrdering.cpp | 60 ++++++++++++++++++--- src/libslic3r/GCode/ToolOrdering.hpp | 1 + src/libslic3r/Print.cpp | 28 ++++++++-- src/slic3r/GUI/BackgroundSlicingProcess.cpp | 3 ++ 4 files changed, 81 insertions(+), 11 deletions(-) diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 113cc0e1d86..40ea183be21 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -659,13 +659,15 @@ void ToolOrdering::sort_and_build_data(const Print& print, unsigned int first_ex max_layer_height = calc_max_layer_height(print.config(), max_layer_height); + this->bridge_fractional_support_layers(print.config()); this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); if (this->insert_wipe_tower_extruder()) { reorder_extruders_for_minimum_flush_volume(reorder_first_layer); // Orca reorders a second time here (BBS has no such path); re-enforce so the // mixed sub-layer component order survives the extra pass. this->enforce_mixed_component_order(); - this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); + this->bridge_fractional_support_layers(print.config()); + this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); } this->collect_extruder_statistics(prime_multi_material); @@ -683,13 +685,15 @@ void ToolOrdering::sort_and_build_data(const PrintObject& object , unsigned int double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); + this->bridge_fractional_support_layers(object.print()->config()); this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); if (this->insert_wipe_tower_extruder()) { reorder_extruders_for_minimum_flush_volume(reorder_first_layer); // Orca reorders a second time here (BBS has no such path); re-enforce so the // mixed sub-layer component order survives the extra pass. this->enforce_mixed_component_order(); - this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); + this->bridge_fractional_support_layers(object.print()->config()); + this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); } this->collect_extruder_statistics(prime_multi_material); @@ -1352,6 +1356,34 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } +// Fractional independent support heights place support-only layers between object grid Zs. +// Give each such layer its tool as the incumbent: the layer below ends with that tool, so +// the toolchange (if one is needed) happens there and purges on a full-height tower slab, +// and the fractional layer itself changes no tools - it then needs no tower layer at all. +void ToolOrdering::bridge_fractional_support_layers(const PrintConfig &config) +{ + if (!config.independent_support_layer_height || config.support_layer_height_step == slhsWholeLayer || + config.timelapse_type == TimelapseType::tlSmooth) + return; + for (size_t i = 1; i < m_layer_tools.size(); ++i) { + LayerTools < = m_layer_tools[i]; + if (!(lt.has_support && !lt.has_object) || lt.extruders.size() != 1) + continue; + const unsigned int tool = lt.extruders.front(); + // Per-extruder layer height combining leaves many LayerTools without extrusions; + // the incumbent comes from the last layer that actually prints something. + LayerTools *below = nullptr; + for (size_t j = i; j-- > 0; ) + if (!m_layer_tools[j].extruders.empty()) { below = &m_layer_tools[j]; break; } + if (below == nullptr || below->extruders.back() == tool) + continue; + auto it = std::find(below->extruders.begin(), below->extruders.end(), tool); + if (it != below->extruders.end()) + below->extruders.erase(it); + below->extruders.emplace_back(tool); + } +} + void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height) { if (m_layer_tools.empty()) @@ -1468,8 +1500,22 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ } for (int i = first_wt_idx + 1; i < last_wt_idx; ++i) { LayerTools < = m_layer_tools[i]; - if (support_only_off_grid(lt)) - continue; // no tower slab on fractional support-only layers + if (support_only_off_grid(lt)) { + // Prefer no tower slab on a fractional support-only layer, but only when + // the slab bridging over it stays within the maximum layer height - + // otherwise this layer keeps its (thin) tower slab. + coordf_t prev_z = m_layer_tools[first_wt_idx].print_z; + for (int j = i - 1; j >= first_wt_idx; --j) + if (m_layer_tools[j].has_wipe_tower) { prev_z = m_layer_tools[j].print_z; break; } + coordf_t next_z = m_layer_tools[last_wt_idx].print_z; + for (int j = i + 1; j <= last_wt_idx; ++j) + if (m_layer_tools[j].has_wipe_tower || !support_only_off_grid(m_layer_tools[j])) { + next_z = m_layer_tools[j].print_z; + break; + } + if (next_z - prev_z <= max_layer_height + EPSILON) + continue; + } lt.has_wipe_tower = true; // GCode::process_layer emits wipe-tower G-code inside `for (extruder_id : layer_tools.extruders)`. // An empty extruders vector here would silently skip wipe tower output, leaving the tower @@ -1541,19 +1587,21 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ if (lt_last_printing == nullptr || lt_next.extruders.empty()) continue; } - if (!lt_next.has_wipe_tower && (lt_next.extruders.front() != lt_last_printing->extruders.back() || lt_next.extruders.size() > 1)) + if (!lt_next.has_wipe_tower && !support_only_off_grid(lt_next) && + (lt_next.extruders.front() != lt_last_printing->extruders.back() || lt_next.extruders.size() > 1)) lt_next.has_wipe_tower = true; // We should also check that the next wipe tower layer is no further than max_layer_height: unsigned int j = i+1; double last_wipe_tower_print_z = lt_next.print_z; while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { - if (!config.enable_wrapping_detection && !support_only_off_grid(m_layer_tools[j])) + if (!config.enable_wrapping_detection) m_layer_tools[j].has_wipe_tower = true; last_wipe_tower_print_z = m_layer_tools[j].print_z; } } + // Calculate the wipe_tower_layer_height values. coordf_t wipe_tower_print_z_last = 0.; for (LayerTools < : m_layer_tools) diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index e6200fc5331..975f1a1ab02 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -392,6 +392,7 @@ class ToolOrdering void initialize_layers(std::vector &zs); void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height); + void bridge_fractional_support_layers(const PrintConfig &config); bool insert_wipe_tower_extruder(); void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height); void collect_extruder_statistics(bool prime_multi_material); diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index dcf8035e391..39c0dd436e8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -5224,7 +5224,16 @@ void Print::_make_wipe_tower() for (auto& layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers ++layer_idx; - if (!layer_tools.has_wipe_tower) continue; + if (!layer_tools.has_wipe_tower) { + // A layer without a tower slab (fractional support-only layer) still switches + // filaments through the plain toolchange path. Keep the plan's tool chain and + // filament bookkeeping in sync, so the next tower layer plans the switch back. + if (!layer_tools.extruders.empty()) { + used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); + current_filament_id = layer_tools.extruders.back(); + } + continue; + } bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, current_filament_id); @@ -5277,7 +5286,7 @@ void Print::_make_wipe_tower() wipe_volume_nc = 15.f; } - wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id, + wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, filament_id, wipe_volume_ec, wipe_volume_nc, volume_to_purge); current_filament_id = filament_id; } @@ -5382,8 +5391,13 @@ void Print::_make_wipe_tower() { unsigned int current_extruder_id = m_wipe_tower_data.tool_ordering.all_extruders().back(); for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers - if (!layer_tools.has_wipe_tower) + if (!layer_tools.has_wipe_tower) { + // Keep the tool chain in sync across layers that switch filament without + // a tower slab (fractional support-only layers). + if (!layer_tools.extruders.empty()) + current_extruder_id = layer_tools.extruders.back(); continue; + } while (layers_to_print_idx + 1 < layers_to_print.size() && layers_to_print[layers_to_print_idx].first + EPSILON < layer_tools.print_z) { ++layers_to_print_idx; @@ -5424,8 +5438,12 @@ void Print::_make_wipe_tower() current_extruder_id = local_z_toolchanges.back().new_tool; } - const std::vector nominal_layer_extruders = - rotate_extruders_to_start_with(layer_tools.extruders, current_extruder_id); + // Plan in the same order G-code emission walks the layer's extruders. The + // flush-volume reorder starts each layer with the incumbent, so this used to + // equal the rotated order - but a layer that deliberately ENDS with a tool + // (bridging for fractional support layers) needs the raw order or the plan + // misses the trailing toolchange. + const std::vector &nominal_layer_extruders = layer_tools.extruders; wipe_tower.plan_toolchange((float) layer_tools.print_z, (float) layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); diff --git a/src/slic3r/GUI/BackgroundSlicingProcess.cpp b/src/slic3r/GUI/BackgroundSlicingProcess.cpp index 64c52c6e72f..4aa8d555b60 100644 --- a/src/slic3r/GUI/BackgroundSlicingProcess.cpp +++ b/src/slic3r/GUI/BackgroundSlicingProcess.cpp @@ -468,6 +468,9 @@ void BackgroundSlicingProcess::call_process(std::exception_ptr& ex) throw() assert(m_print->canceled()); ex = std::current_exception(); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":got cancelled exception" << std::endl; + } catch (const std::exception &e) { + ex = std::current_exception(); + BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":got exception: " << e.what() << std::endl; } catch (...) { ex = std::current_exception(); BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ":got other exception" << std::endl; From 4ce76b57b717ca3c42287f5ce3b7e26067c79e83 Mon Sep 17 00:00:00 2001 From: LixNix <105106115+LixNix@users.noreply.github.com> Date: Sat, 5 Sep 2026 07:14:15 +0200 Subject: [PATCH 43/43] Keep fractional support layers and the prime tower consistent in every slicing mode Fractional support layer heights (half/quarter steps) let support-only layers fall between object layers. Those layers carry no prime tower slab and switch filament directly, which the tower plan and the G-code emission must agree on in every configuration. - Record the tool loaded at each planned tower layer and re-sync the tower's current tool per layer (both tower types), so switches that happen off the tower never leave it generating fills for a stale tool. - Print by layer from the print-wide tool ordering when a sequential print carries a tower: initial tool, extruder set, layer count and skip-flush bookkeeping now come from that ordering. - Count tower partitions and the empty-layer fix-up on the same rotated tool chain the plan and the emission walk; never grant a slab to an empty fractional layer; keep fractional entries transparent in the gap walk; take the raft base from the lowest object; count only slab layers for wrapping detection. - BBL tower: plan in the rotated order, honour the recorded start tool in the block and wall planning, and index per-layer nozzle maps by the tool-ordering layer. - Tree support planner: use the support extruders' own minimum when supports have their own filament or nozzle, keep tail splits above it, floor the maximum at one sub-step, and close the support top exactly at the configured top gap (on a sub-position where the step allows). - Re-plan supports when the prime tower, single-extruder multi-material or the timelapse mode changes; grey out the step where it has no effect; correct both tooltips. - With sparse tower layers disabled, the Type2 tower now prints its first layer (brim and base) even when the first layer uses a single filament; previously that planned layer was dropped. --- src/libslic3r/GCode.cpp | 56 +++++-- src/libslic3r/GCode/ToolOrdering.cpp | 153 ++++++++++-------- src/libslic3r/GCode/ToolOrdering.hpp | 4 +- src/libslic3r/GCode/WipeTower.cpp | 52 ++++-- src/libslic3r/GCode/WipeTower.hpp | 12 ++ src/libslic3r/GCode/WipeTower2.cpp | 37 +++-- src/libslic3r/GCode/WipeTower2.hpp | 4 + src/libslic3r/Print.cpp | 40 +++-- src/libslic3r/PrintConfig.cpp | 13 +- src/libslic3r/Slicing.cpp | 5 + src/libslic3r/Slicing.hpp | 2 + src/libslic3r/Support/SupportMaterial.hpp | 4 +- src/libslic3r/Support/SupportParameters.hpp | 2 +- src/libslic3r/Support/TreeSupport.cpp | 165 +++++++++++++------- src/slic3r/GUI/ConfigManipulation.cpp | 4 + 15 files changed, 375 insertions(+), 178 deletions(-) diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp index 5c6b6381270..9c73ef2179c 100644 --- a/src/libslic3r/GCode.cpp +++ b/src/libslic3r/GCode.cpp @@ -1041,7 +1041,7 @@ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std if (gcodegen.writer().filament() != nullptr && !filament_end_gcode.empty()) { DynamicConfig config; config.set_key_value("current_filament_id", new ConfigOptionInt((int) old_filament_id)); - config.set_key_value("current_nozzle_id", new ConfigOptionInt(nozzle_id_for_gcode_placeholder(group_result, (int) old_filament_id, (int) gcodegen.writer().filament()->extruder_id(), m_layer_idx))); + config.set_key_value("current_nozzle_id", new ConfigOptionInt(nozzle_id_for_gcode_placeholder(group_result, (int) old_filament_id, (int) gcodegen.writer().filament()->extruder_id(), gcodegen.m_layer_index))); config.set_key_value("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get()))); config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get()))); config.set_key_value("layer_num", new ConfigOptionInt(gcodegen.m_layer_index)); @@ -1106,17 +1106,17 @@ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std int old_filament_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->id() : -1; int old_extruder_id = gcodegen.writer().filament() ? (int)gcodegen.writer().filament()->extruder_id() : -1; // Logical nozzle ids for old/new filament (null-safe -> extruder id). - int old_nozzle_id = nozzle_id_for_gcode_placeholder(group_result, old_filament_id, old_extruder_id, m_layer_idx); - int next_nozzle_id = nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx); + int old_nozzle_id = nozzle_id_for_gcode_placeholder(group_result, old_filament_id, old_extruder_id, gcodegen.m_layer_index); + int next_nozzle_id = nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, gcodegen.m_layer_index); config.set_key_value("previous_extruder", new ConfigOptionInt(old_filament_id)); config.set_key_value("next_extruder", new ConfigOptionInt(new_filament_id)); // current_hotend/next_hotend (see hotend_id_for_gcode_placeholder): multi-nozzle H2C -> -1 // (static; dynamic branch dormant), X2D -> -1, existing printers -> extruder id. config.set_key_value("current_hotend", new ConfigOptionInt( - hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, old_filament_id, old_extruder_id, m_layer_idx))); + hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, old_filament_id, old_extruder_id, gcodegen.m_layer_index))); config.set_key_value("next_hotend", new ConfigOptionInt( - hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, new_filament_id, (int) gcodegen.get_extruder_id(new_filament_id), m_layer_idx))); + hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, new_filament_id, (int) gcodegen.get_extruder_id(new_filament_id), gcodegen.m_layer_index))); config.set_key_value("current_nozzle_id", new ConfigOptionInt(old_nozzle_id)); config.set_key_value("next_nozzle_id", new ConfigOptionInt(next_nozzle_id)); config.set_key_value("current_filament_id", new ConfigOptionInt(old_filament_id)); @@ -1319,7 +1319,7 @@ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std // Orca: null-safe, layer-aware nozzle lookup — group_result may be null on // non-multi-nozzle paths (the helper falls back to the extruder id). toolchange_command = gcodegen.writer().toolchange(new_filament_id, - nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx)); + nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, gcodegen.m_layer_index)); if (!custom_gcode_changes_tool(toolchange_gcode_str, gcodegen.writer().toolchange_prefix(), new_filament_id)) toolchange_gcode_str += toolchange_command; else { @@ -1393,9 +1393,9 @@ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std gcodegen.placeholder_parser().set("current_filament_id", new_filament_id); gcodegen.placeholder_parser().set("current_extruder_id", new_extruder_id); gcodegen.placeholder_parser().set("current_nozzle_id", - nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx)); + nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, gcodegen.m_layer_index)); gcodegen.placeholder_parser().set("current_hotend", - hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, new_filament_id, new_extruder_id, m_layer_idx)); + hotend_id_for_gcode_placeholder(gcodegen.m_config, group_result, new_filament_id, new_extruder_id, gcodegen.m_layer_index)); { size_t fi = gcodegen.get_filament_config_index(new_filament_id); gcodegen.placeholder_parser().set("retraction_distance_when_cut", gcodegen.m_config.retraction_distances_when_cut.get_at(fi)); @@ -1412,7 +1412,7 @@ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std DynamicConfig config; config.set_key_value("filament_extruder_id", new ConfigOptionInt(new_filament_id)); config.set_key_value("current_filament_id", new ConfigOptionInt(new_filament_id)); - config.set_key_value("current_nozzle_id", new ConfigOptionInt(nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, m_layer_idx))); + config.set_key_value("current_nozzle_id", new ConfigOptionInt(nozzle_id_for_gcode_placeholder(group_result, new_filament_id, new_extruder_id, gcodegen.m_layer_index))); config.set_key_value("nozzle_diameter_at_nozzle_id", new ConfigOptionFloats(get_nozzle_diameters_by_nozzle_id(group_result.get()))); config.set_key_value("nozzle_volume_types", new ConfigOptionStrings(get_nozzle_volume_types_by_nozzle_id(group_result.get()))); start_filament_gcode_str = gcodegen.placeholder_parser_process("filament_start_gcode", filament_start_gcode, new_filament_id, &config); @@ -2261,7 +2261,11 @@ static bool custom_gcode_changes_tool(const std::string& custom_gcode, const std bool ignore_sparse = false; if (gcodegen.config().wipe_tower_no_sparse_layers.value) { - ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool); + // Mirror tool_change(): the Type2 tower never skips its first layer (brim + base + // slab), even when that layer carries no toolchange. + ignore_sparse = (m_tool_changes[m_layer_idx].size() == 1 && + m_tool_changes[m_layer_idx].front().initial_tool == m_tool_changes[m_layer_idx].front().new_tool && + (gcodegen.wipe_tower_type() != WipeTowerType::Type2 || m_layer_idx != 0)); } if ((m_enable_timelapse_print || m_enable_wrapping_detection) && m_is_first_print) { @@ -3234,7 +3238,11 @@ void GCode::_do_export(Print& print, GCodeOutputStream& file, ThumbnailsGenerato // How many times will be change_layer() called? // change_layer() in turn increments the progress bar status. m_layer_count = 0; - if (print.config().print_sequence == PrintSequence::ByObject) { + // A by-object print with a wipe tower is emitted by layer (see the sequential branch below), + // so it changes layer once per merged print Z like a by-layer print. + const bool count_layers_by_object = print.config().print_sequence == PrintSequence::ByObject && + !(print.has_wipe_tower() && print.tool_ordering().has_wipe_tower()); + if (count_layers_by_object) { // Add each of the object's layers separately. for (auto object : print.objects()) { std::vector zs; @@ -3517,7 +3525,28 @@ void GCode::_do_export(Print& print, GCodeOutputStream& file, ThumbnailsGenerato // Use the extruder IDs collected from Regions. this->set_extruders(print.extruders()); - has_wipe_tower = print.has_wipe_tower() && tool_ordering.has_wipe_tower(); + // With a wipe tower the layers are emitted by layer (see below) from the print-wide + // ordering the tower was planned on: the per-object ordering lacks layers that only + // other objects print (e.g. support-only layers), which would desynchronize the + // positional tower consumption, and the last object's own ordering may not even carry + // a tower although the print does. Everything the by-layer branch derives from its + // ordering (initial tool, extruder set, first filaments) has to come from that same + // ordering too, or the first layer may end on a tool the tower never planned a change + // to, or a filament only the print-wide ordering knows (custom per-layer change) has + // no extruder entry in the writer. + has_wipe_tower = print.has_wipe_tower() && print.tool_ordering().has_wipe_tower(); + if (has_wipe_tower) { + tool_ordering = print.tool_ordering(); + tool_ordering.assign_custom_gcodes(print); + initial_extruder_id = (wipe_tower_type == WipeTowerType::Type2 && !print.config().single_extruder_multi_material_priming) ? + tool_ordering.all_extruders().back() : + tool_ordering.first_extruder(); + initial_non_support_extruder_id = (unsigned int) -1; + std::fill(first_non_support_filaments.begin(), first_non_support_filaments.end(), -1); + std::fill(first_filaments.begin(), first_filaments.end(), -1); + tool_ordering.cal_non_support_filaments(print.config(), initial_non_support_extruder_id, first_non_support_filaments, first_filaments); + this->set_extruders(tool_ordering.all_extruders()); + } } else { // Find tool ordering for all the objects at once, and the initial extruder ID. // If the tool ordering has been pre-calculated by Print class for wipe tower already, reuse it. @@ -8437,7 +8466,8 @@ LayerResult GCode::process_layer( } } - if (print.config().print_sequence == PrintSequence::ByLayer && m_enable_exclude_object && print.config().support_object_skip_flush.value) { + // By-layer emission (including a sequential print that falls back to it for the wipe tower). + if (single_object_instance_idx == size_t(-1) && m_enable_exclude_object && print.config().support_object_skip_flush.value) { std::set all_label_ids; for (InstanceToPrint &instance : filament_to_print_instances[extruder_id].first) all_label_ids.insert(instance.label_object_id); diff --git a/src/libslic3r/GCode/ToolOrdering.cpp b/src/libslic3r/GCode/ToolOrdering.cpp index 40ea183be21..20990587582 100644 --- a/src/libslic3r/GCode/ToolOrdering.cpp +++ b/src/libslic3r/GCode/ToolOrdering.cpp @@ -625,7 +625,9 @@ bool ToolOrdering::insert_wipe_tower_extruder() bool changed = false; const unsigned int wipe_extruder = (unsigned int)(m_print_config_ptr->wipe_tower_filament - 1); for (LayerTools < : m_layer_tools) { - if (lt.wipe_tower_partitions > 0) { + // Only layers that carry a tower slab (fill_wipe_tower_partitions has run): a + // fractional support-only layer without one must not switch to the tower filament. + if (lt.wipe_tower_partitions > 0 && lt.has_wipe_tower) { if (std::find(lt.extruders.begin(), lt.extruders.end(), wipe_extruder) == lt.extruders.end()) { lt.extruders.emplace_back(wipe_extruder); changed = true; @@ -646,28 +648,31 @@ void ToolOrdering::sort_and_build_data(const Print& print, unsigned int first_ex m_sorted = true; double max_layer_height = 0.; - double object_bottom_z = 0.; + // The lowest object bottom: below it the tower needs its base (raft, support under a + // floating part); above it a lower object's fractional support layers must not be taken + // for raft layers. + double object_bottom_z = std::numeric_limits::max(); for (const auto& object : print.objects()) { for (const Layer* layer : object->layers()) { if (layer->has_extrusions()) { - object_bottom_z = layer->print_z - layer->height; + object_bottom_z = std::min(object_bottom_z, layer->print_z - layer->height); break; } } max_layer_height = std::max(max_layer_height, object->config().layer_height.value); } + if (object_bottom_z == std::numeric_limits::max()) + object_bottom_z = 0.; max_layer_height = calc_max_layer_height(print.config(), max_layer_height); - this->bridge_fractional_support_layers(print.config()); this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); if (this->insert_wipe_tower_extruder()) { reorder_extruders_for_minimum_flush_volume(reorder_first_layer); // Orca reorders a second time here (BBS has no such path); re-enforce so the // mixed sub-layer component order survives the extra pass. this->enforce_mixed_component_order(); - this->bridge_fractional_support_layers(print.config()); - this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); + this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height); } this->collect_extruder_statistics(prime_multi_material); @@ -685,15 +690,13 @@ void ToolOrdering::sort_and_build_data(const PrintObject& object , unsigned int double max_layer_height = calc_max_layer_height(object.print()->config(), object.config().layer_height); - this->bridge_fractional_support_layers(object.print()->config()); this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); if (this->insert_wipe_tower_extruder()) { reorder_extruders_for_minimum_flush_volume(reorder_first_layer); // Orca reorders a second time here (BBS has no such path); re-enforce so the // mixed sub-layer component order survives the extra pass. this->enforce_mixed_component_order(); - this->bridge_fractional_support_layers(object.print()->config()); - this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); + this->fill_wipe_tower_partitions(object.print()->config(), object.layers().front()->print_z - object.layers().front()->height, max_layer_height); } this->collect_extruder_statistics(prime_multi_material); @@ -725,6 +728,8 @@ ToolOrdering::ToolOrdering(const PrintObject &object, unsigned int first_extrude for (auto layer : object.support_layers()) zs.emplace_back(layer->print_z); this->initialize_layers(zs); + for (auto layer : object.layers()) + this->tools_for_layer(layer->print_z).on_object_grid = true; } // Collect extruders reuqired to print the layers. Add dontcare extruders @@ -776,6 +781,9 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool max_layer_height = std::max(max_layer_height, object->config().layer_height.value); } this->initialize_layers(zs); + for (auto object : print.objects()) + for (auto layer : object->layers()) + this->tools_for_layer(layer->print_z).on_object_grid = true; } max_layer_height = calc_max_layer_height(print.config(), max_layer_height); @@ -1265,7 +1273,8 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto // Collect the support extruders. for (auto support_layer : object.support_layers()) { LayerTools &layer_tools = this->tools_for_layer(support_layer->print_z); - layer_tools.layer_height = support_layer->height; + if (!layer_tools.has_object) // a shared Z keeps the object layer height + layer_tools.layer_height = support_layer->height; ExtrusionRole role = support_layer->support_fills.role(); bool has_support = false; bool has_interface = false; @@ -1356,32 +1365,16 @@ void ToolOrdering::collect_extruders(const PrintObject &object, const std::vecto } -// Fractional independent support heights place support-only layers between object grid Zs. -// Give each such layer its tool as the incumbent: the layer below ends with that tool, so -// the toolchange (if one is needed) happens there and purges on a full-height tower slab, -// and the fractional layer itself changes no tools - it then needs no tower layer at all. -void ToolOrdering::bridge_fractional_support_layers(const PrintConfig &config) +// The chain the tower plan and the G-code emission walk: a layer starts with the tool still +// loaded whenever it uses it (GCode::process_layer rotates the layer's extruders to it), so a +// custom or cyclic sequence never counts a phantom initial toolchange. +static std::vector rotate_extruders_to_start_with(const std::vector &extruders, unsigned int start_extruder) { - if (!config.independent_support_layer_height || config.support_layer_height_step == slhsWholeLayer || - config.timelapse_type == TimelapseType::tlSmooth) - return; - for (size_t i = 1; i < m_layer_tools.size(); ++i) { - LayerTools < = m_layer_tools[i]; - if (!(lt.has_support && !lt.has_object) || lt.extruders.size() != 1) - continue; - const unsigned int tool = lt.extruders.front(); - // Per-extruder layer height combining leaves many LayerTools without extrusions; - // the incumbent comes from the last layer that actually prints something. - LayerTools *below = nullptr; - for (size_t j = i; j-- > 0; ) - if (!m_layer_tools[j].extruders.empty()) { below = &m_layer_tools[j]; break; } - if (below == nullptr || below->extruders.back() == tool) - continue; - auto it = std::find(below->extruders.begin(), below->extruders.end(), tool); - if (it != below->extruders.end()) - below->extruders.erase(it); - below->extruders.emplace_back(tool); - } + std::vector rotated = extruders; + auto it = std::find(rotated.begin(), rotated.end(), start_extruder); + if (it != rotated.end()) + std::rotate(rotated.begin(), it, rotated.end()); + return rotated; } void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height) @@ -1389,15 +1382,16 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ if (m_layer_tools.empty()) return; - // Count the minimum number of tool changes per layer. + // Count the minimum number of tool changes per layer, on the rotated chain. size_t last_extruder = size_t(-1); for (LayerTools < : m_layer_tools) { lt.wipe_tower_partitions = lt.extruders.size(); if (! lt.extruders.empty()) { - if (last_extruder == size_t(-1) || last_extruder == lt.extruders.front()) + const std::vector chain = rotate_extruders_to_start_with(lt.extruders, (unsigned int) last_extruder); + if (last_extruder == size_t(-1) || last_extruder == chain.front()) // The first extruder on this layer is equal to the current one, no need to do an initial tool change. -- lt.wipe_tower_partitions; - last_extruder = lt.extruders.back(); + last_extruder = chain.back(); } } @@ -1406,32 +1400,49 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ m_layer_tools[i].wipe_tower_partitions = std::max(m_layer_tools[i + 1].wipe_tower_partitions, m_layer_tools[i].wipe_tower_partitions); - int wrapping_layer_nums = config.wrapping_detection_layers; - for (size_t i = 0; i < wrapping_layer_nums; ++i) { - if (i >= m_layer_tools.size()) - break; - LayerTools < = m_layer_tools[i]; - lt.has_wipe_tower = config.enable_wrapping_detection; - } - // Fractional independent support heights place support-only layers between the object // grid Zs. Those layers get no tower layer of their own: the tower would have to print // a sub-minimum slab there, and the toolchange they carry switches TO the support // filament, which tolerates an unpurged nozzle (the residue lands in the support). // The switch back to an object filament happens on an object-grid layer with a full - // tower slab. Smooth timelapse needs a tower layer on every print layer, so it keeps - // the old behavior. - auto support_only_off_grid = [&config](const LayerTools <) { - return lt.has_support && !lt.has_object && + // tower slab; the tower re-syncs its loaded tool per layer. Smooth timelapse needs a + // tower layer on every print layer and single-extruder multi-material needs the + // tower's ramming for every change, so both keep the old behavior. + auto off_grid_gates = [&config]() { + return config.enable_prime_tower && config.independent_support_layer_height && config.support_layer_height_step != slhsWholeLayer && - config.timelapse_type != TimelapseType::tlSmooth; + config.timelapse_type != TimelapseType::tlSmooth && + !config.single_extruder_multi_material; + }; + auto support_only_off_grid = [&off_grid_gates](const LayerTools <) { + return lt.has_support && !lt.has_object && !lt.on_object_grid && off_grid_gates(); + }; + // Only fractional Zs are ever left without a tower slab: support-only layers there and + // support layers that ended up without extrusions (the ladder continues above the + // support tops). Object grid layers (even empty ones) keep theirs, so the slabs that + // remain are whole grid steps. + auto tower_skippable = [&off_grid_gates](const LayerTools <) { + return !lt.has_object && !lt.on_object_grid && (lt.has_support || lt.extruders.empty()) && off_grid_gates(); }; + // The first wrapping_detection_layers slabs; a fractional entry never carries a slab and + // does not consume one of them. + const int wrapping_layer_nums = config.wrapping_detection_layers; + for (size_t i = 0, marked = 0; i < m_layer_tools.size() && int(marked) < wrapping_layer_nums; ++i) { + LayerTools < = m_layer_tools[i]; + if (tower_skippable(lt)) + continue; + lt.has_wipe_tower = config.enable_wrapping_detection; + ++marked; + } + //FIXME this is a hack to get the ball rolling. for (LayerTools < : m_layer_tools) lt.has_wipe_tower |= ((lt.has_object || lt.has_support) && !support_only_off_grid(lt) && (config.timelapse_type == TimelapseType::tlSmooth || lt.wipe_tower_partitions > 0)) + // Below the object bottom (raft layers, support under a floating part) the tower + // always needs its base, fractional support Zs included. || lt.print_z < object_bottom_z + EPSILON; // Test for a raft, insert additional wipe tower layer to fill in the raft separation gap. @@ -1500,16 +1511,16 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ } for (int i = first_wt_idx + 1; i < last_wt_idx; ++i) { LayerTools < = m_layer_tools[i]; - if (support_only_off_grid(lt)) { - // Prefer no tower slab on a fractional support-only layer, but only when - // the slab bridging over it stays within the maximum layer height - - // otherwise this layer keeps its (thin) tower slab. + if (tower_skippable(lt) && !lt.has_wipe_tower) { + // Prefer no tower slab on a fractional support-only (or empty) layer, but + // only when the slab bridging over it stays within the maximum layer + // height - otherwise this layer keeps its (thin) tower slab. coordf_t prev_z = m_layer_tools[first_wt_idx].print_z; for (int j = i - 1; j >= first_wt_idx; --j) if (m_layer_tools[j].has_wipe_tower) { prev_z = m_layer_tools[j].print_z; break; } coordf_t next_z = m_layer_tools[last_wt_idx].print_z; for (int j = i + 1; j <= last_wt_idx; ++j) - if (m_layer_tools[j].has_wipe_tower || !support_only_off_grid(m_layer_tools[j])) { + if (m_layer_tools[j].has_wipe_tower || !tower_skippable(m_layer_tools[j])) { next_z = m_layer_tools[j].print_z; break; } @@ -1576,10 +1587,14 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_ // stopping at the first empty layer. const bool skip_empty_layer_tools = has_extruder_layer_heights(config); const LayerTools *lt_last_printing = nullptr; + // The tool loaded after the last printing layer, on the rotated chain. + unsigned int loaded_tool = (unsigned int) -1; for (unsigned int i=0; i+1extruders.back() || lt_next.extruders.size() > 1)) + if (!lt_next.has_wipe_tower && !tower_skippable(lt_next) && + (lt_next.extruders.size() > 1 || + std::find(lt_next.extruders.begin(), lt_next.extruders.end(), loaded_tool) == lt_next.extruders.end())) lt_next.has_wipe_tower = true; - // We should also check that the next wipe tower layer is no further than max_layer_height: + // We should also check that the next wipe tower layer is no further than max_layer_height. + // Fractional entries are transparent here: they never carry a slab, their neighbours + // bridge them. unsigned int j = i+1; double last_wipe_tower_print_z = lt_next.print_z; - while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) - if (m_layer_tools[j+1].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { + while (++j < m_layer_tools.size()-1 && !m_layer_tools[j].has_wipe_tower) { + if (tower_skippable(m_layer_tools[j])) + continue; + size_t jn = j + 1; + while (jn < m_layer_tools.size() && !m_layer_tools[jn].has_wipe_tower && tower_skippable(m_layer_tools[jn])) + ++jn; + if (jn < m_layer_tools.size() && m_layer_tools[jn].print_z - last_wipe_tower_print_z > max_layer_height + EPSILON) { if (!config.enable_wrapping_detection) m_layer_tools[j].has_wipe_tower = true; last_wipe_tower_print_z = m_layer_tools[j].print_z; } + } } @@ -3561,8 +3585,9 @@ void ToolOrdering::mark_skirt_layers(const PrintConfig &config, coordf_t max_lay static CustomGCode::Info custom_gcode_per_print_z; void ToolOrdering::assign_custom_gcodes(const Print &print) { - // Only valid for non-sequential print. - assert(print.config().print_sequence == PrintSequence::ByLayer); + // Only valid for non-sequential print; a sequential print with a wipe tower is emitted + // by layer from the print-wide ordering and takes the custom G-codes the same way. + assert(print.config().print_sequence == PrintSequence::ByLayer || print.has_wipe_tower()); custom_gcode_per_print_z = print.model().get_curr_plate_custom_gcodes(); if (custom_gcode_per_print_z.gcodes.empty()) diff --git a/src/libslic3r/GCode/ToolOrdering.hpp b/src/libslic3r/GCode/ToolOrdering.hpp index 975f1a1ab02..916f44ddb4d 100644 --- a/src/libslic3r/GCode/ToolOrdering.hpp +++ b/src/libslic3r/GCode/ToolOrdering.hpp @@ -170,6 +170,9 @@ class LayerTools coordf_t print_z = 0.; bool has_object = false; bool has_support = false; + // This print_z is an object layer boundary (of any object, extrusions or not); support-only + // layers between such Zs come from fractional independent support layer heights. + bool on_object_grid = false; // Zero based extruder IDs, ordered to minimize tool switches. std::vector extruders; bool preserve_extruder_order = false; @@ -392,7 +395,6 @@ class ToolOrdering void initialize_layers(std::vector &zs); void collect_extruders(const PrintObject &object, const std::vector> &per_layer_extruder_switches); void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height); - void bridge_fractional_support_layers(const PrintConfig &config); bool insert_wipe_tower_extruder(); void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height); void collect_extruder_statistics(bool prime_multi_material); diff --git a/src/libslic3r/GCode/WipeTower.cpp b/src/libslic3r/GCode/WipeTower.cpp index 5834937169f..27c289e98bd 100644 --- a/src/libslic3r/GCode/WipeTower.cpp +++ b/src/libslic3r/GCode/WipeTower.cpp @@ -2726,7 +2726,7 @@ void WipeTower::set_for_wipe_tower_writer(WipeTowerWriter &writer) writer.set_accel_to_decel_enable(m_accel_to_decel_enable); writer.set_accel_to_decel_factor(m_accel_to_decel_factor); writer.set_first_layer(m_cur_layer_id == 0); - writer.set_layer_id(m_cur_layer_id); + writer.set_layer_id(nozzle_layer_id(int(m_cur_layer_id))); // per-layer nozzle maps use the ToolOrdering index writer.set_physical_extruder_map(m_physical_extruder_map); } #if 0 @@ -2930,8 +2930,10 @@ void WipeTower::plan_toolchange(float z_par, float layer_height_par, unsigned in { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one - if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first + if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) { // if we moved to a new layer, we'll add it to m_plan first m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); + m_plan.back().start_tool = int(old_tool); + } if (m_first_layer_idx == size_t(-1) && (! m_no_sparse_layers || old_tool != new_tool)) m_first_layer_idx = m_plan.size() - 1; @@ -4429,6 +4431,10 @@ void WipeTower::generate_wipe_tower_blocks(bool add_solid_flag) } for (auto &info : m_plan) { std::unordered_set used_tools; + // The tool loaded at this layer may have changed off the tower since the last + // planned toolchange (fractional support layers): trust the recorded start tool. + if (info.start_tool >= 0) + first_tool = info.start_tool; if (info.tool_changes.empty()) { used_tools.insert(get_filament_category(first_tool)); } else { @@ -4653,6 +4659,9 @@ int WipeTower::get_wall_filament_for_all_layer() std::map filament_counts; int current_tool = m_current_tool; for (const auto &layer : m_plan) { + // The tool loaded at a layer may have changed off the tower since the last toolchange. + if (layer.start_tool >= 0) + current_tool = layer.start_tool; if (layer.tool_changes.empty()){ filament_counts[current_tool]++; category_counts[get_filament_category(current_tool)]++; @@ -4712,11 +4721,13 @@ void WipeTower::generate_new(std::vector= 0) { m_current_tool = size_t(layer.start_tool); start_tool_known = true; break; } + if (!start_tool_known) + for (const auto &layer : m_plan) + if (!layer.tool_changes.empty()) { m_current_tool = layer.tool_changes.front().old_tool; break; } } for (auto &used : m_used_filament_length) // reset used filament stats @@ -4730,6 +4741,9 @@ void WipeTower::generate_new(std::vector= 0 && size_t(layer.start_tool) != m_current_tool) + m_current_tool = size_t(layer.start_tool); if (m_layer_info->depth < m_perimeter_width) continue; if (m_wipe_tower_blocks.size() == 1) { if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) { @@ -4933,11 +4947,13 @@ void WipeTower::generate(std::vector> & m_layer_info = m_plan.begin(); // we don't know which extruder to start with - we'll set it according to the first toolchange - for (const auto& layer : m_plan) { - if (!layer.tool_changes.empty()) { - m_current_tool = layer.tool_changes.front().old_tool; - break; - } + { + bool start_tool_known = false; + for (const auto &layer : m_plan) + if (layer.start_tool >= 0) { m_current_tool = size_t(layer.start_tool); start_tool_known = true; break; } + if (!start_tool_known) + for (const auto &layer : m_plan) + if (!layer.tool_changes.empty()) { m_current_tool = layer.tool_changes.front().old_tool; break; } } for (auto& used : m_used_filament_length) // reset used filament stats @@ -5317,24 +5333,26 @@ float WipeTower::get_block_gap_width(int tool,bool is_nozzlechangle) } +// All callers pass a plan layer index; the nozzle group result is indexed by the ToolOrdering +// layer index, which differs once fractional layers carry no tower slab. bool WipeTower::is_need_ramming(int filament_id_1, int filament_id_2, int layer_id) const { - return !m_multi_nozzle_group_result->are_filaments_same_nozzle(filament_id_1, filament_id_2, layer_id); + return !m_multi_nozzle_group_result->are_filaments_same_nozzle(filament_id_1, filament_id_2, nozzle_layer_id(layer_id)); } bool WipeTower::is_same_extruder(int filament_id_1, int filament_id_2, int layer_id) const { - return m_multi_nozzle_group_result->are_filaments_same_extruder(filament_id_1, filament_id_2, layer_id); + return m_multi_nozzle_group_result->are_filaments_same_extruder(filament_id_1, filament_id_2, nozzle_layer_id(layer_id)); } bool WipeTower::is_same_nozzle(int filament_id_1, int filament_id_2, int layer_id) const { - return m_multi_nozzle_group_result->are_filaments_same_nozzle(filament_id_1, filament_id_2, layer_id); + return m_multi_nozzle_group_result->are_filaments_same_nozzle(filament_id_1, filament_id_2, nozzle_layer_id(layer_id)); } -int WipeTower::get_nozzle_id(int filament_id, int layer_id) const { return m_multi_nozzle_group_result->get_nozzle_id(filament_id, layer_id); } +int WipeTower::get_nozzle_id(int filament_id, int layer_id) const { return m_multi_nozzle_group_result->get_nozzle_id(filament_id, nozzle_layer_id(layer_id)); } int WipeTower::get_extruder_id(int filament_id, int layer_id) const { - return m_multi_nozzle_group_result->get_extruder_id(filament_id, layer_id); + return m_multi_nozzle_group_result->get_extruder_id(filament_id, nozzle_layer_id(layer_id)); } } // namespace Slic3r diff --git a/src/libslic3r/GCode/WipeTower.hpp b/src/libslic3r/GCode/WipeTower.hpp index 045c82cbf36..862823508cd 100644 --- a/src/libslic3r/GCode/WipeTower.hpp +++ b/src/libslic3r/GCode/WipeTower.hpp @@ -205,6 +205,13 @@ class WipeTower // Appends into internal structure m_plan containing info about the future wipe tower // to be used before building begins. The entries must be added ordered in z. void plan_toolchange(float z_par, float layer_height_par, unsigned int old_tool, unsigned int new_tool, float wipe_volume_ec = 0.f, float wipe_volume_nc = 0.f, float prime_volume = 0.f); + // Records the ToolOrdering layer index of the last planned layer (see WipeTowerInfo::ordering_layer_idx). + void set_plan_layer_ordering_index(int ordering_layer_idx) { if (!m_plan.empty()) m_plan.back().ordering_layer_idx = ordering_layer_idx; } + // Maps a plan layer index to the ToolOrdering layer index the per-layer nozzle maps use. + int nozzle_layer_id(int plan_layer_id) const { + return plan_layer_id >= 0 && size_t(plan_layer_id) < m_plan.size() && m_plan[plan_layer_id].ordering_layer_idx >= 0 ? + m_plan[plan_layer_id].ordering_layer_idx : plan_layer_id; + } // Iterates through prepared m_plan, generates ToolChangeResults and appends them to "result" @@ -610,6 +617,11 @@ class WipeTower float toolchanges_depth() const { float sum = 0.f; for (const auto &a : tool_changes) sum += a.required_depth; return sum; } std::vector tool_changes; + // Tool loaded when this layer starts (-1 = unknown); toolchanges may happen off the tower. + int start_tool = -1; + // Index of this layer in the ToolOrdering that planned it (-1 = same as the plan index). + // Per-layer nozzle maps are indexed by it; the plan skips layers without a slab. + int ordering_layer_idx = -1; WipeTowerInfo(float z_par, float layer_height_par) : z{z_par}, height{layer_height_par}, depth{0}, extra_spacing{1.f} {} diff --git a/src/libslic3r/GCode/WipeTower2.cpp b/src/libslic3r/GCode/WipeTower2.cpp index 13be232cd4f..f8ab6d6c7aa 100644 --- a/src/libslic3r/GCode/WipeTower2.cpp +++ b/src/libslic3r/GCode/WipeTower2.cpp @@ -2437,8 +2437,10 @@ void WipeTower2::plan_toolchange(float z_par, float layer_height_par, unsigned i { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); // refuses to add a layer below the last one - if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) // if we moved to a new layer, we'll add it to m_plan first + if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) { // if we moved to a new layer, we'll add it to m_plan first m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); + m_plan.back().start_tool = int(old_tool); + } if (m_first_layer_idx == size_t(-1) && (!m_no_sparse_layers || old_tool != new_tool || m_plan.size() == 1)) m_first_layer_idx = m_plan.size() - 1; @@ -2501,8 +2503,10 @@ void WipeTower2::plan_local_z_toolchange(float z_par, float layer_height_par, un { assert(m_plan.empty() || m_plan.back().z <= z_par + WT_EPSILON); - if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) + if (m_plan.empty() || m_plan.back().z + WT_EPSILON < z_par) { m_plan.push_back(WipeTowerInfo(z_par, layer_height_par)); + m_plan.back().start_tool = int(old_tool); + } if (m_first_layer_idx == size_t(-1) && (!m_no_sparse_layers || old_tool != new_tool || m_plan.size() == 1)) m_first_layer_idx = m_plan.size() - 1; @@ -2838,17 +2842,26 @@ void WipeTower2::generate(std::vector>& m_layer_info = m_plan.begin(); m_current_height = 0.f; - // We don't know which extruder to start with, so take the first actual toolchange on the tower. - for (const auto& layer : m_plan) { - if (!layer.local_z_tool_changes.empty()) { - m_current_tool = layer.local_z_tool_changes.front().old_tool; + // Start with the tool loaded at the first planned layer; fall back to the first + // actual toolchange on the tower for plans that did not record it. + bool start_tool_known = false; + for (const auto& layer : m_plan) + if (layer.start_tool >= 0) { + m_current_tool = size_t(layer.start_tool); + start_tool_known = true; break; } - if (!layer.tool_changes.empty()) { - m_current_tool = layer.tool_changes.front().old_tool; - break; + if (!start_tool_known) + for (const auto& layer : m_plan) { + if (!layer.local_z_tool_changes.empty()) { + m_current_tool = layer.local_z_tool_changes.front().old_tool; + break; + } + if (!layer.tool_changes.empty()) { + m_current_tool = layer.tool_changes.front().old_tool; + break; + } } - } m_used_filament_length.assign(m_used_filament_length.size(), 0.f); // reset used filament stats assert(m_used_filament_length_until_layer.empty()); @@ -2868,6 +2881,10 @@ void WipeTower2::generate(std::vector>& << " planned_depth=" << layer.planned_depth(); set_layer(layer.z, layer.height, 0, false /*layer.z == m_plan.front().z*/, layer.z == m_plan.back().z); m_internal_rotation += 180.f; + // Re-sync to the tool actually loaded at this layer: a filament change may have + // happened off the tower since the previous layer. + if (layer.start_tool >= 0 && size_t(layer.start_tool) != m_current_tool) + m_current_tool = size_t(layer.start_tool); if (m_layer_info->depth < m_wipe_tower_depth - m_perimeter_width) m_y_shift = (m_wipe_tower_depth - m_layer_info->depth - m_perimeter_width) / 2.f; diff --git a/src/libslic3r/GCode/WipeTower2.hpp b/src/libslic3r/GCode/WipeTower2.hpp index 83a91b4cd07..c099e751730 100644 --- a/src/libslic3r/GCode/WipeTower2.hpp +++ b/src/libslic3r/GCode/WipeTower2.hpp @@ -398,6 +398,10 @@ class WipeTower2 std::vector tool_changes; std::vector local_z_tool_changes; + // Tool loaded when this layer starts (-1 = unknown). Toolchanges may happen off + // the tower between layers (fractional support layers), so the tower cannot infer + // it from its own toolchange chain. + int start_tool = -1; WipeTowerInfo(float z_par, float layer_height_par) : z{z_par}, height{layer_height_par}, depth{0} {} diff --git a/src/libslic3r/Print.cpp b/src/libslic3r/Print.cpp index 39c0dd436e8..df8dfa76ff8 100644 --- a/src/libslic3r/Print.cpp +++ b/src/libslic3r/Print.cpp @@ -871,6 +871,18 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n ) { steps.emplace_back(psWipeTower); steps.emplace_back(psSkirtBrim); + if ((opt_key == "enable_prime_tower" || opt_key == "single_extruder_multi_material") && m_config.independent_support_layer_height.value) { + // The support layer height plan depends on both: supports align to the object + // grid under the tower and keep whole steps under single-extruder multi-material. + osteps.emplace_back(posSupportMaterial); + osteps.emplace_back(posSimplifySupportPath); + } + } else if (opt_key == "timelapse_type") { + // Print-level: everything (the key used to reach the catch-all below); object-level: + // the support layer plan gates fractional boundaries on smooth timelapse. + invalidated |= this->invalidate_all_steps(); + osteps.emplace_back(posSupportMaterial); + osteps.emplace_back(posSimplifySupportPath); } else if (opt_key == "filament_soluble" || opt_key == "filament_is_support" || opt_key == "filament_printable" @@ -5230,16 +5242,25 @@ void Print::_make_wipe_tower() // filament bookkeeping in sync, so the next tower layer plans the switch back. if (!layer_tools.extruders.empty()) { used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); - current_filament_id = layer_tools.extruders.back(); + for (unsigned int filament_id : rotate_extruders_to_start_with(layer_tools.extruders, current_filament_id)) + if (auto nozzle = nozzle_group_result.get_nozzle_for_filament(filament_id, layer_idx)) + nozzle_recorder.set_nozzle_status(nozzle->group_id, filament_id, nozzle->extruder_id); + current_filament_id = rotate_extruders_to_start_with(layer_tools.extruders, current_filament_id).back(); } continue; } bool first_layer = &layer_tools == &m_wipe_tower_data.tool_ordering.front(); wipe_tower.plan_toolchange((float)layer_tools.print_z, (float)layer_tools.wipe_tower_layer_height, current_filament_id, current_filament_id); + // Per-layer nozzle maps are indexed by this ordering's layer index, which no longer + // equals the tower's own layer index once fractional layers carry no slab. + wipe_tower.set_plan_layer_ordering_index(layer_idx); used_filament_ids.insert(layer_tools.extruders.begin(), layer_tools.extruders.end()); - for (const auto filament_id : layer_tools.extruders) { + // Mirrors GCode::process_layer, which rotates the layer's filaments to start with + // the one currently loaded (same as the Type2 branch below). + const std::vector layer_filaments = rotate_extruders_to_start_with(layer_tools.extruders, current_filament_id); + for (const auto filament_id : layer_filaments) { if (filament_id == current_filament_id) continue; @@ -5393,9 +5414,10 @@ void Print::_make_wipe_tower() for (auto &layer_tools : m_wipe_tower_data.tool_ordering.layer_tools()) { // for all layers if (!layer_tools.has_wipe_tower) { // Keep the tool chain in sync across layers that switch filament without - // a tower slab (fractional support-only layers). + // a tower slab (fractional support-only layers): emission rotates the + // layer to start with the loaded tool, so the last tool is the rotated back. if (!layer_tools.extruders.empty()) - current_extruder_id = layer_tools.extruders.back(); + current_extruder_id = rotate_extruders_to_start_with(layer_tools.extruders, current_extruder_id).back(); continue; } while (layers_to_print_idx + 1 < layers_to_print.size() && @@ -5438,12 +5460,10 @@ void Print::_make_wipe_tower() current_extruder_id = local_z_toolchanges.back().new_tool; } - // Plan in the same order G-code emission walks the layer's extruders. The - // flush-volume reorder starts each layer with the incumbent, so this used to - // equal the rotated order - but a layer that deliberately ENDS with a tool - // (bridging for fractional support layers) needs the raw order or the plan - // misses the trailing toolchange. - const std::vector &nominal_layer_extruders = layer_tools.extruders; + // Mirrors GCode::process_layer, which rotates the layer's extruders to start + // with the tool currently loaded. + const std::vector nominal_layer_extruders = + rotate_extruders_to_start_with(layer_tools.extruders, current_extruder_id); wipe_tower.plan_toolchange((float) layer_tools.print_z, (float) layer_tools.wipe_tower_layer_height, current_extruder_id, current_extruder_id, false); diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 152926a26db..1525e2d86b4 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -7531,9 +7531,13 @@ void PrintConfigDef::init_fff_params() def->tooltip = L("Step granularity for independent support layer heights while the prime tower is enabled. " "With whole layers, support layer heights are multiples of the object layer height. " "Half or quarter steps also allow multiples like 1.5x or 1.25x, which helps when the support " - "nozzle's maximum layer height sits between two whole multiples. The finer steps place " - "support boundaries between object layers, which adds thin prime tower layers there and " - "increases the purged volume on those toolchanges."); + "nozzle's maximum layer height sits between two whole multiples. Support boundaries may then " + "fall between object layers; such support-only layers print without a prime tower layer: the " + "switch to the support filament happens directly (any residue ends up in the support) and the " + "switch back purges on the next full prime tower layer. With smooth timelapse the sub-layer " + "boundaries get their own prime tower layers instead and are only used where those stay at or " + "above the nozzles' minimum layer height. Not used with single-extruder multi-material, which " + "keeps whole layers."); def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); def->enum_values.emplace_back("whole"); def->enum_values.emplace_back("half"); @@ -7548,7 +7552,8 @@ void PrintConfigDef::init_fff_params() def->label = L("Independent support layer height"); def->category = L("Support"); def->tooltip = L("Support layer uses layer height independent with object layer. This is to support customizing Z-gap and save print time. " - "This option will be invalid when the prime tower is enabled."); + "With the prime tower enabled, support layer heights stay aligned to the object layer grid " + "(see Support layer height step)."); def->mode = comAdvanced; // Mainline default. The Snapmaker process profiles either set this to 1 // explicitly or (U1) leave it unset; with the old false default, loading a diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 913d12f689f..38193867131 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -169,6 +169,11 @@ SlicingParameters SlicingParameters::create_from_config( support_layer_height_limit(object_config.support_filament.value, false), support_layer_height_limit(object_config.support_interface_filament.value, false)); params.max_suport_layer_height = params.max_layer_height; + // The support extruders' own (unclamped) minimum matters only when supports are pinned + // to their own filaments or nozzle; with the object's filaments the object's clamped + // minimum applies as before. + if (object_config.support_filament.value != 0 || object_config.support_interface_filament.value != 0 || restricted_default(0)) + params.min_suport_layer_height = params.min_layer_height; } if (object_extruders.empty()) { diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index f735ec55186..3557aeb2c97 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -71,6 +71,8 @@ struct SlicingParameters coordf_t min_layer_height { 0 }; coordf_t max_layer_height { 0 }; coordf_t max_suport_layer_height { 0 }; + // The support extruders' own minimum, not clamped to the object layer height. + coordf_t min_suport_layer_height { 0 }; // First layer height of the print, this may be used for the first layer of the raft // or for the first layer of the print. diff --git a/src/libslic3r/Support/SupportMaterial.hpp b/src/libslic3r/Support/SupportMaterial.hpp index 50b8256c4a3..024f7e0dceb 100644 --- a/src/libslic3r/Support/SupportMaterial.hpp +++ b/src/libslic3r/Support/SupportMaterial.hpp @@ -28,7 +28,9 @@ class PrintObjectSupportMaterial bool has_support() const { return m_object_config->enable_support.value || m_object_config->enforce_support_layers; } bool build_plate_only() const { return this->has_support() && m_object_config->support_on_build_plate_only.value; } // BBS - bool synchronize_layers() const { return /*m_slicing_params.zero_gap_interface_top && */!m_print_config->independent_support_layer_height.value; } + // Free-form support heights only without the prime tower: with the tower on, the classic + // generator keeps every support layer on the object grid (see support_layer_heights_free()). + bool synchronize_layers() const { return !m_print_config->independent_support_layer_height.value || m_print_config->enable_prime_tower.value; } bool has_contact_loops() const { return m_object_config->support_interface_loop_pattern.value; } // Generate support material for the object. diff --git a/src/libslic3r/Support/SupportParameters.hpp b/src/libslic3r/Support/SupportParameters.hpp index 2982551910b..68d7d430f54 100644 --- a/src/libslic3r/Support/SupportParameters.hpp +++ b/src/libslic3r/Support/SupportParameters.hpp @@ -200,7 +200,7 @@ struct SupportParameters { // Sub-layer step for grid-aligned heights: 1 = whole object layers, 2/4 allow // boundaries on half/quarter subdivisions (thin tower layers appear there). grid_height_step = 1; - if (grid_aligned_layer_height) { + if (grid_aligned_layer_height && !print_config.single_extruder_multi_material) { if (print_config.support_layer_height_step.value == slhsHalfLayer) grid_height_step = 2; else if (print_config.support_layer_height_step.value == slhsQuarterLayer) diff --git a/src/libslic3r/Support/TreeSupport.cpp b/src/libslic3r/Support/TreeSupport.cpp index d9c3ed65cf7..82509540276 100644 --- a/src/libslic3r/Support/TreeSupport.cpp +++ b/src/libslic3r/Support/TreeSupport.cpp @@ -3266,21 +3266,14 @@ std::vector TreeSupport::plan_layer_heights() // sub-layer boundaries get their own (thinner) tower layers, which costs extra // purge there but lets support heights exceed whole multiples (e.g. 1.5x) when // the support nozzle's maximum lies between two whole multiples. - const coordf_t max_layer_height = std::max(m_slicing_params.max_suport_layer_height, m_object->config().layer_height.value); - const coordf_t min_layer_height = m_slicing_params.min_layer_height; const int height_step = std::max(1, m_support_params.grid_height_step); + // The support extruder's own minimum is not clamped to the object layer height: a + // coarse support nozzle under a fine object grid must not get sub-minimum tails. + const coordf_t min_layer_height = std::max(m_slicing_params.min_layer_height, m_slicing_params.min_suport_layer_height); + // Floor the maximum at one sub-step: a support nozzle with a maximum below the object + // pitch then closes its pieces on sub-grid positions instead of whole object layers. + const coordf_t max_layer_height = std::max({m_slicing_params.max_suport_layer_height, m_object->config().layer_height.value / height_step, min_layer_height}); const size_t n = m_object->layer_count(); - // A run must end exactly at a top-contact layer so the support tops keep their - // contact Z (mirrors the boundaries the free-form planner inserts), and the contact - // layer itself prints at the object layer height for interface quality. - std::vector boundary(n + 1, 0); - for (size_t layer_nr = 1; layer_nr < contact_nodes.size() && layer_nr < n; ++layer_nr) - if (!contact_nodes[layer_nr].empty()) { - boundary[layer_nr] = 1; - boundary[layer_nr + 1] = 1; - } - layer_heights.reserve(n); - layer_heights.push_back({m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}); // Fractional support-only layers normally get no prime tower layer (their // toolchange goes straight to the support filament), so they impose nothing on // the tower. Smooth timelapse is the exception: it needs a tower layer on every @@ -3293,6 +3286,62 @@ std::vector TreeSupport::plan_layer_heights() coordf_t min_h = m_object->print()->config().min_layer_height.get_at(extruder_id); tower_min_slab = std::max(tower_min_slab, min_h == 0. ? 0.07 : min_h); } + // Sub-positions of an object layer usable as piece boundaries. Fractional positions + // keep a quantum comfortably above the tower plan's 1e-3 mm Z-merge epsilon, or the + // layer is left unsplittable. + auto sub_positions = [&](const Layer *layer, std::vector &out) { + out.clear(); + const coordf_t h = layer->height; + const int steps = h / height_step > 0.002 ? height_step : 1; + for (int k = 1; k < steps; ++k) { + const coordf_t below = h * k / steps; + if (below < tower_min_slab - EPSILON || h - below < tower_min_slab - EPSILON) + continue; // a tower slab on either side of this boundary would be too thin + out.push_back(layer->print_z - h + below); + } + }; + // A run must end exactly at a top-contact layer so the support tops keep their + // contact Z (mirrors the boundaries the free-form planner inserts), and the contact + // layer itself prints at the object layer height for interface quality. + std::vector boundary(n + 1, 0); + std::vector forced_close_zs; // exact support tops between object layers + std::vector layer_subs; + for (size_t layer_nr = 1; layer_nr < contact_nodes.size() && layer_nr < n; ++layer_nr) + if (!contact_nodes[layer_nr].empty()) { + boundary[layer_nr] = 1; + boundary[layer_nr + 1] = 1; + // The support top sits support_top_z_distance below the contact (the contact + // node's height is that gap, as the free-form planner uses it). Close a piece + // exactly there: on a sub-position of the object layer containing it when the + // step allows, otherwise at the nearest object layer below - or the gap rounds + // up to a whole support piece. + const SupportNode *node = contact_nodes[layer_nr].front(); + if (node->height > EPSILON) { + const coordf_t gap_bottom = node->print_z - node->height; + for (size_t j = layer_nr; j-- > 0;) + if (m_object->get_layer(j)->print_z <= gap_bottom + EPSILON) { + bool on_sub_position = false; + if (j + 1 < n) { + sub_positions(m_object->get_layer(j + 1), layer_subs); + for (coordf_t z : layer_subs) + if (std::abs(z - gap_bottom) < EPSILON) { on_sub_position = true; break; } + } + if (on_sub_position) + forced_close_zs.push_back(gap_bottom); + else + boundary[j + 1] = 1; + break; + } + } + } + auto forced_close = [&forced_close_zs](coordf_t z) { + for (coordf_t f : forced_close_zs) + if (std::abs(f - z) < EPSILON) + return true; + return false; + }; + layer_heights.reserve(n); + layer_heights.push_back({m_object->get_layer(0)->print_z, m_object->get_layer(0)->height, 0}); // A candidate boundary: a sub-position of an object layer. struct SubPos { coordf_t z; size_t obj_layer_nr; }; std::vector subs; @@ -3301,58 +3350,60 @@ std::vector TreeSupport::plan_layer_heights() size_t span_end = i + 1; while (span_end < n && !boundary[span_end]) ++span_end; // Sub-position ladder over object layers i..span_end-1. Grid positions use the - // layer's print_z verbatim so they stay bit-exact; fractional positions keep a - // quantum comfortably above the tower plan's 1e-3 mm Z-merge epsilon, or the - // layer is left unsplittable. + // layer's print_z verbatim so they stay bit-exact. subs.clear(); for (size_t l = i; l < span_end; ++l) { - const Layer *layer = m_object->get_layer(l); - const coordf_t h = layer->height; - const int steps = h / height_step > 0.002 ? height_step : 1; - for (int k = 1; k < steps; ++k) { - const coordf_t below = h * k / steps; - if (below < tower_min_slab - EPSILON || h - below < tower_min_slab - EPSILON) - continue; // a tower slab on either side of this boundary would be too thin - subs.push_back({layer->print_z - h + below, l}); - } + const Layer *layer = m_object->get_layer(l); + sub_positions(layer, layer_subs); + for (coordf_t z : layer_subs) + subs.push_back({z, l}); subs.push_back({layer->print_z, l}); } - const coordf_t span_start_z = m_object->get_layer(i)->print_z - m_object->get_layer(i)->height; - const size_t pieces_begin = layer_heights.size(); - coordf_t piece_start = span_start_z; - for (size_t s = 0; s < subs.size(); ++s) { - // Close the piece at the span end, or right before the sub-position that - // would push it past the maximum support layer height. - if (s + 1 == subs.size() || subs[s + 1].z - piece_start > max_layer_height + EPSILON) { - layer_heights.push_back({subs[s].z, subs[s].z - piece_start, subs[s].obj_layer_nr}); - piece_start = subs[s].z; + // Pieces are laid out per segment: the ladder up to each forced close (an exact + // support top) and the remainder get the same closing and rebalancing. + coordf_t piece_start = m_object->get_layer(i)->print_z - m_object->get_layer(i)->height; + size_t seg_begin = 0; + while (seg_begin < subs.size()) { + size_t seg_end = seg_begin; // index of the last ladder position of this segment + while (seg_end + 1 < subs.size() && !forced_close(subs[seg_end].z)) ++seg_end; + const size_t pieces_begin = layer_heights.size(); + for (size_t s = seg_begin; s <= seg_end; ++s) { + // Close the piece at the segment end, or right before the sub-position that + // would push it past the maximum support layer height. + if (s == seg_end || subs[s + 1].z - piece_start > max_layer_height + EPSILON) { + layer_heights.push_back({subs[s].z, subs[s].z - piece_start, subs[s].obj_layer_nr}); + piece_start = subs[s].z; + } } - } - // Rebalance a trailing piece thinner than the support minimum. - if (layer_heights.size() - pieces_begin >= 2 && layer_heights.back().height < min_layer_height - EPSILON) { - LayerHeightData tail = layer_heights.back(); layer_heights.pop_back(); - LayerHeightData prev = layer_heights.back(); layer_heights.pop_back(); - const coordf_t combined_start = prev.print_z - prev.height; - const coordf_t combined = tail.print_z - combined_start; - if (combined <= max_layer_height + EPSILON) { - layer_heights.push_back({tail.print_z, combined, tail.obj_layer_nr}); - } else { - // Cannot merge: split the two pieces as evenly as the sub-grid allows. - const coordf_t ideal = combined_start + combined / 2.; - size_t best = subs.size(); - for (size_t s = 0; s < subs.size(); ++s) - if (subs[s].z > combined_start + EPSILON && subs[s].z < tail.print_z - EPSILON && - (best == subs.size() || std::abs(subs[s].z - ideal) < std::abs(subs[best].z - ideal))) - best = s; - if (best < subs.size()) { - layer_heights.push_back({subs[best].z, subs[best].z - combined_start, subs[best].obj_layer_nr}); - layer_heights.push_back({tail.print_z, tail.print_z - subs[best].z, tail.obj_layer_nr}); + // Rebalance a trailing piece thinner than the support minimum. + if (layer_heights.size() - pieces_begin >= 2 && layer_heights.back().height < min_layer_height - EPSILON) { + LayerHeightData tail = layer_heights.back(); layer_heights.pop_back(); + LayerHeightData prev = layer_heights.back(); layer_heights.pop_back(); + const coordf_t combined_start = prev.print_z - prev.height; + const coordf_t combined = tail.print_z - combined_start; + if (combined <= max_layer_height + EPSILON) { + layer_heights.push_back({tail.print_z, combined, tail.obj_layer_nr}); } else { - // No usable split point: keep the original pieces, thin tail and all. - layer_heights.push_back(prev); - layer_heights.push_back(tail); + // Cannot merge: split the two pieces as evenly as the sub-grid allows. + const coordf_t ideal = combined_start + combined / 2.; + size_t best = subs.size(); + // Both halves must respect the support minimum, or the split just moves + // the thin piece. + for (size_t s = seg_begin; s <= seg_end; ++s) + if (subs[s].z - combined_start >= min_layer_height - EPSILON && tail.print_z - subs[s].z >= min_layer_height - EPSILON && + (best == subs.size() || std::abs(subs[s].z - ideal) < std::abs(subs[best].z - ideal))) + best = s; + if (best < subs.size()) { + layer_heights.push_back({subs[best].z, subs[best].z - combined_start, subs[best].obj_layer_nr}); + layer_heights.push_back({tail.print_z, tail.print_z - subs[best].z, tail.obj_layer_nr}); + } else { + // No usable split point: keep the original pieces, thin tail and all. + layer_heights.push_back(prev); + layer_heights.push_back(tail); + } } } + seg_begin = seg_end + 1; } i = span_end; } diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 5766667688b..6f829fb6ee0 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -977,6 +977,10 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in // ORCA: Independent support layer height is not compatible with organic tree supports, // as they rely on the support layers being the same as the object layers to determine where to place branches. toggle_line("independent_support_layer_height", have_support_material && !support_is_organic); + // The step only has an effect for non-organic tree supports with independent layer heights + // under the prime tower; single-extruder multi-material keeps whole steps. + toggle_line("support_layer_height_step", support_is_normal_tree && config->opt_bool("independent_support_layer_height") && + config->opt_bool("enable_prime_tower") && !bSEMM); toggle_field("tree_support_brim_width", support_is_tree && !config->opt_bool("tree_support_auto_brim")); // tree support use max_bridge_length instead of bridge_no_support