mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-28 03:11:47 +00:00
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.
This commit is contained in:
@@ -18740,3 +18740,12 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Switch track at Filament Track Switch"
|
msgid "Switch track at Filament Track Switch"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Fan direction"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Cooling fan direction of the printer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Both"
|
||||||
|
msgstr ""
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
"1",
|
"1",
|
||||||
"6"
|
"6"
|
||||||
],
|
],
|
||||||
|
"fan_direction": "left",
|
||||||
"hotend_cooling_rate": [
|
"hotend_cooling_rate": [
|
||||||
"1.6",
|
"1.6",
|
||||||
"1.6",
|
"1.6",
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"0x0,325x0,325x320,0x320",
|
"0x0,325x0,325x320,0x320",
|
||||||
"25x0,350x0,350x320,25x320"
|
"25x0,350x0,350x320,25x320"
|
||||||
],
|
],
|
||||||
|
"fan_direction": "left",
|
||||||
"hotend_heating_rate": [
|
"hotend_heating_rate": [
|
||||||
"3.6",
|
"3.6",
|
||||||
"3.6",
|
"3.6",
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
"0x0,325x0,325x320,0x320",
|
"0x0,325x0,325x320,0x320",
|
||||||
"25x0,350x0,350x320,25x320"
|
"25x0,350x0,350x320,25x320"
|
||||||
],
|
],
|
||||||
|
"fan_direction": "left",
|
||||||
"hotend_heating_rate": [
|
"hotend_heating_rate": [
|
||||||
"3.6",
|
"3.6",
|
||||||
"3.6",
|
"3.6",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"extruder_variant_list": [
|
"extruder_variant_list": [
|
||||||
"Direct Drive Standard,Direct Drive High Flow"
|
"Direct Drive Standard,Direct Drive High Flow"
|
||||||
],
|
],
|
||||||
|
"fan_direction": "left",
|
||||||
"long_retractions_when_cut": [
|
"long_retractions_when_cut": [
|
||||||
"0",
|
"0",
|
||||||
"0"
|
"0"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"extruder_variant_list": [
|
"extruder_variant_list": [
|
||||||
"Direct Drive Standard,Direct Drive High Flow"
|
"Direct Drive Standard,Direct Drive High Flow"
|
||||||
],
|
],
|
||||||
|
"fan_direction": "left",
|
||||||
"long_retractions_when_cut": [
|
"long_retractions_when_cut": [
|
||||||
"0",
|
"0",
|
||||||
"0"
|
"0"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"extruder_variant_list": [
|
"extruder_variant_list": [
|
||||||
"Direct Drive Standard,Direct Drive High Flow"
|
"Direct Drive Standard,Direct Drive High Flow"
|
||||||
],
|
],
|
||||||
|
"fan_direction": "left",
|
||||||
"long_retractions_when_cut": [
|
"long_retractions_when_cut": [
|
||||||
"0",
|
"0",
|
||||||
"0"
|
"0"
|
||||||
|
|||||||
+192
-15
@@ -27,19 +27,20 @@ namespace Slic3r {
|
|||||||
namespace orientation {
|
namespace orientation {
|
||||||
|
|
||||||
struct CostItems {
|
struct CostItems {
|
||||||
float overhang;
|
float overhang = 0;
|
||||||
float bottom;
|
float bottom = 0;
|
||||||
float bottom_hull;
|
float bottom_hull = 0;
|
||||||
float contour;
|
float contour = 0;
|
||||||
float area_laf; // area_of_low_angle_faces
|
float area_laf = 0; // area_of_low_angle_faces
|
||||||
float area_projected; // area of projected 2D profile
|
float area_projected = 0; // area of projected 2D profile
|
||||||
float volume;
|
float volume = 0;
|
||||||
float area_total; // total area of all faces
|
float area_total = 0; // total area of all faces
|
||||||
float radius; // radius of bounding box
|
float radius = 0; // radius of bounding box
|
||||||
float height_to_bottom_hull_ratio; // affects stability, the lower the better
|
float height_to_bottom_hull_ratio = 0; // affects stability, the lower the better
|
||||||
float unprintability;
|
float unprintability = 0;
|
||||||
|
Eigen::VectorXf areas_cooling;
|
||||||
CostItems(CostItems const & other) = default;
|
CostItems(CostItems const & other) = default;
|
||||||
CostItems() { memset(this, 0, sizeof(*this)); }
|
CostItems() = default;
|
||||||
static std::string field_names() {
|
static std::string field_names() {
|
||||||
return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability";
|
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_max, z_max_hull; // max of projected z
|
||||||
Eigen::VectorXf z_median; // median of projected z
|
Eigen::VectorXf z_median; // median of projected z
|
||||||
Eigen::VectorXf z_mean; // mean of projected z
|
Eigen::VectorXf z_mean; // mean of projected z
|
||||||
|
Eigen::VectorXf areas_cooling; // weighted areas for cool direction
|
||||||
std::vector<Vec3f> face_normals;
|
std::vector<Vec3f> face_normals;
|
||||||
std::vector<Vec3f> face_normals_hull;
|
std::vector<Vec3f> face_normals_hull;
|
||||||
OrientParams params;
|
OrientParams params;
|
||||||
|
bool has_cooling_fan = false;
|
||||||
|
|
||||||
std::vector< Vec3f> orientations; // Vec3f == stl_normal
|
std::vector< Vec3f> orientations; // Vec3f == stl_normal
|
||||||
std::function<void(unsigned)> progressind = { }; // default empty indicator function
|
std::function<void(unsigned)> progressind = { }; // default empty indicator function
|
||||||
@@ -85,6 +87,7 @@ public:
|
|||||||
orient_mesh = orient_mesh_;
|
orient_mesh = orient_mesh_;
|
||||||
mesh = &orient_mesh->mesh;
|
mesh = &orient_mesh->mesh;
|
||||||
params = params_;
|
params = params_;
|
||||||
|
has_cooling_fan = orient_mesh->has_cooling_fan;
|
||||||
progressind = progressind_;
|
progressind = progressind_;
|
||||||
params.ASCENT = cos(PI - orient_mesh->overhang_angle * PI / 180); // use per-object overhang angle
|
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.
|
//To avoid flipping, we need to verify if there are orientations with same unprintability.
|
||||||
Vec3f n1 = {0, 0, 1};
|
Vec3f n1 = {0, 0, 1};
|
||||||
auto best_orientation = results_vector[0].first;
|
auto best_orientation = results_vector[0].first;
|
||||||
|
size_t best_index = 0;
|
||||||
|
|
||||||
for (int i = 1; i< results_vector.size()-1; i++) {
|
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].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;
|
best_orientation = n1;
|
||||||
break;
|
best_index = i;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
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();
|
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;
|
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);
|
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();
|
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
|
// height to bottom_hull_area ratio
|
||||||
//float total_max_z = z_projected.maxCoeff();
|
//float total_max_z = z_projected.maxCoeff();
|
||||||
//costs.height_to_bottom_hull_ratio = SQ(total_max_z) / (costs.bottom_hull + 1e-7);
|
//costs.height_to_bottom_hull_ratio = SQ(total_max_z) / (costs.bottom_hull + 1e-7);
|
||||||
@@ -468,6 +489,67 @@ public:
|
|||||||
|
|
||||||
return cost;
|
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<float>();
|
||||||
|
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<float>();
|
||||||
|
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<Vec3f> 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<Vec3f> 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<double>();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void _orient(OrientMeshs& meshs_,
|
void _orient(OrientMeshs& meshs_,
|
||||||
@@ -497,6 +579,13 @@ void _orient(OrientMeshs& meshs_,
|
|||||||
mesh_.orientation = orienter.process();
|
mesh_.orientation = orienter.process();
|
||||||
Geometry::rotation_from_two_vectors(mesh_.orientation, { 0,0,1 }, mesh_.axis, mesh_.angle, &mesh_.rotation_matrix);
|
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);
|
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();
|
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);
|
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<Vec3f> 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<double>(), machine_cool_dir.cast<double>(), axis, angle, &rotation_matrix);
|
||||||
|
mesh.rotate(angle, axis);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace arr
|
} // namespace arr
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -26,10 +26,18 @@ struct OrientMesh {
|
|||||||
TriangleMesh mesh; /// The real mesh data
|
TriangleMesh mesh; /// The real mesh data
|
||||||
double overhang_angle = 30;
|
double overhang_angle = 30;
|
||||||
double angle{ 0 };
|
double angle{ 0 };
|
||||||
|
double angle_vertical{ 0 };
|
||||||
Vec3d axis{ 0,0,1 };
|
Vec3d axis{ 0,0,1 };
|
||||||
|
Vec3d axis_vertical{ 0,0,1 };
|
||||||
Vec3d orientation{ 0,0,1 };
|
Vec3d orientation{ 0,0,1 };
|
||||||
Matrix3d rotation_matrix;
|
Vec3d orientation_vertical{ -1,0,0 };
|
||||||
Vec3d euler_angles;
|
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;
|
std::string name;
|
||||||
|
|
||||||
/// Optional setter function which can store arbitrary data in its closure
|
/// Optional setter function which can store arbitrary data in its closure
|
||||||
@@ -154,6 +162,9 @@ void orient(ModelObject* obj);
|
|||||||
|
|
||||||
void orient(ModelInstance* instance);
|
void orient(ModelInstance* instance);
|
||||||
|
|
||||||
|
// rotate z axis for cooling
|
||||||
|
void orient_for_cooling(TriangleMesh& mesh, const FanDirection& fan_dir);
|
||||||
|
|
||||||
}} // namespace Slic3r::orientment
|
}} // namespace Slic3r::orientment
|
||||||
|
|
||||||
#endif // MODELORIENT_HPP
|
#endif // MODELORIENT_HPP
|
||||||
|
|||||||
@@ -1390,7 +1390,7 @@ static std::vector<std::string> s_Preset_printer_options {
|
|||||||
"default_print_profile", "inherits",
|
"default_print_profile", "inherits",
|
||||||
"silent_mode",
|
"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",
|
"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",
|
"best_object_pos", "head_wrap_detect_zone",
|
||||||
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
|
"host_type", "print_host", "printhost_apikey", "flashforge_serial_number", "bbl_use_printhost", "printer_agent",
|
||||||
"print_host_webui",
|
"print_host_webui",
|
||||||
|
|||||||
@@ -512,6 +512,14 @@ static t_config_enum_values s_keys_map_NozzleType {
|
|||||||
};
|
};
|
||||||
CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(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 {
|
static t_config_enum_values s_keys_map_PrinterStructure {
|
||||||
{"undefine", int(PrinterStructure::psUndefine)},
|
{"undefine", int(PrinterStructure::psUndefine)},
|
||||||
{"corexy", int(PrinterStructure::psCoreXY)},
|
{"corexy", int(PrinterStructure::psCoreXY)},
|
||||||
@@ -3997,6 +4005,21 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comAdvanced;
|
def->mode = comAdvanced;
|
||||||
def->set_default_value(new ConfigOptionBool(false));
|
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<FanDirection>::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<FanDirection>(fdUndefine));
|
||||||
|
|
||||||
def = this->add("fan_speedup_time", coFloat);
|
def = this->add("fan_speedup_time", coFloat);
|
||||||
// Label is set in Tab.cpp in the Line object.
|
// Label is set in Tab.cpp in the Line object.
|
||||||
//def->label = L("Fan speed-up time");
|
//def->label = L("Fan speed-up time");
|
||||||
|
|||||||
@@ -362,6 +362,13 @@ enum LayerSeq {
|
|||||||
flsCustomize
|
flsCustomize
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum FanDirection {
|
||||||
|
fdUndefine = 0,
|
||||||
|
fdLeft,
|
||||||
|
fdRight,
|
||||||
|
fdBoth
|
||||||
|
};
|
||||||
|
|
||||||
static std::unordered_map<NozzleType, std::string>NozzleTypeEumnToStr = {
|
static std::unordered_map<NozzleType, std::string>NozzleTypeEumnToStr = {
|
||||||
{NozzleType::ntUndefine, "undefine"},
|
{NozzleType::ntUndefine, "undefine"},
|
||||||
{NozzleType::ntHardenedSteel, "hardened_steel"},
|
{NozzleType::ntHardenedSteel, "hardened_steel"},
|
||||||
@@ -1494,6 +1501,7 @@ PRINT_CONFIG_CLASS_DEFINE(
|
|||||||
((ConfigOptionEnumsGenericNullable,nozzle_type))
|
((ConfigOptionEnumsGenericNullable,nozzle_type))
|
||||||
((ConfigOptionInt, nozzle_hrc))
|
((ConfigOptionInt, nozzle_hrc))
|
||||||
((ConfigOptionBool, auxiliary_fan))
|
((ConfigOptionBool, auxiliary_fan))
|
||||||
|
((ConfigOptionEnum<FanDirection>, fan_direction))
|
||||||
((ConfigOptionBool, support_air_filtration))
|
((ConfigOptionBool, support_air_filtration))
|
||||||
((ConfigOptionBool, cooling_filter_enabled))
|
((ConfigOptionBool, cooling_filter_enabled))
|
||||||
((ConfigOptionEnum<PrinterStructure>,printer_structure))
|
((ConfigOptionEnum<PrinterStructure>,printer_structure))
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
|
|
||||||
#include "slic3r/Utils/FixModelByCgal.hpp"
|
#include "slic3r/Utils/FixModelByCgal.hpp"
|
||||||
#include "libslic3r/Format/bbs_3mf.hpp"
|
#include "libslic3r/Format/bbs_3mf.hpp"
|
||||||
|
#include "libslic3r/Orient.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
@@ -2560,6 +2561,15 @@ void ObjectList::load_shape_object(const std::string &type_name)
|
|||||||
// Create mesh
|
// Create mesh
|
||||||
BoundingBoxf3 bb;
|
BoundingBoxf3 bb;
|
||||||
TriangleMesh mesh = create_mesh(type_name, 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<ConfigOptionEnum<FanDirection>>("fan_direction")->value;
|
||||||
|
orientation::orient_for_cooling(mesh, config_dir);
|
||||||
|
}
|
||||||
// BBS: remove "Shape" prefix
|
// BBS: remove "Shape" prefix
|
||||||
load_mesh_object(mesh, _(type_name));
|
load_mesh_object(mesh, _(type_name));
|
||||||
wxGetApp().mainframe->update_title();
|
wxGetApp().mainframe->update_title();
|
||||||
|
|||||||
@@ -229,15 +229,32 @@ orientation::OrientMesh OrientJob::get_orient_mesh(ModelInstance* instance)
|
|||||||
auto obj = instance->get_object();
|
auto obj = instance->get_object();
|
||||||
om.name = obj->name;
|
om.name = obj->name;
|
||||||
om.mesh = obj->mesh(); // don't know the difference to obj->raw_mesh(). Both seem OK
|
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"))
|
if (obj->config.has("support_threshold_angle"))
|
||||||
om.overhang_angle = obj->config.opt_int("support_threshold_angle");
|
om.overhang_angle = obj->config.opt_int("support_threshold_angle");
|
||||||
else {
|
else {
|
||||||
const Slic3r::DynamicPrintConfig& config = wxGetApp().preset_bundle->full_config();
|
|
||||||
om.overhang_angle = config.opt_int("support_threshold_angle");
|
om.overhang_angle = config.opt_int("support_threshold_angle");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (config.has("fan_direction") && config.has("auxiliary_fan")) {
|
||||||
|
FanDirection config_dir = config.option<ConfigOptionEnum<FanDirection>>("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) {
|
om.setter = [instance](const OrientMesh& p) {
|
||||||
instance->rotate(p.rotation_matrix);
|
instance->rotate(p.rotation_matrix);
|
||||||
|
instance->rotate(p.rotation_matrix_vertical);
|
||||||
instance->get_object()->invalidate_bounding_box();
|
instance->get_object()->invalidate_bounding_box();
|
||||||
instance->get_object()->ensure_on_bed();
|
instance->get_object()->ensure_on_bed();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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_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("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("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_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");
|
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");
|
const bool support_parallel_printheads = printer_cfg.opt_bool("support_parallel_printheads");
|
||||||
toggle_line("parallel_printheads_count", support_parallel_printheads);
|
toggle_line("parallel_printheads_count", support_parallel_printheads);
|
||||||
|
|
||||||
|
toggle_line("fan_direction", m_config->opt_bool("auxiliary_fan"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user