From b3c7abdec63153718e1822d14f2d4179ce6be102 Mon Sep 17 00:00:00 2001 From: Ian Bassi Date: Wed, 3 Jun 2026 10:12:26 -0300 Subject: [PATCH] ENH: Relative bridge direction + Align bridge/Ironing angles with model (#12055) Co-authored-by: Rodrigo Faselli <162915171+RF47@users.noreply.github.com> --- src/libslic3r/Fill/Fill.cpp | 17 +++++++--- src/libslic3r/LayerRegion.cpp | 49 +++++++++++++++++++++++---- src/libslic3r/PerimeterGenerator.cpp | 10 ++++-- src/libslic3r/PerimeterGenerator.hpp | 3 ++ src/libslic3r/Preset.cpp | 1 + src/libslic3r/PrintConfig.cpp | 34 +++++++++++++++---- src/libslic3r/PrintConfig.hpp | 1 + src/libslic3r/PrintObject.cpp | 16 +++++++-- src/slic3r/GUI/ConfigManipulation.cpp | 2 +- src/slic3r/GUI/Tab.cpp | 1 + 10 files changed, 112 insertions(+), 22 deletions(-) diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp index e30c7ee9ec..fc0e447382 100644 --- a/src/libslic3r/Fill/Fill.cpp +++ b/src/libslic3r/Fill/Fill.cpp @@ -935,10 +935,13 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p params.fixed_angle = !region_config.solid_infill_rotate_template.value.empty(); } params.bridge_angle = float(surface.bridge_angle); - + + // ORCA: Align infill angle to model + float align_offset = 0.f; if (region_config.align_infill_direction_to_model) { auto m = layer.object()->trafo().matrix(); - params.angle += atan2((float) m(1, 0), (float) m(0, 0)); + align_offset = atan2((float)m(1, 0), (float)m(0, 0)); + params.angle += align_offset; } // Calculate the actual flow we'll be using for this infill. @@ -1024,6 +1027,7 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p if (fill.region_id == size_t(-1)) { fill.region_id = region_id; fill.surface = surface; + fill.surface.bridge_angle = params->bridge_angle; fill.expolygons.emplace_back(std::move(fill.surface.expolygon)); //BBS fill.region_id_group.push_back(region_id); @@ -1578,7 +1582,7 @@ void Layer::make_ironing() if (ironing_params.extruder != -1) { //TODO just_infill is currently not used. ironing_params.just_infill = false; - // Get filament-specific overrides if configured, otherwise use default values + // ORCA: Get filament-specific overrides if configured, otherwise use process values size_t extruder_idx = ironing_params.extruder - 1; ironing_params.line_spacing = (!config.filament_ironing_spacing.is_nil(extruder_idx) ? config.filament_ironing_spacing.get_at(extruder_idx) @@ -1592,7 +1596,12 @@ void Layer::make_ironing() ironing_params.speed = (!config.filament_ironing_speed.is_nil(extruder_idx) ? config.filament_ironing_speed.get_at(extruder_idx) : config.ironing_speed); - ironing_params.angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.; + double ironing_angle = (config.ironing_angle_fixed ? 0 : calculate_infill_rotation_angle(this->object(), this->id(), config.solid_infill_direction.value, config.solid_infill_rotate_template.value)) + config.ironing_angle * M_PI / 180.; + if (config.align_infill_direction_to_model) { + auto m = this->object()->trafo().matrix(); + ironing_angle += atan2((double)m(1, 0), (double)m(0, 0)); + } + ironing_params.angle = ironing_angle; ironing_params.fixed_angle = config.ironing_angle_fixed || !config.solid_infill_rotate_template.value.empty(); ironing_params.pattern = config.ironing_pattern; ironing_params.layerm = layerm; diff --git a/src/libslic3r/LayerRegion.cpp b/src/libslic3r/LayerRegion.cpp index fdf03570a8..20ded6e72c 100644 --- a/src/libslic3r/LayerRegion.cpp +++ b/src/libslic3r/LayerRegion.cpp @@ -83,6 +83,12 @@ 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); + double model_rotation_rad = 0.0; + if (region_config.align_infill_direction_to_model) { + auto m = this->layer()->object()->trafo().matrix(); + model_rotation_rad = std::atan2((double)m(1, 0), (double)m(0, 0)); + } + PerimeterGenerator g( // input: &slices, @@ -94,6 +100,7 @@ void LayerRegion::make_perimeters(const SurfaceCollection &slices, const LayerRe &this->layer()->object()->config(), &print_config, spiral_mode, + model_rotation_rad, // output: &this->perimeters, @@ -517,10 +524,27 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly SurfaceCollection bridges; { BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges. layer" << this->layer()->print_z; - const double custom_angle = this->region().config().bridge_angle.value; - bridges.surfaces = custom_angle > 0 ? - expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, Geometry::deg2rad(custom_angle)) : + // ORCA: Relative/Align Bridge Angle + const auto ®ion_config = this->region().config(); + const double custom_angle_deg = region_config.bridge_angle.value; + const bool relative_angle = region_config.relative_bridge_angle.value; + const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg); + + double align_offset_rad = 0.0; + if (region_config.align_infill_direction_to_model) { + auto m = this->layer()->object()->trafo().matrix(); + align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0)); + } + + bridges.surfaces = (custom_angle_deg > 0.0 && !relative_angle) ? + expand_merge_surfaces(this->fill_surfaces.surfaces, stBottomBridge, expansion_zones, closing_radius, custom_angle_rad + align_offset_rad) : expand_bridges_detect_orientations(this->fill_surfaces.surfaces, expansion_zones, closing_radius); + if (custom_angle_deg > 0.0 && relative_angle) { + for (Surface &bridge_surface : bridges.surfaces) { + if (bridge_surface.bridge_angle >= 0) + bridge_surface.bridge_angle += custom_angle_rad; + } + } BOOST_LOG_TRIVIAL(trace) << "Processing external surface, detecting bridges - done"; #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { @@ -782,12 +806,25 @@ void LayerRegion::process_external_surfaces(const Layer *lower_layer, const Poly // would get merged into a single one while they need different directions // also, supply the original expolygon instead of the grown one, because in case // of very thin (but still working) anchors, the grown expolygon would go beyond them - double custom_angle = Geometry::deg2rad(this->region().config().bridge_angle.value); - if (custom_angle > 0.0) { - bridges[idx_last].bridge_angle = custom_angle; + // ORCA: Relative/Align Bridge Angle + const auto ®ion_config = this->region().config(); + const double custom_angle_deg = region_config.bridge_angle.value; + const bool relative_angle = region_config.relative_bridge_angle.value; + const double custom_angle_rad = Geometry::deg2rad(custom_angle_deg); + + double align_offset_rad = 0.0; + if (region_config.align_infill_direction_to_model) { + auto m = this->layer()->object()->trafo().matrix(); + align_offset_rad = std::atan2((double)m(1, 0), (double)m(0, 0)); + } + + if (custom_angle_deg > 0.0 && !relative_angle) { + bridges[idx_last].bridge_angle = custom_angle_rad + align_offset_rad; } else { auto [bridging_dir, unsupported_dist] = detect_bridging_direction(to_polygons(initial), to_polygons(lower_layer->lslices)); bridges[idx_last].bridge_angle = PI + std::atan2(bridging_dir.y(), bridging_dir.x()); + if (custom_angle_deg > 0.0 && relative_angle) + bridges[idx_last].bridge_angle += custom_angle_rad; } /* diff --git a/src/libslic3r/PerimeterGenerator.cpp b/src/libslic3r/PerimeterGenerator.cpp index a5cdfe5bc4..58d2e038df 100644 --- a/src/libslic3r/PerimeterGenerator.cpp +++ b/src/libslic3r/PerimeterGenerator.cpp @@ -1760,8 +1760,14 @@ void PerimeterGenerator::process_no_bridge(Surfaces& all_surfaces, coord_t perim BridgeDetector detector{ unsupported, lower_island.expolygons, perimeter_spacing / 4}; // Use a finer BridgeDetector. This affects coverage resolution, not extrusion spacing. - - if (detector.detect_angle(Geometry::deg2rad(this->config->bridge_angle.value))) + // ORCA: Relative/Align Bridge Angle + const double custom_angle_deg = this->config->bridge_angle.value; + const bool relative_angle = this->config->relative_bridge_angle.value; + const double detect_angle_rad = (custom_angle_deg > 0.0 && !relative_angle) + ? Geometry::deg2rad(custom_angle_deg) + + (this->config->align_infill_direction_to_model ? this->m_model_rotation_rad : 0.0) + : 0.0; + if (detector.detect_angle(detect_angle_rad)) expolygons_append(bridgeable, union_ex(detector.coverage(-1, true))); } if (!bridgeable.empty() && !surface->expolygon.holes.empty()) { // keep out if cannot be bridged or no holes to bridge diff --git a/src/libslic3r/PerimeterGenerator.hpp b/src/libslic3r/PerimeterGenerator.hpp index 9ccf8c4d8c..e4f918d8bd 100644 --- a/src/libslic3r/PerimeterGenerator.hpp +++ b/src/libslic3r/PerimeterGenerator.hpp @@ -117,6 +117,7 @@ public: const PrintObjectConfig* object_config, const PrintConfig* print_config, const bool spiral_mode, + const double model_rotation_rad, // Output: // Loops with the external thin walls ExtrusionEntityCollection* loops, @@ -132,6 +133,7 @@ public: 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)), + m_model_rotation_rad(model_rotation_rad), 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) {} @@ -157,6 +159,7 @@ private: private: bool m_spiral_vase; double m_scaled_resolution; + double m_model_rotation_rad; double m_ext_mm3_per_mm; double m_mm3_per_mm; double m_mm3_per_mm_overhang; diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 0070bdcbd7..21050ee09f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1161,6 +1161,7 @@ static std::vector s_Preset_print_options{ "small_perimeter_threshold", "bridge_angle", "internal_bridge_angle", + "relative_bridge_angle", "filter_out_gap_fill", "travel_acceleration", "inner_wall_acceleration", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 8fdcede569..a113c3057a 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -1241,11 +1241,16 @@ void PrintConfigDef::init_fff_params() def->label = L("External bridge infill direction"); def->category = L("Strength"); // xgettext:no-c-format, no-boost-format - def->tooltip = L("Bridging angle override. If left to zero, the bridging angle will be calculated " - "automatically. Otherwise the provided angle will be used for external bridges. " - "Use 180° for zero angle."); + def->tooltip = L("External Bridging angle override.\n" + "If left to zero, the bridging angle will be calculated automatically for each specific bridge.\n" + "Otherwise the provided angle will be used according to:\n" + " - The absolute coordinates\n" + " - The absolute coordinates + Model rotation: If Align infill direction to model is enabled\n" + " - The optimal automatic angle + this value: If 'Relative Bridge Angle' is enabled\n\n" + "Use 180° for zero absolute angle."); def->sidetext = u8"°"; // degrees, don't need translation def->min = 0; + def->max = 180; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); @@ -1253,14 +1258,28 @@ void PrintConfigDef::init_fff_params() def = this->add("internal_bridge_angle", coFloat); def->label = L("Internal bridge infill direction"); def->category = L("Strength"); - def->tooltip = L("Internal bridging angle override. If left to zero, the bridging angle will be calculated " - "automatically. Otherwise the provided angle will be used for internal bridges. " - "Use 180° for zero angle.\n\nIt is recommended to leave it at 0 unless there is a specific model need not to."); + def->tooltip = L("Internal Bridging angle override.\n" + "If left to zero, the bridging angle will be calculated automatically for each specific bridge.\n" + "Otherwise the provided angle will be used according to:\n" + " - The absolute coordinates\n" + " - The absolute coordinates + Model rotation: If Align infill direction to model is enabled\n" + " - The optimal automatic angle + this value: If 'Relative Bridge Angle' is enabled\n\n" + "Use 180° for zero absolute angle."); def->sidetext = u8"°"; // degrees, don't need translation def->min = 0; + def->max = 180; def->mode = comAdvanced; def->set_default_value(new ConfigOptionFloat(0.)); + // ORCA: Relative bridge angle + def = this->add("relative_bridge_angle", coBool); + def->label = L("Relative bridge angle"); + def->category = L("Strength"); + def->tooltip = L("When enabled, the bridge angle values are added to the automatically calculated bridge direction instead of overriding it.\n" + "Recommended to add a small angle (<10°) to improve bridge covering in closed shapes."); + def->mode = comAdvanced; + def->set_default_value(new ConfigOptionBool(false)); + def = this->add("bridge_density", coPercent); def->label = L("External bridge density"); def->category = L("Strength"); @@ -2924,7 +2943,8 @@ void PrintConfigDef::init_fff_params() def = this->add("align_infill_direction_to_model", coBool); def->label = L("Align infill direction to model"); def->category = L("Strength"); - def->tooltip = L("Aligns infill and surface fill directions to follow the model's orientation on the build plate. When enabled, fill directions rotate with the model to maintain optimal strength characteristics."); + def->tooltip = L("Aligns infill, bridge, ironing and surface fill directions to follow the model's orientation on the build plate.\n" + "When enabled, directions rotate with the model to maintain optimal strength characteristics."); def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index aae20833d4..5cf095b588 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -1080,6 +1080,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionFloat, bottom_shell_thickness)) ((ConfigOptionFloat, bridge_angle)) ((ConfigOptionFloat, internal_bridge_angle)) // ORCA: Internal bridge angle override + ((ConfigOptionBool, relative_bridge_angle)) // ORCA: Relative bridge angle flag ((ConfigOptionFloat, bridge_flow)) ((ConfigOptionFloat, internal_bridge_flow)) ((ConfigOptionFloat, bridge_speed)) diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 65d624be46..732e0bb900 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -1275,6 +1275,7 @@ bool PrintObject::invalidate_state_by_config_options( || opt_key == "ensure_vertical_shell_thickness" || opt_key == "bridge_angle" || opt_key == "internal_bridge_angle" // ORCA: Internal bridge angle override + || opt_key == "relative_bridge_angle" // ORCA: Relative bridge angle //BBS || opt_key == "bridge_density" || opt_key == "internal_bridge_density") { @@ -3217,8 +3218,19 @@ void PrintObject::bridge_over_infill() } // ORCA: Internal bridge angle override - if (candidate.region->region().config().internal_bridge_angle > 0) - bridging_angle = candidate.region->region().config().internal_bridge_angle.value * PI / 180.0; // Convert degrees to radians + if (candidate.region->region().config().internal_bridge_angle.value > 0) { + const auto ®ion_config = candidate.region->region().config(); + const double custom_angle_rad = Geometry::deg2rad(region_config.internal_bridge_angle.value); + if (region_config.relative_bridge_angle.value) + bridging_angle += custom_angle_rad; + else { + bridging_angle = custom_angle_rad; + if (region_config.align_infill_direction_to_model) { + auto m = po->trafo().matrix(); + bridging_angle += std::atan2((double)m(1, 0), (double)m(0, 0)); + } + } + } boundary_plines.insert(boundary_plines.end(), anchors.begin(), anchors.end()); if (!lightning_area.empty() && !intersection(area_to_be_bridge, lightning_area).empty()) { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 1e5b350039..c4a4a6449e 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -655,7 +655,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, co toggle_field("bottom_surface_density", has_bottom_shell); for (auto el : { "infill_direction", "sparse_infill_line_width", "gap_fill_target","filter_out_gap_fill","infill_wall_overlap", - "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle", + "sparse_infill_speed", "bridge_speed", "internal_bridge_speed", "bridge_angle", "internal_bridge_angle", "relative_bridge_angle", "solid_infill_direction", "solid_infill_rotate_template", "internal_solid_infill_pattern", "solid_infill_filament", }) toggle_field(el, have_infill || has_solid_infill); diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 071a4b24eb..9df41c954d 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -2499,6 +2499,7 @@ void TabPrint::build() optgroup->append_single_option_line("extra_solid_infills", "strength_settings_infill#extra-solid-infill"); optgroup->append_single_option_line("bridge_angle", "strength_settings_advanced#bridge-infill-direction"); optgroup->append_single_option_line("internal_bridge_angle", "strength_settings_advanced#bridge-infill-direction"); // ORCA: Internal bridge angle override + optgroup->append_single_option_line("relative_bridge_angle", "strength_settings_advanced#bridge-infill-direction"); optgroup->append_single_option_line("minimum_sparse_infill_area", "strength_settings_advanced#minimum-sparse-infill-threshold"); optgroup->append_single_option_line("infill_combination", "strength_settings_advanced#infill-combination"); optgroup->append_single_option_line("infill_combination_max_layer_height", "strength_settings_advanced#max-layer-height");