From b12aad6a8c6d4f0f7b3342898822144d9188ab13 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Fri, 10 Jul 2026 16:21:48 +0800 Subject: [PATCH] feat(orient): auto-orient the largest overhang toward the cooling fan New printer option fan_direction (undefine/left/right/both, default undefine) declares which side the auxiliary part-cooling airflow comes from. When set and the printer has an auxiliary fan, auto-orient adds a yaw rotation so the dominant overhang area faces the airflow, and newly added primitive shapes are pre-oriented the same way (except the Cube, whose axis-aligned bounding box the pressure-advance pattern calibration depends on). - FanDirection enum + fan_direction printer option (Accessory group, enabled only with auxiliary_fan) - orient engine: weighted overhang areas per candidate, yaw-direction search, vertical rotation applied on top of the primary orientation; the cooling weights are taken from the candidate actually chosen, including the flat-bottom tie-break - orient_for_cooling() for primitive placement - set fan_direction=left on H2C/H2D/H2D Pro/X1/X1E/P1S 0.4 profiles (X1C/H2S/P2S/X2D/Qidi X-Max 4 already carried the key, which now takes effect) With fan_direction unset or no auxiliary fan the vertical rotation stays identity and auto-orient results are unchanged; slicing and g-code are never affected. --- localization/i18n/OrcaSlicer.pot | 9 + .../BBL/machine/Bambu Lab H2C 0.4 nozzle.json | 1 + .../BBL/machine/Bambu Lab H2D 0.4 nozzle.json | 1 + .../machine/Bambu Lab H2D Pro 0.4 nozzle.json | 1 + .../BBL/machine/Bambu Lab P1S 0.4 nozzle.json | 1 + .../BBL/machine/Bambu Lab X1 0.4 nozzle.json | 1 + .../BBL/machine/Bambu Lab X1E 0.4 nozzle.json | 1 + src/libslic3r/Orient.cpp | 207 ++++++++++++++++-- src/libslic3r/Orient.hpp | 15 +- src/libslic3r/Preset.cpp | 2 +- src/libslic3r/PrintConfig.cpp | 23 ++ src/libslic3r/PrintConfig.hpp | 8 + src/slic3r/GUI/GUI_ObjectList.cpp | 10 + src/slic3r/GUI/Jobs/OrientJob.cpp | 19 +- src/slic3r/GUI/Tab.cpp | 3 + 15 files changed, 283 insertions(+), 19 deletions(-) diff --git a/localization/i18n/OrcaSlicer.pot b/localization/i18n/OrcaSlicer.pot index 5f77005177..d37ac20a45 100644 --- a/localization/i18n/OrcaSlicer.pot +++ b/localization/i18n/OrcaSlicer.pot @@ -18740,3 +18740,12 @@ msgstr "" msgid "Switch track at Filament Track Switch" msgstr "" + +msgid "Fan direction" +msgstr "" + +msgid "Cooling fan direction of the printer" +msgstr "" + +msgid "Both" +msgstr "" diff --git a/resources/profiles/BBL/machine/Bambu Lab H2C 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab H2C 0.4 nozzle.json index 8cd1a172b9..270bc67fe6 100644 --- a/resources/profiles/BBL/machine/Bambu Lab H2C 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab H2C 0.4 nozzle.json @@ -31,6 +31,7 @@ "1", "6" ], + "fan_direction": "left", "hotend_cooling_rate": [ "1.6", "1.6", diff --git a/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json index fb44c350ff..0aa11e5c34 100644 --- a/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab H2D 0.4 nozzle.json @@ -24,6 +24,7 @@ "0x0,325x0,325x320,0x320", "25x0,350x0,350x320,25x320" ], + "fan_direction": "left", "hotend_heating_rate": [ "3.6", "3.6", diff --git a/resources/profiles/BBL/machine/Bambu Lab H2D Pro 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab H2D Pro 0.4 nozzle.json index e8113f5750..454a4879ca 100644 --- a/resources/profiles/BBL/machine/Bambu Lab H2D Pro 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab H2D Pro 0.4 nozzle.json @@ -27,6 +27,7 @@ "0x0,325x0,325x320,0x320", "25x0,350x0,350x320,25x320" ], + "fan_direction": "left", "hotend_heating_rate": [ "3.6", "3.6", diff --git a/resources/profiles/BBL/machine/Bambu Lab P1S 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab P1S 0.4 nozzle.json index 6d7efbb11b..e7d011aa54 100644 --- a/resources/profiles/BBL/machine/Bambu Lab P1S 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab P1S 0.4 nozzle.json @@ -28,6 +28,7 @@ "extruder_variant_list": [ "Direct Drive Standard,Direct Drive High Flow" ], + "fan_direction": "left", "long_retractions_when_cut": [ "0", "0" diff --git a/resources/profiles/BBL/machine/Bambu Lab X1 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab X1 0.4 nozzle.json index a8e9871954..d56f1094f8 100644 --- a/resources/profiles/BBL/machine/Bambu Lab X1 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab X1 0.4 nozzle.json @@ -34,6 +34,7 @@ "extruder_variant_list": [ "Direct Drive Standard,Direct Drive High Flow" ], + "fan_direction": "left", "long_retractions_when_cut": [ "0", "0" diff --git a/resources/profiles/BBL/machine/Bambu Lab X1E 0.4 nozzle.json b/resources/profiles/BBL/machine/Bambu Lab X1E 0.4 nozzle.json index 455469bab7..4b2fe3c755 100644 --- a/resources/profiles/BBL/machine/Bambu Lab X1E 0.4 nozzle.json +++ b/resources/profiles/BBL/machine/Bambu Lab X1E 0.4 nozzle.json @@ -28,6 +28,7 @@ "extruder_variant_list": [ "Direct Drive Standard,Direct Drive High Flow" ], + "fan_direction": "left", "long_retractions_when_cut": [ "0", "0" diff --git a/src/libslic3r/Orient.cpp b/src/libslic3r/Orient.cpp index 367d6b0967..ae1954087d 100644 --- a/src/libslic3r/Orient.cpp +++ b/src/libslic3r/Orient.cpp @@ -27,19 +27,20 @@ namespace Slic3r { namespace orientation { struct CostItems { - float overhang; - float bottom; - float bottom_hull; - float contour; - float area_laf; // area_of_low_angle_faces - float area_projected; // area of projected 2D profile - float volume; - float area_total; // total area of all faces - float radius; // radius of bounding box - float height_to_bottom_hull_ratio; // affects stability, the lower the better - float unprintability; + float overhang = 0; + float bottom = 0; + float bottom_hull = 0; + float contour = 0; + float area_laf = 0; // area_of_low_angle_faces + float area_projected = 0; // area of projected 2D profile + float volume = 0; + float area_total = 0; // total area of all faces + float radius = 0; // radius of bounding box + float height_to_bottom_hull_ratio = 0; // affects stability, the lower the better + float unprintability = 0; + Eigen::VectorXf areas_cooling; CostItems(CostItems const & other) = default; - CostItems() { memset(this, 0, sizeof(*this)); } + CostItems() = default; static std::string field_names() { return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability"; } @@ -68,10 +69,11 @@ public: Eigen::VectorXf z_max, z_max_hull; // max of projected z Eigen::VectorXf z_median; // median of projected z Eigen::VectorXf z_mean; // mean of projected z + Eigen::VectorXf areas_cooling; // weighted areas for cool direction std::vector face_normals; std::vector face_normals_hull; OrientParams params; - + bool has_cooling_fan = false; std::vector< Vec3f> orientations; // Vec3f == stl_normal std::function progressind = { }; // default empty indicator function @@ -85,6 +87,7 @@ public: orient_mesh = orient_mesh_; mesh = &orient_mesh->mesh; params = params_; + has_cooling_fan = orient_mesh->has_cooling_fan; progressind = progressind_; params.ASCENT = cos(PI - orient_mesh->overhang_angle * PI / 180); // use per-object overhang angle @@ -158,12 +161,14 @@ public: //To avoid flipping, we need to verify if there are orientations with same unprintability. Vec3f n1 = {0, 0, 1}; auto best_orientation = results_vector[0].first; + size_t best_index = 0; for (int i = 1; i< results_vector.size()-1; i++) { if (abs(results_vector[i].second.unprintability - results_vector[0].second.unprintability) < EPSILON && abs(results_vector[0].first.dot(n1)-1) > EPSILON) { - if (abs(results_vector[i].first.dot(n1)-1) < EPSILON*EPSILON) { + if (abs(results_vector[i].first.dot(n1)-1) < EPSILON*EPSILON) { best_orientation = n1; - break; + best_index = i; + break; } } else { @@ -172,6 +177,9 @@ public: } + // cooling weights are per-orientation, so take them from the orientation actually chosen + areas_cooling = results_vector[best_index].second.areas_cooling; + BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(6) << "best:" << best_orientation.transpose() << ", costs:" << results_vector[0].second.field_values(); std::cout << std::fixed << std::setprecision(6) << "best:" << best_orientation.transpose() << ", costs:" << results_vector[0].second.field_values() << std::endl; @@ -441,6 +449,19 @@ public: Eigen::MatrixXf laf_areas = ((normal_projection_abs.array() < params.LAF_MAX) * (normal_projection_abs.array() > params.LAF_MIN) * (z_max.array() > total_min_z + params.FIRST_LAY_H)).select(areas, 0); costs.area_laf = laf_areas.sum(); + if (has_cooling_fan) + { + // Angle range of overhang faces requiring cooling + float angle_thres_high = -0.6427f; + float angle_thres_low = -0.97f; + // compute the weighted overhang faces area + Eigen::VectorXf ones_f = Eigen::VectorXf::Ones(mesh->facets_count()); + auto overhang_area_condition = (normal_projection.array() < angle_thres_high && normal_projection.array() > angle_thres_low).eval(); + Eigen::VectorXf areas_ = (overhang_area_condition * !bottom_condition_2nd).select(areas, 0); + Eigen::VectorXf weighted_areas = areas_.cwiseProduct(ones_f - normal_projection); + costs.areas_cooling = weighted_areas; + } + // height to bottom_hull_area ratio //float total_max_z = z_projected.maxCoeff(); //costs.height_to_bottom_hull_ratio = SQ(total_max_z) / (costs.bottom_hull + 1e-7); @@ -468,6 +489,67 @@ public: return cost; } + + Vec3d find_cooling_direction2(Vec3d euler_angles, const Eigen::VectorXf& areas_in, TriangleMesh& mesh) + { + Vec3f machine_cool_dir = this->orient_mesh->cooling_direction.cast(); + const size_t num_faces = areas.rows(); + Vec3f best_direction = { 0, 0, 0 }; + + // 1. Make a copy of input mesh, rotate and translate to the best orientation + TriangleMesh mesh_copy = TriangleMesh(mesh.its); + mesh_copy.rotate_x(euler_angles(0, 0)); + mesh_copy.rotate_y(euler_angles(1, 0)); + mesh_copy.rotate_z(euler_angles(2, 0)); + auto bounding_box = mesh_copy.bounding_box(); + Eigen::VectorXf translate_distance = bounding_box.min.array().cast(); + Vec3d mesh_center = mesh_copy.center(); + mesh_copy.translate(-mesh_center(0), -mesh_center(1), -translate_distance(2)); + + // 2. sample cooling direction + const size_t sample_nums = 180; + std::vector cool_dirs; + for (size_t i = 0; i < sample_nums; i++) + { + float angle_deg = i * (360.0 / sample_nums); + float angle_rad = angle_deg * (PI / 180.0); + cool_dirs.push_back(Vec3f{ std::cos(angle_rad), std::sin(angle_rad), 0}); + } + + // 3. accumulate the weighted projected overhang area, find the max weighted project area direction + std::vector face_normals_copy = its_face_normals(mesh_copy.its); + float overhang_projected_max = 0.f; + float overhang_projected_origin = 0.f; + for (auto cool_dir : cool_dirs) + { + float overhang_projected_tmp = 0.f; + for (size_t i = 0; i < num_faces; i++) + { + float cool_dir_projection = face_normals_copy[i].dot(cool_dir); + if (areas_in[i] > 0 && cool_dir_projection > 0) + { + overhang_projected_tmp += areas_in[i] * cool_dir_projection; + } + } + if (overhang_projected_tmp > overhang_projected_max) + { + overhang_projected_max = overhang_projected_tmp; + best_direction = cool_dir; + } + if (cool_dir.dot(machine_cool_dir) > 0.999) + { + overhang_projected_origin = overhang_projected_tmp; + } + } + + // The symmetric model has similar overhang projection at all angles, so Z-axis rotation is unnecessary. + if (std::abs(overhang_projected_origin - overhang_projected_max) < 1.0f) + { + best_direction = machine_cool_dir; + } + BOOST_LOG_TRIVIAL(info) << "best cooling dir = " << best_direction.transpose() << "\n"; + return best_direction.cast(); + } }; void _orient(OrientMeshs& meshs_, @@ -497,6 +579,13 @@ void _orient(OrientMeshs& meshs_, mesh_.orientation = orienter.process(); Geometry::rotation_from_two_vectors(mesh_.orientation, { 0,0,1 }, mesh_.axis, mesh_.angle, &mesh_.rotation_matrix); mesh_.euler_angles = Geometry::extract_euler_angles(mesh_.rotation_matrix); + // find cool direction + if (mesh_.has_cooling_fan) + { + mesh_.orientation_vertical = orienter.find_cooling_direction2(mesh_.euler_angles, orienter.areas_cooling, mesh_.mesh); + BOOST_LOG_TRIVIAL(info) << "cooling direction: " << mesh_.orientation_vertical.transpose() << "\n"; + Geometry::rotation_from_two_vectors(mesh_.orientation_vertical, mesh_.cooling_direction, mesh_.axis_vertical, mesh_.angle_vertical, &mesh_.rotation_matrix_vertical); + } BOOST_LOG_TRIVIAL(debug) << "rotation_from_two_vectors: " << mesh_.orientation << "; " << mesh_.axis << "; " << mesh_.angle << "; euler: " << mesh_.euler_angles.transpose(); }}); } @@ -539,6 +628,94 @@ void orient(ModelInstance* instance) instance->rotate(rotation_matrix); } +void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir) +{ + Vec3f best_direction{ 0, 0, 0 }; + Vec3f machine_cool_dir{ 0, 0, 0 }; + + if (fan_dir == FanDirection::fdUndefine) + { + // no cooling fan, do not rotate along z axis + return; + } + else if (fan_dir == FanDirection::fdRight) + { + machine_cool_dir = { 1, 0, 0 }; // the cooling fan is on the right side. + } + else + { + // the cooling fan is on the left side or both side has cooling fans + machine_cool_dir = { -1, 0, 0 }; + } + + // 1. filter the overhang_areas + int nfaces = mesh.facets_count(); + auto face_normals = its_face_normals(mesh.its); + + Eigen::VectorXf normal_projection(nfaces, 1); + for (auto i = 0; i < nfaces; i++) + { + normal_projection(i) = face_normals[i].dot(Vec3f(0, 0, 1)); + } + float angle_thres_high = -0.6427f; + float angle_thres_low = -0.97f; + // 2. compute the weighted overhang faces area + Eigen::VectorXf weighted_areas = Eigen::VectorXf::Zero(nfaces); + for (int i = 0; i < nfaces; i++) + { + if (normal_projection(i) < angle_thres_high && normal_projection(i) > angle_thres_low) + { + weighted_areas(i) = mesh.its.facet_area(i) * (1.0f - normal_projection(i)); + } + } + + const size_t sample_nums = 180; + std::vector cool_dirs; + for (size_t i = 0; i < sample_nums; i++) + { + float angle_deg = i * (360.0 / sample_nums); + float angle_rad = angle_deg * (PI / 180.0); + cool_dirs.push_back(Vec3f{ std::cos(angle_rad), std::sin(angle_rad), 0 }); + } + + // 3. accumulate the weighted projected overhang area, find the max weighted project area direction + float overhang_projected_max = 0.f; + float overhang_projected_origin = 0.f; + for (auto cool_dir : cool_dirs) + { + float overhang_projected_tmp = 0.f; + for (size_t i = 0; i < nfaces; i++) + { + float cool_dir_projection = face_normals[i].dot(cool_dir); + if (weighted_areas[i] > 0 && cool_dir_projection > 0) + { + overhang_projected_tmp += weighted_areas[i] * cool_dir_projection; + } + } + if (overhang_projected_tmp > overhang_projected_max) + { + overhang_projected_max = overhang_projected_tmp; + best_direction = cool_dir; + } + if (cool_dir.dot(machine_cool_dir) > 0.999) + { + overhang_projected_origin = overhang_projected_tmp; + } + } + + // The symmetric model has similar overhang projection at all angles, so Z-axis rotation is unnecessary. + if (std::abs(overhang_projected_origin - overhang_projected_max) < 1.0f) + { + return; + } + + // rotate the mesh + Vec3d axis; + double angle; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(best_direction.cast(), machine_cool_dir.cast(), axis, angle, &rotation_matrix); + mesh.rotate(angle, axis); +} } // namespace arr } // namespace Slic3r diff --git a/src/libslic3r/Orient.hpp b/src/libslic3r/Orient.hpp index 76be064cd1..30dbdd3a20 100644 --- a/src/libslic3r/Orient.hpp +++ b/src/libslic3r/Orient.hpp @@ -26,10 +26,18 @@ struct OrientMesh { TriangleMesh mesh; /// The real mesh data double overhang_angle = 30; double angle{ 0 }; + double angle_vertical{ 0 }; Vec3d axis{ 0,0,1 }; + Vec3d axis_vertical{ 0,0,1 }; Vec3d orientation{ 0,0,1 }; - Matrix3d rotation_matrix; - Vec3d euler_angles; + Vec3d orientation_vertical{ -1,0,0 }; + Matrix3d rotation_matrix = Matrix3d::Identity(); + Matrix3d rotation_matrix_vertical = Matrix3d::Identity(); + Vec3d euler_angles = {0, 0, 0}; + Vec3d euler_angles_vertical = {0, 0, 0}; + Vec3d cooling_direction = {0, 0, 0}; + bool has_cooling_fan{false}; + std::string name; /// Optional setter function which can store arbitrary data in its closure @@ -154,6 +162,9 @@ void orient(ModelObject* obj); void orient(ModelInstance* instance); +// rotate z axis for cooling +void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir); + }} // namespace Slic3r::orientment #endif // MODELORIENT_HPP diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 06532db5df..b25d7d5a7f 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -1390,7 +1390,7 @@ static std::vector s_Preset_printer_options { "default_print_profile", "inherits", "silent_mode", "scan_first_layer", "enable_power_loss_recovery", "wrapping_detection_layers", "wrapping_exclude_area", "machine_load_filament_time", "machine_unload_filament_time", "machine_tool_change_time", "time_cost", "machine_pause_gcode", "template_custom_gcode", - "nozzle_type", "nozzle_hrc","auxiliary_fan", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","cooling_filter_enabled","printer_structure","farthest_point_timelapse", + "nozzle_type", "nozzle_hrc","auxiliary_fan", "fan_direction", "nozzle_volume","upward_compatible_machine", "z_hop_types", "travel_slope", "retract_lift_enforce","support_chamber_temp_control","support_air_filtration","cooling_filter_enabled","printer_structure","farthest_point_timelapse", "best_object_pos", "head_wrap_detect_zone", "host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent", "print_host_webui", diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index 45c5b3df45..5940aa003e 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -512,6 +512,14 @@ static t_config_enum_values s_keys_map_NozzleType { }; CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(NozzleType) +static t_config_enum_values s_keys_map_FanDirection { + { "undefine", int(FanDirection::fdUndefine) }, + { "left", int(FanDirection::fdLeft) }, + { "right", int(FanDirection::fdRight) }, + { "both", int(FanDirection::fdBoth) } +}; +CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(FanDirection) + static t_config_enum_values s_keys_map_PrinterStructure { {"undefine", int(PrinterStructure::psUndefine)}, {"corexy", int(PrinterStructure::psCoreXY)}, @@ -3997,6 +4005,21 @@ void PrintConfigDef::init_fff_params() def->mode = comAdvanced; def->set_default_value(new ConfigOptionBool(false)); + def = this->add("fan_direction", coEnum); + def->label = L("Fan direction"); + def->tooltip = L("Cooling fan direction of the printer"); + def->enum_keys_map = &ConfigOptionEnum::get_enum_values(); + def->enum_values.push_back("undefine"); + def->enum_values.push_back("left"); + def->enum_values.push_back("right"); + def->enum_values.push_back("both"); + def->enum_labels.push_back(L("Undefined")); + def->enum_labels.push_back(L("Left")); + def->enum_labels.push_back(L("Right")); + def->enum_labels.push_back(L("Both")); + def->mode = comDevelop; + def->set_default_value(new ConfigOptionEnum(fdUndefine)); + def = this->add("fan_speedup_time", coFloat); // Label is set in Tab.cpp in the Line object. //def->label = L("Fan speed-up time"); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 78335fe4ed..1e1dae74a7 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -362,6 +362,13 @@ enum LayerSeq { flsCustomize }; +enum FanDirection { + fdUndefine = 0, + fdLeft, + fdRight, + fdBoth +}; + static std::unordered_mapNozzleTypeEumnToStr = { {NozzleType::ntUndefine, "undefine"}, {NozzleType::ntHardenedSteel, "hardened_steel"}, @@ -1494,6 +1501,7 @@ PRINT_CONFIG_CLASS_DEFINE( ((ConfigOptionEnumsGenericNullable,nozzle_type)) ((ConfigOptionInt, nozzle_hrc)) ((ConfigOptionBool, auxiliary_fan)) + ((ConfigOptionEnum, fan_direction)) ((ConfigOptionBool, support_air_filtration)) ((ConfigOptionBool, cooling_filter_enabled)) ((ConfigOptionEnum,printer_structure)) diff --git a/src/slic3r/GUI/GUI_ObjectList.cpp b/src/slic3r/GUI/GUI_ObjectList.cpp index fcdf005aad..0e040277a5 100644 --- a/src/slic3r/GUI/GUI_ObjectList.cpp +++ b/src/slic3r/GUI/GUI_ObjectList.cpp @@ -38,6 +38,7 @@ #include "slic3r/Utils/FixModelByCgal.hpp" #include "libslic3r/Format/bbs_3mf.hpp" +#include "libslic3r/Orient.hpp" #include "libslic3r/PrintConfig.hpp" #ifdef __WXMSW__ @@ -2560,6 +2561,15 @@ void ObjectList::load_shape_object(const std::string &type_name) // Create mesh BoundingBoxf3 bb; TriangleMesh mesh = create_mesh(type_name, bb); + // Rotate the largest overhang area toward the part cooling fan so new shapes start in a + // cooling friendly orientation. Skip the plain cube: the pressure advance pattern + // calibration reuses it as an axis-aligned anchor and scales it by its bounding box, + // so its orientation must stay fixed. + const Slic3r::DynamicPrintConfig& full_config = wxGetApp().preset_bundle->full_config(); + if (type_name != "Cube" && full_config.has("fan_direction") && full_config.has("auxiliary_fan")) { + FanDirection config_dir = full_config.option>("fan_direction")->value; + orientation::orient_for_cooling(mesh, config_dir); + } // BBS: remove "Shape" prefix load_mesh_object(mesh, _(type_name)); wxGetApp().mainframe->update_title(); diff --git a/src/slic3r/GUI/Jobs/OrientJob.cpp b/src/slic3r/GUI/Jobs/OrientJob.cpp index 2a7a7556fc..7347bad6a2 100644 --- a/src/slic3r/GUI/Jobs/OrientJob.cpp +++ b/src/slic3r/GUI/Jobs/OrientJob.cpp @@ -229,15 +229,32 @@ orientation::OrientMesh OrientJob::get_orient_mesh(ModelInstance* instance) auto obj = instance->get_object(); om.name = obj->name; om.mesh = obj->mesh(); // don't know the difference to obj->raw_mesh(). Both seem OK + const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config(); if (obj->config.has("support_threshold_angle")) om.overhang_angle = obj->config.opt_int("support_threshold_angle"); else { - const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config(); om.overhang_angle = config.opt_int("support_threshold_angle"); } + if (config.has("fan_direction") && config.has("auxiliary_fan")) { + FanDirection config_dir = config.option>("fan_direction")->value; + if (config_dir == FanDirection::fdUndefine || !config.opt_bool("auxiliary_fan")) { + // no part cooling airflow to face, keep the orientation around the z axis unchanged + om.cooling_direction = {0, 0, 0}; + } else if (config_dir == FanDirection::fdRight) { + // the part cooling airflow comes from the right side + om.cooling_direction = {1, 0, 0}; + om.has_cooling_fan = true; + } else { + // the part cooling airflow comes from the left side, or from both sides + om.cooling_direction = {-1, 0, 0}; + om.has_cooling_fan = true; + } + } + om.setter = [instance](const OrientMesh& p) { instance->rotate(p.rotation_matrix); + instance->rotate(p.rotation_matrix_vertical); instance->get_object()->invalidate_bounding_box(); instance->get_object()->ensure_on_bed(); }; diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 0f4a0caf96..929b90da06 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -4956,6 +4956,7 @@ void TabPrinter::build_fff() optgroup->append_single_option_line("nozzle_type", "printer_basic_information_accessory#nozzle-type"); optgroup->append_single_option_line("nozzle_hrc", "printer_basic_information_accessory#nozzle-hrc"); optgroup->append_single_option_line("auxiliary_fan", "printer_basic_information_accessory#auxiliary-part-cooling-fan"); + optgroup->append_single_option_line("fan_direction"); optgroup->append_single_option_line("support_chamber_temp_control", "printer_basic_information_accessory#support-controlling-chamber-temperature"); optgroup->append_single_option_line("support_air_filtration", "printer_basic_information_accessory#support-air-filtration"); @@ -5892,6 +5893,8 @@ void TabPrinter::toggle_options() const bool support_parallel_printheads = printer_cfg.opt_bool("support_parallel_printheads"); toggle_line("parallel_printheads_count", support_parallel_printheads); + + toggle_line("fan_direction", m_config->opt_bool("auxiliary_fan")); }