diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 54ad24d..960deda 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -1237,6 +1237,8 @@ add_library(slic3r src/main/jni/libnest2d/src/libnest2d.cpp + src/main/jni/bbl/Orient.cpp + src/main/jni/slicebeam/beam_native.cpp src/main/jni/slicebeam/GLModel.cpp src/main/jni/slicebeam/GLShader.cpp diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/OrientationMenu.java b/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/OrientationMenu.java index 78f8dde..dc79b73 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/OrientationMenu.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/components/bed_menu/OrientationMenu.java @@ -456,8 +456,7 @@ public class OrientationMenu extends ListBedMenu { dz %= 360; model.rotate(j, Math.toRadians(dx), Math.toRadians(dy), Math.toRadians(dz)); - model.getBoundingBoxExact(j, bbMin, bbMax); - model.translate(j, 0, 0, -bbMin.z); + model.ensureOnBed(j); fragment.getGlView().getRenderer().setSelectionRotation(0, 0, 0); fragment.getGlView().getRenderer().invalidateGlModel(j); diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/render/GLRenderer.java b/app/src/main/java/ru/ytkab0bp/slicebeam/render/GLRenderer.java index 6504301..04f1d06 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/render/GLRenderer.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/render/GLRenderer.java @@ -421,8 +421,7 @@ public class GLRenderer implements GLSurfaceView.Renderer { if (minPlane != -1) { GLModel glModel = flattenPlanes.get(minPlane); model.flattenRotate(selectedObject, glModel); - model.getBoundingBoxExact(j, bbMin, bbMax); - model.translate(j, 0, 0, -bbMin.z); + model.ensureOnBed(selectedObject); invalidateGlModel(selectedObject); for (int k = 0, l = flattenPlanes.size(); k < l; k++) { diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java index 4bd6885..313e5fb 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Model.java @@ -86,6 +86,10 @@ public class Model { resetBoundingBox(); } + public void ensureOnBed(int i) { + Native.model_ensure_on_bed(pointer, i); + } + public void scale(int i, double x, double y, double z) { Native.model_scale(pointer, i, x, y, z); } diff --git a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java index af936b4..0dd245c 100644 --- a/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java +++ b/app/src/main/java/ru/ytkab0bp/slicebeam/slic3r/Native.java @@ -66,6 +66,7 @@ class Native { static native boolean model_is_left_handed(long ptr, int i); static native void model_translate(long ptr, int i, double x, double y, double z); static native void model_translate_global(long ptr, double x, double y, double z); + static native void model_ensure_on_bed(long ptr, int i); static native void model_scale(long ptr, int i, double x, double y, double z); static native void model_rotate(long ptr, int i, double x, double y, double z); static native void model_flatten_rotate(long ptr, int i, long surfacePtr); diff --git a/app/src/main/jni/bbl/Orient.cpp b/app/src/main/jni/bbl/Orient.cpp new file mode 100644 index 0000000..df81310 --- /dev/null +++ b/app/src/main/jni/bbl/Orient.cpp @@ -0,0 +1,610 @@ +#include "Orient.hpp" +#include "Geometry.hpp" +#include +#include +#include +#include +#include "bbl_utils.hpp" + +#if defined(_MSC_VER) && defined(__clang__) +#define BOOST_NO_CXX17_HDR_STRING_VIEW +#endif + +#include +#include +#include + +#undef MAX3 +#define MAX3(a, b, c) std::max(std::max(a,b),c) + +#undef MEDIAN +#define MEDIAN3(a, b, c) std::max(std::min(a,b), std::min(std::max(a,b),c)) +#ifndef SQ +#define SQ(x) ((x)*(x)) +#endif + +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; + + CostItems(CostItems const &other) = default; + + CostItems() { memset(this, 0, sizeof(*this)); } + + static std::string field_names() { + return " overhang, bottom, bothull, contour, A_laf, A_prj, unprintability"; + } + + std::string field_values() { + std::stringstream ss; + ss << std::fixed << std::setprecision(1); + ss << overhang << ",\t" << bottom << ",\t" << bottom_hull << ",\t" << contour + << ",\t" << area_laf << ",\t" << area_projected << ",\t" << unprintability; + return ss.str(); + } + }; + + +// A class encapsulating the libnest2d Nester class and extending it with other +// management and spatial index structures for acceleration. + class AutoOrienter { + public: + int face_count_hull; + OrientMesh *orient_mesh = NULL; + TriangleMesh *mesh; + TriangleMesh mesh_convex_hull; + Eigen::MatrixXf normals, normals_quantize, normals_hull, normals_hull_quantize; + Eigen::VectorXf areas, areas_hull; + Eigen::VectorXf is_apperance; // whether a facet is outer apperance + Eigen::MatrixXf z_projected; + 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 + std::vector face_normals; + std::vector face_normals_hull; + OrientParams params; + + + std::vector orientations; // Vec3f == stl_normal + std::function progressind = {}; // default empty indicator function + + public: + AutoOrienter(OrientMesh *orient_mesh_, + const OrientParams ¶ms_, + std::function progressind_, + std::function stopcond_) { + orient_mesh = orient_mesh_; + mesh = &orient_mesh->mesh; + params = params_; + progressind = progressind_; + params.ASCENT = cos(PI - orient_mesh->overhang_angle * PI / + 180); // use per-object overhang angle + + // BOOST_LOG_TRIVIAL(info) << orient_mesh->name << ", angle=" << orient_mesh->overhang_angle << ", params.ASCENT=" << params.ASCENT; + // std::cout << orient_mesh->name << ", angle=" << orient_mesh->overhang_angle << ", params.ASCENT=" << params.ASCENT; + + preprocess(); + } + + AutoOrienter(TriangleMesh *mesh_) { + mesh = mesh_; + preprocess(); + } + + struct VecHash { + size_t operator()(const Vec3f &n1) const { + return std::hash()(int(n1(0) * 100 + 100)) + + std::hash()(int(n1(1) * 100 + 100)) * 101 + + std::hash()(int(n1(2) * 100 + 100)) * 10221; + } + }; + + Vec3f quantize_vec3f(const Vec3f n1) { + return Vec3f(floor(n1(0) * 1000) / 1000, floor(n1(1) * 1000) / 1000, + floor(n1(2) * 1000) / 1000); + } + + Vec3d process() { + orientations = {{0, 0, -1}}; // original orientation + + area_cumulation_accurate(face_normals, normals_quantize, areas, 10); + + area_cumulation_accurate(face_normals_hull, normals_hull_quantize, areas_hull, 14); + + add_supplements(); + + if (progressind) + progressind(20); + + remove_duplicates(); + + if (progressind) + progressind(30); + + std::unordered_map results; + BOOST_LOG_TRIVIAL(info) << CostItems::field_names(); + std::cout << CostItems::field_names() << std::endl; + for (int i = 0; i < orientations.size(); i++) { + auto orientation = -orientations[i]; + + project_vertices(orientation); + + auto cost_items = get_features(orientation, params.min_volume); + + float unprintability = target_function(cost_items, params.min_volume); + + results[orientation] = cost_items; + + BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(4) << "orientation:" + << orientation.transpose() << ", cost:" << std::fixed + << std::setprecision(4) << cost_items.field_values(); + std::cout << std::fixed << std::setprecision(4) << "orientation:" + << orientation.transpose() << ", cost:" << std::fixed + << std::setprecision(4) << cost_items.field_values() << std::endl; + } + if (progressind) + progressind(60); + + typedef std::pair PAIR; + std::vector results_vector(results.begin(), results.end()); + sort(results_vector.begin(), results_vector.end(), + [](const PAIR &p1, const PAIR &p2) { + return p1.second.unprintability < p2.second.unprintability; + }); + + if (progressind) + progressind(80); + + //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; + + 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) { + best_orientation = n1; + break; + } + } else { + break; + } + + } + + 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; + + return best_orientation.cast(); + } + + void preprocess() { + int count_apperance = 0; + { + int face_count = mesh->facets_count(); + auto its = mesh->its; + face_normals = its_face_normals(its); + areas = Eigen::VectorXf::Zero(face_count); + is_apperance = Eigen::VectorXf::Zero(face_count); + normals = Eigen::MatrixXf::Zero(face_count, 3); + normals_quantize = Eigen::MatrixXf::Zero(face_count, 3); + for (size_t i = 0; i < face_count; i++) { + float area = get_its_facet_area(its, i); + normals.row(i) = face_normals[i]; + normals_quantize.row(i) = quantize_vec3f(face_normals[i]); + areas(i) = area; + // TODO: Fix this // is_apperance(i) = (its.get_property(i).type == EnumFaceTypes::eExteriorAppearance); + count_apperance += (is_apperance(i) == 1); + } + } + + if (orient_mesh) + BOOST_LOG_TRIVIAL(debug) << orient_mesh->name << ", count_apperance=" + << count_apperance; + + // get convex hull statistics + { + mesh_convex_hull = mesh->convex_hull_3d(); + //mesh_convex_hull.write_binary("convex_hull_debug.stl"); + + int face_count = mesh_convex_hull.facets_count(); + auto its = mesh_convex_hull.its; + face_count_hull = mesh_convex_hull.facets_count(); + face_normals_hull = its_face_normals(its); + areas_hull = Eigen::VectorXf::Zero(face_count); + normals_hull = Eigen::MatrixXf::Zero(face_count_hull, 3); + normals_hull_quantize = Eigen::MatrixXf::Zero(face_count_hull, 3); + for (size_t i = 0; i < face_count; i++) { + float area = get_its_facet_area(its, i); + //We cannot use quantized vector here, the accumulated error will result in bad orientations. + normals_hull.row(i) = face_normals_hull[i]; + normals_hull_quantize.row(i) = quantize_vec3f(face_normals_hull[i]); + areas_hull(i) = area; + } + } + } + + void area_cumulation(const Eigen::MatrixXf &normals_, const Eigen::VectorXf &areas_, + int num_directions = 10) { + std::unordered_map alignments; + // init to 0 + for (auto i = 0; i < areas_.size(); i++) + alignments.insert(std::pair(normals_.row(i), 0)); + // cumulate areas + for (auto i = 0; i < areas_.size(); i++) { + alignments[normals_.row(i)] += areas_(i); + } + + typedef std::pair PAIR; + std::vector align_counts(alignments.begin(), alignments.end()); + sort(align_counts.begin(), align_counts.end(), + [](const PAIR &p1, const PAIR &p2) { return p1.second > p2.second; }); + + num_directions = std::min((size_t) num_directions, align_counts.size()); + for (size_t i = 0; i < num_directions; i++) { + orientations.push_back(align_counts[i].first); + //orientations.push_back(its_face_normals(mesh->its)[i]); + BOOST_LOG_TRIVIAL(debug) << align_counts[i].first.transpose() << ", area: " + << align_counts[i].second; + } + } + + //This function is to make sure to return the accurate normal rather than quantized normal + void area_cumulation_accurate(std::vector &normals_, + const Eigen::MatrixXf &quantize_normals_, + const Eigen::VectorXf &areas_, int num_directions = 10) { + std::unordered_map, Vec3f>, VecHash> alignments_; + Vec3f n1 = {0, 0, 0}; + std::vector current_areas = {0, 0}; + // init to 0 + for (auto i = 0; i < areas_.size(); i++) { + alignments_.insert( + std::pair(quantize_normals_.row(i), std::pair(current_areas, n1))); + } + // cumulate areas + for (auto i = 0; i < areas_.size(); i++) { + alignments_[quantize_normals_.row(i)].first[1] += areas_(i); + if (areas_(i) > alignments_[quantize_normals_.row(i)].first[0]) { + alignments_[quantize_normals_.row(i)].second = normals_[i]; + alignments_[quantize_normals_.row(i)].first[0] = areas_(i); + } + } + + typedef std::pair, Vec3f>> PAIR; + std::vector align_counts(alignments_.begin(), alignments_.end()); + sort(align_counts.begin(), align_counts.end(), [](const PAIR &p1, const PAIR &p2) { + return p1.second.first[1] > p2.second.first[1]; + }); + + num_directions = std::min((size_t) num_directions, align_counts.size()); + for (size_t i = 0; i < num_directions; i++) { + orientations.push_back(align_counts[i].second.second); + BOOST_LOG_TRIVIAL(debug) << align_counts[i].second.second.transpose() + << ", area: " << align_counts[i].second.first[1]; + } + } + + void add_supplements() { + std::vector vecs = {{0, 0, -1}, + {0.70710678, 0, -0.70710678}, + {0, 0.70710678, -0.70710678}, + {-0.70710678, 0, -0.70710678}, + {0, -0.70710678, -0.70710678}, + {1, 0, 0}, + {0.70710678, 0.70710678, 0}, + {0, 1, 0}, + {-0.70710678, 0.70710678, 0}, + {-1, 0, 0}, + {-0.70710678, -0.70710678, 0}, + {0, -1, 0}, + {0.70710678, -0.70710678, 0}, + {0.70710678, 0, 0.70710678}, + {0, 0.70710678, 0.70710678}, + {-0.70710678, 0, 0.70710678}, + {0, -0.70710678, 0.70710678}, + {0, 0, 1}}; + orientations.insert(orientations.end(), vecs.begin(), vecs.end()); + } + + /// + /// remove duplicate orientations + /// + /// tolerance. default 0.01 =sin(0.57\degree) + void remove_duplicates(double tol = 0.0000001) { + for (auto it = orientations.begin() + 1; it < orientations.end();) { + bool duplicate = false; + for (auto it_ok = orientations.begin(); it_ok < it; it_ok++) { + if (it_ok->isApprox(*it, tol)) { + duplicate = true; + break; + } + } + const Vec3f all_zero = {0, 0, 0}; + if (duplicate || it->isApprox(all_zero, tol)) + it = orientations.erase(it); + else + it++; + } + } + + void project_vertices(Vec3f orientation) { + int face_count = mesh->facets_count(); + auto its = mesh->its; + z_projected.resize(face_count, 3); + z_max.resize(face_count, 1); + z_median.resize(face_count, 1); + z_mean.resize(face_count, 1); + for (size_t i = 0; i < face_count; i++) { + float z0 = get_its_vertex(its, i, 0).dot(orientation); + float z1 = get_its_vertex(its, i, 1).dot(orientation); + float z2 = get_its_vertex(its, i, 2).dot(orientation); + z_projected(i, 0) = z0; + z_projected(i, 1) = z1; + z_projected(i, 2) = z2; + z_max(i) = MAX3(z0, z1, z2); + z_median(i) = MEDIAN3(z0, z1, z2); + z_mean(i) = (z0 + z1 + z2) / 3; + } + + z_max_hull.resize(mesh_convex_hull.facets_count(), 1); + its = mesh_convex_hull.its; + for (auto i = 0; i < z_max_hull.rows(); i++) { + float z0 = get_its_vertex(its, i, 0).dot(orientation); + float z1 = get_its_vertex(its, i, 1).dot(orientation); + float z2 = get_its_vertex(its, i, 2).dot(orientation); + z_max_hull(i) = MAX3(z0, z1, z2); + } + } + + static Eigen::VectorXi + argsort(const Eigen::VectorXf &vec, std::string order = "ascend") { + Eigen::VectorXi ind = Eigen::VectorXi::LinSpaced(vec.size(), 0, + vec.size() - 1);//[0 1 2 3 ... N-1] + std::function rule; + if (order == "ascend") { + rule = [vec](int i, int j) -> bool { + return vec(i) < vec(j); + }; + } else { + rule = [vec](int i, int j) -> bool { + return vec(i) > vec(j); + }; + } + std::sort(ind.data(), ind.data() + ind.size(), rule); + return ind; + + //sorted_vec.resize(vec.size()); + //for (int i = 0; i < vec.size(); i++) { + // sorted_vec(i) = vec(ind(i)); + //} + } + + // previously calc_overhang + CostItems get_features(Vec3f orientation, bool min_volume = true) { + CostItems costs; + costs.area_total = area_of_boundingbox(mesh->bounding_box()); + costs.radius = mesh->bounding_box().radius(); + // volume + costs.volume = + mesh->stats().volume > 0 ? mesh->stats().volume : its_volume(mesh->its); + + float total_min_z = z_projected.minCoeff(); + // filter bottom area + auto bottom_condition = + z_max.array() < total_min_z + this->params.FIRST_LAY_H - EPSILON; + auto bottom_condition_hull = + z_max_hull.array() < total_min_z + this->params.FIRST_LAY_H - EPSILON; + auto bottom_condition_2nd = + z_max.array() < total_min_z + this->params.FIRST_LAY_H / 2.f - EPSILON; + //The first layer is sliced on half of the first layer height. + //The bottom area is measured by accumulating first layer area with the facets area below first layer height. + //By combining these two factors, we can avoid the wrong orientation of large planar faces while not influence the + //orientations of complex objects with small bottom areas. + costs.bottom = bottom_condition.select(areas, 0).sum() * 0.5 + + bottom_condition_2nd.select(areas, 0).sum(); + + // filter overhang + Eigen::VectorXf normal_projection(normals.rows(), + 1);// = this->normals.dot(orientation); + for (auto i = 0; i < normals.rows(); i++) { + normal_projection(i) = normals.row(i).dot(orientation); + } + auto areas_appearance = areas.cwiseProduct( + (is_apperance * params.APPERANCE_FACE_SUPP + + Eigen::VectorXf::Ones(is_apperance.rows(), is_apperance.cols()))); + auto overhang_areas = ((normal_projection.array() < params.ASCENT) * + (!bottom_condition_2nd)).select(areas_appearance, 0); + Eigen::MatrixXf inner = normal_projection.array() - params.ASCENT; + inner = inner.cwiseMin(0).cwiseAbs(); + if (min_volume) { + Eigen::MatrixXf heights = z_mean.array() - total_min_z; + costs.overhang = (heights.array() * overhang_areas.array() * + inner.array()).sum(); + } else { + costs.overhang = overhang_areas.array().cwiseAbs().sum(); + } + + { + // contour perimeter +#if 1 + // the simple way for contour is even better for faces of small bridges + costs.contour = 4 * sqrt(costs.bottom); +#else + float contour = 0; + int face_count = mesh->facets_count(); + auto its = mesh->its; + int contour_amout = 0; + for (size_t i = 0; i < face_count; i++) + { + if (bottom_condition(i)) { + Eigen::VectorXi index = argsort(z_projected.row(i)); + stl_vertex line = its.get_vertex(i, index(0)) - its.get_vertex(i, index(1)); + contour += line.norm(); + contour_amout++; + } + } + costs.contour += contour + params.CONTOUR_AMOUNT * contour_amout; +#endif + } + + // bottom of convex hull + costs.bottom_hull = (bottom_condition_hull).select(areas_hull, 0).sum(); + + // low angle faces + auto normal_projection_abs = normal_projection.cwiseAbs(); + 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(); + + // 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); + + return costs; + } + + float target_function(CostItems &costs, bool min_volume) { + float cost = 0; + float bottom = costs.bottom;//std::min(costs.bottom, params.BOTTOM_MAX); + float bottom_hull = costs.bottom_hull;// std::min(costs.bottom_hull, params.BOTTOM_HULL_MAX); + if (min_volume) { + float overhang = costs.overhang / 25; + cost = params.TAR_A * (overhang + params.TAR_B) + params.RELATIVE_F * + (/*costs.volume/100*/ + overhang * + params.TAR_C + + params.TAR_D + + params.TAR_LAF * + costs.area_laf * + params.use_low_angle_face) / + (params.TAR_D + + params.CONTOUR_F * + costs.contour + + params.BOTTOM_F * bottom + + params.BOTTOM_HULL_F * + bottom_hull + + params.TAR_E * overhang + + params.TAR_PROJ_AREA * + costs.area_projected); + } else { + float overhang = costs.overhang; + cost = params.RELATIVE_F * (costs.overhang * params.TAR_C + params.TAR_D + + params.TAR_LAF * costs.area_laf * + params.use_low_angle_face) / + (params.TAR_D + params.CONTOUR_F * costs.contour + + params.BOTTOM_F * bottom + params.BOTTOM_HULL_F * bottom_hull + + params.TAR_PROJ_AREA * costs.area_projected); + } + cost += (costs.bottom < params.BOTTOM_MIN) * + 100;// +(costs.height_to_bottom_hull_ratio > params.height_to_bottom_hull_ratio_MIN) * 110; + + costs.unprintability = costs.unprintability = cost; + + return cost; + } + }; + + void _orient(OrientMeshs &meshs_, + const OrientParams ¶ms, + std::function progressfn, + std::function stopfn) { + if (!params.parallel) { + for (size_t i = 0; i != meshs_.size(); ++i) { + auto &mesh_ = meshs_[i]; + progressfn(i, mesh_.name); + //auto progressfn_i = [&](unsigned cnt) {progressfn(cnt, "Orienting " + mesh_.name); }; + AutoOrienter orienter(&mesh_, params, /*progressfn_i*/{}, stopfn); + mesh_.orientation = orienter.process(); + Geometry::rotation_from_two_vectors(mesh_.orientation, {0, 0, 1}, mesh_.axis, + mesh_.angle, &mesh_.rotation_matrix); + BOOST_LOG_TRIVIAL(info) << std::fixed << std::setprecision(3) << "v,phi: " + << mesh_.axis.transpose() << ", " << mesh_.angle; + //flush_logs(); + } + } else { + tbb::parallel_for(tbb::blocked_range(0, meshs_.size()), + [&meshs_, ¶ms, progressfn, stopfn]( + const tbb::blocked_range &range) { + for (size_t i = range.begin(); i != range.end(); ++i) { + auto &mesh_ = meshs_[i]; + progressfn(i, mesh_.name); + AutoOrienter orienter(&mesh_, params, {}, stopfn); + 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); + BOOST_LOG_TRIVIAL(debug) << "rotation_from_two_vectors: " + << mesh_.orientation.transpose() + << "; axis: " + << mesh_.axis.transpose() + << "; angle: " << mesh_.angle + << "; euler: " + << mesh_.euler_angles.transpose() + << ", rotation_matrix:\n" + << mesh_.rotation_matrix; + } + }); + } + } + + void orient(OrientMeshs &arrangables, + const OrientMeshs &excludes, + const OrientParams ¶ms) { + + auto &cfn = params.stopcondition; + auto &pri = params.progressind; + + _orient(arrangables, params, pri, cfn); + + } + + void orient(ModelObject *obj) { + auto m = obj->mesh(); + AutoOrienter orienter(&m); + Vec3d orientation = orienter.process(); + Vec3d axis; + double angle; + Geometry::rotation_from_two_vectors(orientation, {0, 0, 1}, axis, angle, nullptr); + + obj->rotate(angle, axis); + obj->ensure_on_bed(); + } + + void orient(ModelInstance *instance) { + auto m = instance->get_object()->mesh(); + AutoOrienter orienter(&m); + Vec3d orientation = orienter.process(); + Vec3d axis; + double angle; + Matrix3d rotation_matrix; + Geometry::rotation_from_two_vectors(orientation, {0, 0, 1}, axis, angle, &rotation_matrix); + + rotate_model_instance(instance, rotation_matrix); + } + + + } // namespace arr +} // namespace Slic3r diff --git a/app/src/main/jni/bbl/Orient.hpp b/app/src/main/jni/bbl/Orient.hpp new file mode 100644 index 0000000..76be064 --- /dev/null +++ b/app/src/main/jni/bbl/Orient.hpp @@ -0,0 +1,159 @@ +#ifndef ORIENT_HPP +#define ORIENT_HPP + +#include "libslic3r/Model.hpp" + +namespace Slic3r { + +namespace orientation { + + +/// A logical bed representing an object not being orientd. Either the orient +/// has not yet successfully run on this OrientPolygon or it could not fit the +/// object due to overly large size or invalid geometry. +static const constexpr int UNORIENTD = -1; + +/// Input/Output structure for the orient() function. The mesh field will not +/// be modified during orientment. Instead, the translation and rotation fields +/// will mark the needed transformation for the polygon to be in the orientd +/// position. These can also be set to an initial offset and rotation. +/// +/// The bed_idx field will indicate the logical bed into which the +/// polygon belongs: UNORIENTD means no place for the polygon +/// (also the initial state before orient), 0..N means the index of the bed. +/// Zero is the physical bed, larger than zero means a virtual bed. +struct OrientMesh { + TriangleMesh mesh; /// The real mesh data + double overhang_angle = 30; + double angle{ 0 }; + Vec3d axis{ 0,0,1 }; + Vec3d orientation{ 0,0,1 }; + Matrix3d rotation_matrix; + Vec3d euler_angles; + std::string name; + + /// Optional setter function which can store arbitrary data in its closure + std::function setter = nullptr; + + /// Helper function to call the setter with the orient data arguments + void apply() const { if (setter) setter(*this); } + +}; + +// params for minimizing support area +struct OrientParamsArea { + float TAR_A = 0.015f; + float TAR_B = 0.177f; + float RELATIVE_F = 20; + float CONTOUR_F = 0.5f; + float BOTTOM_F = 2.5f; + float BOTTOM_HULL_F = 0.1f; + float TAR_C = 0.1f; + float TAR_D = 1; + float TAR_E = 0.0115f; + float FIRST_LAY_H = 0.2f;//0.0475; + float VECTOR_TOL = -0.00083f; + float NEGL_FACE_SIZE = 0.01f; + float ASCENT = -0.5f; + float PLAFOND_ADV = 0.0599f; + float CONTOUR_AMOUNT = 0.0182427f; + float OV_H = 2.574f; + float height_offset = 2.3728f; + float height_log = 0.041375f; + float height_log_k = 1.9325457f; + float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face 0.9997f + float LAF_MIN = 0.97f; // cos(14\degree) 0.9703f + float TAR_LAF = 0.001f; //0.01f + float TAR_PROJ_AREA = 0.1f; + float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the object may be unstable + float BOTTOM_MAX = 2000; // max bottom area. If get to it the object is stable enough (further increase bottom area won't do more help) + float height_to_bottom_hull_ratio_MIN = 1; + float BOTTOM_HULL_MAX = 2000;// max bottom hull area + float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face + + float overhang_angle = 60.f; + bool use_low_angle_face = true; + bool min_volume = false; + Eigen::Vector3f fun_dir; + + /// Allow parallel execution. + bool parallel = true; + + /// Progress indicator callback called when an object gets packed. + /// The unsigned argument is the number of items remaining to pack. + std::function progressind = {}; + + /// A predicate returning true if abort is needed. + std::function stopcondition = {}; + + OrientParamsArea() = default; +}; + +struct OrientParams { + float TAR_A = 0.01f;//0.128f; + float TAR_B = 0.177f; + float RELATIVE_F= 6.610621027964314f; + float CONTOUR_F = 0.23228623269775997f; + float BOTTOM_F = 1.167152017941474f; + float BOTTOM_HULL_F = 0.1f; + float TAR_C = 0.24308070476924726f; + float TAR_D = 0.6284515508160871f; + float TAR_E = 0;//0.032157292647062234; + float FIRST_LAY_H = 0.2f;//0.029; + float VECTOR_TOL = -0.0011163303070972383f; + float NEGL_FACE_SIZE = 0.1f; + float ASCENT= -0.5f; + float PLAFOND_ADV = 0.04079208948120519f; + float CONTOUR_AMOUNT = 0.0101472219892684f; + float OV_H = 1.0370178217794535f; + float height_offset = 2.7417608343142073f; + float height_log = 0.06442030687034085f; + float height_log_k = 0.3933594673063997f; + float LAF_MAX = 0.999f; // cos(1.4\degree) for low angle face //0.9997f; + float LAF_MIN= 0.9703f; // cos(14\degree) 0.9703f; + float TAR_LAF = 0.01f; //0.1f + float TAR_PROJ_AREA = 0.1f; + float BOTTOM_MIN = 0.1f; // min bottom area. If lower than it the objects may be unstable + float BOTTOM_MAX = 2000; //400 + float height_to_bottom_hull_ratio_MIN = 1; + float BOTTOM_HULL_MAX = 2000;// max bottom hull area to clip //600 + float APPERANCE_FACE_SUPP=3; // penalty of generating supports on appearance face + + float overhang_angle = 60.f; + bool use_low_angle_face = true; + bool min_volume = false; + Eigen::Vector3f fun_dir; + + + /// Allow parallel execution. + bool parallel = false; + + /// Progress indicator callback called when an object gets packed. + /// The unsigned argument is the number of items remaining to pack. + std::function progressind = {}; + + /// A predicate returning true if abort is needed. + std::function stopcondition = {}; + + OrientParams() = default; +}; + +using OrientMeshs = std::vector; + +/** + * \brief Orients the input polygons. + + * \param items Input vector of OrientMeshs. The transformation, rotation + * and bin_idx fields will be changed after the call finished and can be used + * to apply the result on the input polygon. + */ +void orient(OrientMeshs &items, const OrientMeshs &excludes, const OrientParams ¶ms = {}); + +// this function should be deleted, since rotating objects are so complicated that its inherited transformation may be a trouble +void orient(ModelObject* obj); + +void orient(ModelInstance* instance); + +}} // namespace Slic3r::orientment + +#endif // MODELORIENT_HPP diff --git a/app/src/main/jni/bbl/bbl_utils.hpp b/app/src/main/jni/bbl/bbl_utils.hpp new file mode 100644 index 0000000..593c966 --- /dev/null +++ b/app/src/main/jni/bbl/bbl_utils.hpp @@ -0,0 +1,56 @@ +#ifndef BBL_UTILS_HPP +#define BBL_UTILS_HPP + +#include "libslic3r/Point.hpp" + +typedef enum { + eNormal, // normal face + eSmallOverhang, // small overhang + eSmallHole, // face with small hole + eExteriorAppearance, // exterior appearance + eMaxNumFaceTypes +}EnumFaceTypes; + +namespace Slic3r { + namespace Geometry { + void rotation_from_two_vectors(Vec3d& from, Vec3d to, Vec3d& rotation_axis, double& phi, Matrix3d* rotation_matrix) { + const Matrix3d m = Eigen::Quaterniond().setFromTwoVectors(from, to).toRotationMatrix(); + const Eigen::AngleAxisd aa(m); + rotation_axis = aa.axis(); + phi = aa.angle(); + if (rotation_matrix) + *rotation_matrix = m; + } + + Vec3d extract_euler_angles(const Eigen::Matrix& rotation_matrix) { + // The extracted "rotation" is a triplet of numbers such that Geometry::rotation_transform + // returns the original transform. Because of the chosen order of rotations, the triplet + // is not equivalent to Euler angles in the usual sense. + Vec3d angles = rotation_matrix.eulerAngles(2,1,0); + std::swap(angles(0), angles(2)); + return angles; + } + } + + double area_of_boundingbox(BoundingBoxf3 bb) { + return double(bb.max(0) - bb.min(0)) * (bb.max(1) - bb.min(1)); + } + + stl_vertex get_its_vertex(indexed_triangle_set& its, int facet_idx, int vertex_idx) { + return its.vertices[its.indices[facet_idx][vertex_idx]]; + } + + float get_its_facet_area(indexed_triangle_set& its, int facet_idx) { + return std::abs((get_its_vertex(its, facet_idx, 0) - get_its_vertex(its, facet_idx, 1)) + .cross(get_its_vertex(its, facet_idx, 0) - get_its_vertex(its, facet_idx, 2)).norm()) / 2; + } + + void rotate_model_instance(ModelInstance* obj, Matrix3d& rotation_matrix) { + auto m_transformation = obj->get_transformation(); + auto rotation = m_transformation.get_rotation_matrix(); + rotation = rotation_matrix * rotation; + obj->set_rotation(Geometry::Transformation(rotation).get_rotation()); + } +} + +#endif //BBL_UTILS_HPP diff --git a/app/src/main/jni/slicebeam/beam_native.cpp b/app/src/main/jni/slicebeam/beam_native.cpp index 7f2c21b..61dc6a8 100644 --- a/app/src/main/jni/slicebeam/beam_native.cpp +++ b/app/src/main/jni/slicebeam/beam_native.cpp @@ -458,6 +458,11 @@ extern "C" { model->model.objects[i]->translate(x, y, z); } + JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1ensure_1on_1bed(JNIEnv* env, jclass, jlong ptr, jint i) { + ModelRef* model = (ModelRef *) (intptr_t) ptr; + model->model.objects[i]->ensure_on_bed(false); + } + JNIEXPORT void JNICALL Java_ru_ytkab0bp_slicebeam_slic3r_Native_model_1scale(JNIEnv* env, jclass, jlong ptr, jint i, jdouble x, jdouble y, jdouble z) { ModelRef* model = (ModelRef *) (intptr_t) ptr; Vec3d factor(x, y, z);